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

Recursos selecionados para complementar sua leitura
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 pontos para encontrar bugs, riscos de segurança e problemas de performance antes do lançamento.
Continue explorando tópicos similares
Templates testados em produção, usados por desenvolvedores. Economize semanas de setup no seu próximo projeto.
Consultorias modulares para founders e CTOs fracionados. Você recebe diagnóstico acionável e acompanhamento direto comigo.
2 vagas para consultorias no 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.