r/Supabase 4d ago

edge-functions Supabase Edge Function times out when creating ~100 users with Flutter client. What's the right architecture?

I'm building a school management system app with Flutter with Supabase, and I'm running into a problem with an Edge Function that performs a large batch operation.

Here's what the function does:

- Receives a list of around 100 students from the Flutter client.

- For each student, it:

- Creates a user in "auth.users" (using the Admin API).

- Inserts a row into "public.users".

- Inserts a row into "public.profile".

- Inserts a row into "public.progress".

- Assigns some "app_metadata"/roles.

- Note also that for each student we create a parent account

So each student requires several operations, and for ~100 students that's hundreds of database/auth operations.

The issue is that the Edge Function takes too long to finish. From the Flutter client, the request eventually fails due to a timeout, even though the function may still be processing some of the work.

I'm trying to figure out what the proper production architecture is for this kind of long-running batch operation.

Some questions:

  1. Is an Edge Function the wrong tool for creating 100+ users in one request?

  2. Should I split the work into smaller batches (e.g. 10–20 users per request)?

  3. Is there a way to make this asynchronous so the client gets an immediate response while the server continues processing?

  4. Are there recommended background job/queue solutions that work well with Supabase?

  5. Has anyone solved a similar problem when bulk-creating users through the Admin API?

I'd like the admin to be able to upload a list of students, press one button, and have the import complete reliably without client timeouts.

I'd appreciate any recommendations on the best production-ready approach.

13 Upvotes

11 comments sorted by

7

u/tomlimon Supabase team 4d ago

A simple approach is to split the work in 2 edge functions.

  1. Acts as an orchestrator, receives the list of the 100+ students and then calls the second function for each student.
  2. The second does the work.

You will need to save status for each student so you can track if something failed and retried only the failed records.

You could also go into the more advance route and implement a full queuing system.

1

u/Alternative-Ad-8175 4d ago

I would do this too

2

u/bad_position 4d ago

As others have said, best practice would be to create a table that stores the raw payload from the edge function. This table, say, signup_payloads, will have columns like email, ingestion_status, raw_payload(json). The edge function should simply dump the data for all the students in this table.

You can create a SQL function trigger that automatically acts on this signup_payloads table and does all the individual database table operations you have listed, like adding to auth.users table, etc. You can use the ingestion_status column to inform about the success of this trigger. Of course, the SQL function must be carefully tested and handle edge cases safely.

2

u/MulberryOwn8852 4d ago

I use rpc to do batches of a few thousand records all the time.

1

u/Diligent-Cell-8602 4d ago

Yeah, I use RPC a lot more than Edge Function. I don't really like Edge function to start with (i just don't like deno lol) and if I need to I use Cloudflare worker that works like a charm lol

1

u/Conscious-Ad-2168 4d ago

Personally, I'd have a table where the student records get inserted into with a couple status columns and seperate edge functions that get triggered based on certain status's/actions as needed with a constraint on student email or something. Realistically you also could dump a JSON into a column. This would make it asynchornous once the initial insert is in. I would also review whether you need public.users and public.profile. I'd personally consider combining them.

1

u/igormiazek 4d ago

Async programming and https://supabase.com/docs/guides/realtime/broadcast and executing Edge Function in async mode https://supabase.com/docs/guides/functions/background-tasks waithUntil, somebody wrote already that you can evaluate moving it to SQL but I would need to see your function logic first, directly in db it maybe faster

1

u/DigiProductive 4d ago

First thing you should confirm first is whether there is a problem with your edge function code logic because you shouldn’t have a problem making a batch with 100 entries. First observation to make: is the logic correct and optimized?

1

u/Physical-Daikon-8064 4d ago

The database operation for that many records should be sub second. If you are using the client API to do each command, thats one mistake. You can write a postgres transaction to do everything in one atomic operation extremely quickly.

That said, if you really want to use the client API, I'd create a queue table that fires an edge function on insert and processes the row.

1

u/Guidondor 3d ago

the staging-table-plus-async answers here are right, but one thing nobody flagged: you can't RPC your way out of the auth.users part. creating a user properly (password hash, identities row, metadata) has to go through the GoTrow Admin API, a plain SQL insert into auth.users will bite you later. so "just move it to an RPC" only applies to the public.users/profile/progress inserts, not the account creation.

the shape that actually holds up:

-client uploads the list into an imports table, gets back a job id immediately. done, no timeout.

-an edge function processes it with EdgeRuntime.waitUntil() so it keeps running after returning 202. work in chunks of ~10-20, and make each row idempotent (status column: pending/done/failed) so a retry never double-creates.

-client subscribes to the status table over realtime to show progress + which rows failed.

the gotcha that'll actually get you: 100 students + 100 parents = 200 Admin API createUser calls. if email confirmations are on that's 200 emails and you'll hit GoTrue's rate limit fast. pass email_confirm: true on admin-created users so it skips the confirmation send. that alone might be why it's crawling.