Projetar iFood em escala é orquestrar sistemas independentes com latência baixa e consistência financeira.

Recursos seleccionados para complementar tu lectura
Software Architect
Pós-graduado em arquitetura de software e soluções. Conecto profundidade técnica com resultados de negócio para entregar produtos que as pessoas realmente usam. Também mentoro desenvolvedores e criadores em programas ao vivo, podcasts e iniciativas de comunidade focadas em tecnologia inclusiva.
Checklist de 47 puntos para detectar bugs, riesgos de seguridad y problemas de rendimiento antes del lanzamiento.
Continúa explorando temas similares
Templates probados en producción, usados por desarrolladores. Ahorra semanas de setup en tu próximo proyecto.
Consultorías modulares con diagnóstico técnico, plan de acción y acompañamiento directo. Desde auditorías express hasta CTO fraccionado.
2 cupos para consultorías en el Q2
Diagramas utlizados disponíveis no link https://link.excalidraw.com/l/7XRBb57RGJp/AtcIsUy9BEw
Às 12:06, um usuário em São Paulo faz pedido de almoço. Nos próximos 10 minutos, milhares de usuários na mesma região fazem pedidos parecidos.
Ao mesmo tempo:
A UX esperada continua simples:
No backend, isso é orquestração distribuída com múltiplos atores:
Esse artigo foca no que realmente quebra em escala e como desenhar para evitar.
| Requisito | Meta | Motivo |
|---|---|---|
| Disponibilidade discovery | 99,99% | alto volume e entrada do funil |
| Sucesso checkout | > 99,9% | caminho de receita |
| Latência order create | < 500ms p99 | confirmação imediata |
| Tempo de dispatch | < 2s p99 | atraso aqui vira atraso de ETA |
| Delay de tracking | < 3s p95 | confiança do usuário |
| Durabilidade de pedido | sem perda após ack | requisito financeiro |
| Pico operacional | 8x nos horários críticos | padrão de carga diurna |
Premissas:
DAU: 35 milhões
Pedidos/dia: 12 milhões
Restaurantes ativos: 450 mil
Entregadores ativos/dia: 1,2 milhão
Location ping: 1 a cada 3s quando ativo
Pico almoço/jantar: 8x média
Pedidos/s medio = 12.000.000 / 86.400 ~= 139
Pico ~= 1.112 pedidos/s
Checkout req/s medio ~= 520
Pico ~= 4.160
Discovery req/s medio ~= 34.722
Pico ~= 208.332
Assumindo 450 mil entregadores ativos simultâneos em pico:
pings/s = 450.000 / 3 ~= 150.000
payload médio 120B => ~18MB/s (+ overhead)
order row ~4KB => 12M/dia ~= 48GB/dia
order events ~600B * 240M ~= 144GB/dia
trilha de localização amostrada ~= 100GB+/dia
Três caminhos dominam arquitetura:
GET /v1/restaurants?lat={lat}&lng={lng}&page=1
GET /v1/restaurants/{id}/menu
POST /v1/carts/{cart_id}/items
PATCH /v1/carts/{cart_id}/items/{item_id}
POST /v1/checkout/preview
POST /v1/checkout/submit
GET /v1/orders/{order_id}
GET /v1/orders/{order_id}/tracking
POST /v1/orders/{order_id}/cancel
PATCH /v1/restaurants/{id}/availability
PATCH /v1/orders/{order_id}/status
POST /v1/menus/{menu_id}/items
PATCH /v1/menus/{menu_id}/items/{item_id}
GET /v1/courier/tasks/next
POST /v1/courier/tasks/{task_id}/accept
POST /v1/courier/tasks/{task_id}/status
POST /v1/courier/location
{
"idempotency_key": "0d6e3d13-cbe3-42ec-bbd4-b7f5f2b57a0c",
"customer_id": "cust_321",
"cart_id": "cart_99",
"delivery_address_id": "addr_777",
"payment_method_id": "pm_abc",
"tip_cents": 500,
"coupon_code": "ALMOCO10",
"client_context": {
"device_id": "ios_55",
"ip": "198.51.100.20",
"app_version": "8.14.2"
}
}
CREATE TABLE idempotency_records (
idempotency_key VARCHAR(64) NOT NULL,
customer_id BIGINT NOT NULL,
request_hash CHAR(64) NOT NULL,
response_code INT NOT NULL,
response_body JSONB NOT NULL,
created_at TIMESTAMP NOT NULL,
expires_at TIMESTAMP NOT NULL,
PRIMARY KEY (idempotency_key, customer_id)
);
CREATE TABLE orders (
order_id BIGSERIAL PRIMARY KEY,
customer_id BIGINT NOT NULL,
restaurant_id BIGINT NOT NULL,
courier_id BIGINT,
status VARCHAR(32) NOT NULL,
subtotal_cents BIGINT NOT NULL,
delivery_fee_cents BIGINT NOT NULL,
service_fee_cents BIGINT NOT NULL,
tip_cents BIGINT NOT NULL,
discount_cents BIGINT NOT NULL,
total_cents BIGINT NOT NULL,
currency CHAR(3) NOT NULL,
placed_at TIMESTAMP NOT NULL,
updated_at TIMESTAMP NOT NULL,
idempotency_key VARCHAR(64) NOT NULL,
UNIQUE (customer_id, idempotency_key)
);
CREATE TABLE order_items (
order_item_id BIGSERIAL PRIMARY KEY,
order_id BIGINT NOT NULL REFERENCES orders(order_id),
menu_item_id BIGINT NOT NULL,
quantity INT NOT NULL,
unit_price_cents BIGINT NOT NULL,
customization JSONB,
item_total_cents BIGINT NOT NULL
);
CREATE TABLE order_events (
event_id BIGSERIAL PRIMARY KEY,
order_id BIGINT NOT NULL,
event_type VARCHAR(64) NOT NULL,
event_payload JSONB NOT NULL,
created_at TIMESTAMP NOT NULL
);
OrderPlaced.Evite 2PC global. Use:
Balancear:
score =
w1 * pickup_eta
+ w2 * dropoff_eta
+ w3 * acceptance_penalty
+ w4 * utilization_penalty
+ w5 * zone_balance_penalty
Se entregador falhar:
{
"courier_id": "cour_887",
"task_id": "task_552",
"lat": -23.5601,
"lng": -46.6552,
"speed_mps": 6.2,
"heading_deg": 110,
"timestamp": "2026-02-22T12:41:10Z"
}
ETA_total = prep_time + pickup_buffer + transit_time + handoff_buffer
{
"order_id": "ord_9011",
"pickup": { "lat": -23.551, "lng": -46.633 },
"dropoff": { "lat": -23.560, "lng": -46.655 },
"restaurant_id": "r_91",
"courier_id": "cour_887",
"current_state": "COURIER_AT_RESTAURANT"
}
Resposta:
{
"eta_minutes": 18,
"confidence": 0.86,
"window": {
"from_minutes": 14,
"to_minutes": 24
},
"updated_at": "2026-02-22T12:42:20Z"
}
Janela de confiança realista é melhor que ETA pontual super otimista.
delivery_fee = base
+ multiplier(demand_supply)
+ distance_component
+ weather_component
- subsidy_component
CREATE TABLE payment_attempts (
payment_attempt_id BIGSERIAL PRIMARY KEY,
order_id BIGINT NOT NULL,
provider VARCHAR(32) NOT NULL,
provider_txn_id VARCHAR(128),
status VARCHAR(32) NOT NULL,
amount_cents BIGINT NOT NULL,
currency CHAR(3) NOT NULL,
failure_code VARCHAR(64),
created_at TIMESTAMP NOT NULL,
updated_at TIMESTAMP NOT NULL,
UNIQUE (provider, provider_txn_id)
);
type PaymentWebhook = {
provider: string;
eventId: string;
orderId: string;
status: "AUTHORIZED" | "CAPTURED" | "FAILED" | "REFUNDED";
occurredAt: string;
};
async function handlePaymentWebhook(evt: PaymentWebhook) {
const dedupeKey = `${evt.provider}:${evt.eventId}`;
if (await dedupeStore.exists(dedupeKey)) return;
await db.transaction(async (tx) => {
await tx.paymentEvents.insert(evt);
await tx.orders.applyPaymentTransition(evt.orderId, evt.status);
await tx.dedupe.insert({ key: dedupeKey, ttlHours: 72 });
});
}
Distribuir corretamente:
| Cenário | Cliente | Restaurante | Entregador |
|---|---|---|---|
| restaurante recusou | reembolso total | sem compensação | sem compensação |
| atraso extremo sem alocação | total ou cupom | parcial por política | sem compensação |
| cancelamento tardio cliente | parcial | parcial/total | possível taxa |
| item errado | total ou redelivery | fluxo de contestação | depende de evidência |
Policy engine versionado e auditável para replay de disputas.
{
"restaurant_id": "r_91",
"is_open": true,
"accepting_orders": true,
"prep_time_minutes": {
"base": 18,
"current_dynamic": 27
},
"capacity_state": "HIGH_LOAD",
"updated_at": "2026-02-22T12:39:00Z"
}
(order,event,channel),restaurants:tile:{geo_hash}:{filters_hash}
menu:{restaurant_id}:v{menu_version}
cart:{customer_id}
eta_preview:{restaurant_id}:{zone_id}
restaurant_status:{restaurant_id}
| Domínio | Chave |
|---|---|
| orders | hash(order_id) |
| dispatch | zone_id + time bucket |
| tracking | courier_id + day bucket |
| restaurant | hash(restaurant_id) |
OrderPlacedOrderAcceptedOrderRejectedCourierAssignedOrderPickedUpOrderDeliveredPaymentAuthorizedPaymentCapturedRefundIssuedRestaurantCapacityChangedCourierLocationUpdatedCREATE TABLE outbox_events (
event_id UUID PRIMARY KEY,
aggregate_type VARCHAR(32) NOT NULL,
aggregate_id VARCHAR(64) NOT NULL,
event_type VARCHAR(64) NOT NULL,
payload JSONB NOT NULL,
created_at TIMESTAMP NOT NULL,
published_at TIMESTAMP
);
| Risco | Mitigação |
|---|---|
| cupom abuse | device graph + velocity + limites |
| card fraud | risk score + challenge + sinais do gateway |
| location spoof | anomaly detection + attestation |
| refund abuse | policy + score de reincidência |
| takeover | MFA adaptativo + detecção de login suspeito |
| Servico | SLI | SLO |
|---|---|---|
| Discovery API | p99 latência | < 250ms |
| Checkout submit | sucesso | > 99,9% |
| Order create | perda de ack | 0 |
| Dispatch assignment | p99 tempo | < 2s |
| Tracking freshness | p95 atraso | < 3s |
| ETA accuracy | erro absoluto mediano | dentro da meta por cidade |
Checkout/Payment:
Dispatch:
Tracking:
x-request-id
x-order-id
x-dispatch-task-id
x-courier-id
x-payment-attempt-id
| Decisão | Opção A | Opção B | Recomendação |
|---|---|---|---|
| dispatch | nearest-only | score optimization | score default |
| tracking ingest | processamento pesado sync | ingest leve + stream | ingest leve |
| ETA | ponto único | janela com confiança | janela |
| consistência | eventual total | forte no crítico | mista |
| regiões | única | multi-região por zona | multi-região |
Problema: valor final divergente.
Correção: recomputar no submit.
Problema: latência explode em baixa oferta.
Correção: criar pedido e disparar dispatch assínc.
Problema: falha de telemetria quebra fluxo principal.
Correção: isolar tracking como subsistema.
Problema: estados financeiros/operacionais inconsistentes.
Correção: compensações determinísticas por falha.
Problema: aceites além da capacidade real.
Correção: sinais de capacidade no ranking e checkout.
Problema: unidade econômica degrada rapidamente.
Correção: score de risco desde o início.
Problema: um bairro degrada a plataforma inteira.
Correção: particionamento e autoscaling por zona.
Projetar iFood em escala é orquestrar sistemas independentes com latência baixa e consistência financeira.
Arquiteturas que funcionam compartilham cinco pilares:
Esse conjunto é o que diferencia um desenho de entrevista superficial de um desenho que sobreviveria em produção.
| Componente | Responsabilidade |
|---|---|
| Discovery | busca/ranking de restaurantes |
| Cart | estado mutável do pedido |
| Checkout | validação final + submit |
| Order | aggregate durável de pedido |
| Payment | auth/capture/refund + webhook |
| Dispatch | matching e alocação |
| Tracking | ingest de localização e fanout |
| ETA | previsão dinâmica de entrega |
| Notification | comunicação de estados |
CART -> CHECKOUT_SUBMITTED -> PLACED -> ACCEPTED -> PREPARING -> READY -> PICKED_UP -> DELIVERED
\-> CANCELLED / FAILED / REFUNDED
Você agora tem um blueprint completo para system design de delivery estilo iFood em escala nacional.