moncef
All writing

·9 min readbackendarchitecture

The AADL 3 night, and how to build a form a million people submit at once

What really breaks when the whole country opens the same form at the same minute, and the simple steps developers can take so it does not happen to their platform.

Contents11 sections

On Friday 5 July 2024, at 21:00, the AADL 3 registration opened. If you are Algerian, you probably remember that night, or someone in your family does. Error messages, pages that never load, people trying until 3 in the morning, and queues outside cyber cafés.

By 10:00 the next morning, 358,000 people had registered. After 14 days, more than 1.4 million.1 The figures are from Algérie Eco (6 and 8 July 2024) and Algérie360 (January 2025). The ministry said the slowdowns came from the huge number of people trying to register at the same time, and that it stopped dozens of hacking attempts.

I don't know how this platform is built from the inside, so this post is not about blaming anyone. It is about a problem that every developer in Algeria will face one day: a form that the whole country needs on the same night. Bac results or university orientation. I asked myself a simple question: if Selance had to build it, what would we do?

8 registrations per second? That's it?#

358,000 registrations in about 13 hours is around 8 registrations per second.

Anyone reading that number will say it is small: a normal database can write thousands of rows per second. And that is exactly the point: the problem was never the amount of data. The problem is the shape of the traffic: hundreds of thousands of people arriving in the same minute, and every person who waits too long presses refresh, which sends one more request to a server that is already drowning.

So we don't design for the average. We design for the spike, and for the retries that the spike creates.

To see what that means, here is a model of the whole night, from 21:00 to 10:00, on one server. The red is the work the server was asked for and could not do. The fixes are explained below: switch them on one by one and watch the night change.

registration.nightInteractive demo
Fixes
load on the server×100×10what the server can do
registeredwanted to register
21:0000:0003:0006:0009:00
Registered by 10:00
358,426
Error pages
22.6M
Refreshes
15.2M
Duplicate rows
190,793

A model, tuned so the night with no fix ends near the real 358,000. peak ×68.0

Switch the fixes on and replay the same night on the same server.

And the data? It stays in Algeria#

A platform like this holds the ID number, the phone and the family situation of millions of citizens. Under Law 18-07 (amended by Law 25-11 in 2025), sending personal data abroad needs an authorization from the National Authority, and it is forbidden when it can touch public security.2 A summary of the law and its amendments by DLA Piper. For a public platform, the answer is simple: the backend and the data stay on servers in Algeria.

So the backend and the data run on open source tools, on your own servers in an Algerian data center. The only thing that can live outside is the HTML page, because it holds no personal data.

The page doesn't need a server#

The page that shows the form is the same for everyone. There is no reason for the application to build it again for every visitor.

Build it as a static HTML page and put it on a CDN like Cloudflare. The page holds no personal data, so one million page loads are answered from the location closest to the visitor and never reach your servers. Your servers only receive the request that really matters: the submission.

Warning

One thing to be careful with

The submission must go straight to the backend in Algeria, on a separate subdomain (like api), not through Cloudflare, so personal data never passes through servers abroad. And if you don't want even the page outside, Nginx or Varnish in the data center can do the same job.

Flow diagram: The page abroad, the data in AlgeriaALGERIAABROADloads the pagesubmits, directlyone rowa jobthe heavy workVisitorCloudflare CDNstatic HTML pageapiseparate subdomainqueueworkersDatabase
The page abroad, the data in Algeria

A flow of 6 parts.

  • Visitor, person
  • Cloudflare CDN (static HTML page), external service
  • api (separate subdomain), highlighted
  • queue
  • workers
  • Database, database

Algeria holds api, queue, workers, Database.

Abroad holds Cloudflare CDN.

Connections:

  • Visitor to Cloudflare CDN: loads the page
  • Visitor to api: submits, directly (highlighted)
  • api to Database: one row (highlighted)
  • api to queue: a job
  • queue to workers
  • workers to Database: the heavy work (highlighted)

In many outages, the server dies just from serving the page, before anyone even reaches the submit button. The problem starts with opening the page, not with registering.

Take the request, do the work later#

Most forms do everything inside one request: check the ID, check eligibility, write in many tables, send the SMS, then show the confirmation. Every one of these steps keeps a connection open while the line behind it grows.

The fix is to split it in two:

  1. The submit endpoint: validates the input, writes one row, and answers right away with a reference number.
  2. The workers: read from a queue at the speed the system can handle, and do the heavy work (eligibility, duplicates, SMS).
Sequence diagram: An answer in a second, the work after01 POST /register02 one row03 a new job04 202, your number is 4821THE USER HAS A NUMBER, THE WORK GOES ON IN THE BACKGROUND05 takes a job06 eligibility and duplicatesat the pace the system can handle07 the confirmation08 a textUserapiDatabasequeueworkerSMS
An answer in a second, the work after

A sequence between User, person; api, highlighted; Database, database; queue; worker; SMS, external service.

  1. User to api: POST /register
  2. api to Database: one row (highlighted)
  3. api to queue: a new job
  4. api to User: 202, your number is 4821 (a reply)
  5. The user has a number, the work goes on in the background
  6. worker to queue: takes a job
  7. worker to Database: eligibility and duplicates (highlighted)
  8. Note on worker and Database: at the pace the system can handle
  9. worker to SMS: the confirmation
  10. SMS to User: a text (a reply)

A worker here is just a separate program running in the background on your own servers. It is not connected to any user. It takes one job from the queue, finishes it, then takes the next one. The queue itself can be RabbitMQ or Redis, both open source. If one million requests arrive in one hour, the queue holds them, and the workers finish them in a few hours at a steady pace, without the database ever feeling the spike.

The user gets "received, your number is 4821" in less than a second. The heavy work happens in the next minutes, and nobody is staring at a loading spinner.

Pressed submit five times? Nothing happens#

When a page hangs, people press submit again, and again. If each press creates a new record, you get duplicates. If each press starts the full process, your server does the same work five times while it is already overloaded.

Try it: press Register the way people pressed it that night, and watch the table.

submit.twiceInteractive demo
National ID1099 4041 2033 5700 14
Server

without a key every request writes a row4 rows

  1. POST /register4821
  2. POST /register4822
  3. POST /register4823
  4. POST /register4824
48215700 14
48225700 14
48235700 14
48245700 14

Your number: 4824

4 rows for one person

with idempotency same ID, same answer1 rows

  1. POST /register4821
  2. POST /register4821
  3. POST /register4821
  4. POST /register4821
48215700 14

Your number: 4821

Four impatient presses on a slow server. Your turn: press Register.

Press Register a few times: without a key every press is a new row, with the ID as the key it stays one.

The solution is called idempotency. Give each submission a key, and here the natural key is the national ID number. If the same key comes again, return the first result without doing the work again. With a unique index, the database does the hard part for you:

register.tsTypeScript
// national_id has a UNIQUE index, so a second submit inserts nothing
const row = await db
  .insert(registrations)
  .values({ nationalId, phone, wilaya })
  .onConflictDoNothing({ target: registrations.nationalId })
  .returning({ ref: registrations.ref });

const ref = row[0]?.ref ?? (await findRef(nationalId));
return c.json({ ref }, 202);

And this also answers the question everyone was asking in Facebook groups that night: "am I registered or not?". Submit again, and the system tells you, safely.

A line is better than a crash#

If you know that one million people will come at 21:00, you can decide how many enter at once.

A virtual waiting room (a small service you host yourself, in front of the application, that gives each visitor a ticket with their number) lets in only the number of users your backend can serve, and shows everyone else their place in the line. It turns a crash into a queue. A queue is frustrating, but it is honest and fair: people are served in order, not by who refreshes at the lucky moment.

Bots take your place#

A public form with a "first come, first served" rule attracts scripts. There is even a browser extension on the Chrome Web Store made to fill the AADL 3 form automatically. Every automated request takes a place from a real person.

  • Rate limit at the entrance: per IP and per ID number, in Nginx, before the request reaches your application.
  • Add a challenge on submit: a self hosted one like ALTCHA (proof of work, no foreign service), on the submit step, not on the page load.
  • Protect the SMS code: limit this endpoint the most, because every SMS sent through the local operator costs money.

The database falls first#

Adding more application servers is easy. The database is not. When you add twenty servers in a hurry, they open twenty times more connections, and the database falls instead of the servers.

  • Use a connection pooler (like PgBouncer) in front of the database.
  • Keep the write small: one insert, with an index on the ID number.
  • Cache everything that can be cached (the list of wilayas, the rules, the page itself) so it never touches the database.

Try it before you announce it#

Nothing above is proven until you send real traffic to it. Tools like k6 or Locust can simulate tens of thousands of users from a laptop. Run them from inside Algeria too, against the real data center, because the network between the users and the servers is part of what you are testing.

Test at ten times the traffic you expect. Test with users who retry when the page is slow, because real users do that. Fix the first thing that breaks, then run it again.

What if the problem is the rule, not the server?#

This is the part developers rarely have a say in, but I think we should. The rules said that requests are processed in the order of their registration date and time. This tells every citizen the same thing: be there in the first minute, or you lose your place. The rule itself creates the spike.

Some product decisions can make the technical problem disappear:

  • Open registration by wilaya, or by the last digit of the ID number, across several days.
  • Keep registration open for two weeks, then do a random draw, so arriving first gives no advantage.
  • Say clearly that the order inside the first day does not matter.

Here are the same 1.4 million people over the same 14 days, under three different rules. Switch the rule and watch the peak:

rules.vs.peakInteractive demo
Rule
×108 a second, what the first night held
day 1day 4day 7day 10day 14
Busiest hour
×10.4
Hours over capacity
4

Everyone races for the first minute.

The same 1.4 million people over the same 14 days, under three rules.

My take

The cheapest request is the one that never needed to arrive at 21:00.

In short#

If you are about to launch something the public will rush to:

  1. Backend and data hosted in Algeria, with open source tools.
  2. Static page on a CDN, submissions straight to the backend in Algeria.
  3. One small write per submission, everything else in a queue with background workers.
  4. Idempotency on the ID number.
  5. A waiting room for the opening hour.
  6. Rate limits and a self hosted challenge.
  7. A pooled and protected database.
  8. A load test at ten times the expected traffic, with retries.
  9. Rules that spread the traffic instead of concentrating it.

None of this is new. These are the same tools that keep online stores alive on Black Friday, and most of them cost less than one night of bad news.

If you have something to add, or you spot a mistake in this post, send it to me in a private message. Thanks.

At Selance we build platforms for businesses in Algeria and the Gulf that need to stay up on their busiest day. If you have a launch coming and you want someone to look at your architecture, let's talk.

Notes

  1. The figures are from Algérie Eco (6 and 8 July 2024) and Algérie360 (January 2025). ↩︎

  2. A summary of the law and its amendments by DLA Piper. ↩︎

Was this useful?

Your project

Working on something like this?

If this is close to what you are building, I can help. Book a call and we will look at it together, or write to me about it.