Serverless na AWS-u: arhitektura koja skalira na nulu

Pre par godina sam preuzeo integraciju između interne aplikacije i Zendesk-a koja je radila na EC2 instanci od 70 dolara mesečno. Posao tog servera je bio da svakih nekoliko minuta proveri red zadataka i, ako ima nešto, pošalje webhook. 95% vremena server je bio dosadno prazan. Prebacio sam celu stvar na Lambda + EventBridge + API Gateway, račun je pao na manje od 2 dolara mesečno, i prvi put smo ispoštovali SLA. Ovo je priča o tome kako tu arhitekturu postaviš za mali tim, šta zaista košta, i gde su zamke koje niko ne piše u marketinškim postovima.
Kada serverless ima smisla za mali tim
Serverless se isplati kada je tvoj saobraćaj neravnomeran, kada nemaš DevOps inženjera, i kada ne želiš da plaćaš servere koji 90% vremena ništa ne rade. Konkretno: integracije, webhook obrada, scheduled poslovi, interni alati, low-traffic API-ji, i event-driven pipeline-ovi za podatke. Tu Lambda + EventBridge + API Gateway zaista skaliraju na nulu, naplaćuje ti se po pozivu, i ne moraš da brineš o OS patch-evima.
Gde NE bih išao serverless: latency-critical sistemi gde 200ms cold start ubija UX, dugotrajni workload-ovi preko 15 minuta (Lambda hard limit), WebSocket-i sa hiljadama persistent konekcija (mogu, ali ECS Fargate je realniji), i workload-ovi gde znaš da ti CPU radi 24/7 punim gasom. Tu ti je rezervisana EC2 ili Fargate jeftiniji.
Praktičan filter koji koristim: ako mi je prosečno opterećenje ispod 30% kapaciteta jednog malog servera, idem serverless. Ako je preko 60%, računam oba scenarija i biram po ceni i operativnoj kompleksnosti.
Referentna arhitektura iz stvarne integracije
Evo arhitekture koju koristim za većinu integracija. Apstrahovaću je sa Zendesk primera, ali šablon je isti za Shopify, Stripe, HubSpot, interni ERP, šta god.
[ Eksterni sistem ]
|
| webhook (HTTPS)
v
[ API Gateway HTTP API ]
|
| (auth + validation)
v
[ Lambda: ingest ]
|
| put_events
v
[ EventBridge custom bus ]
|
+--> rule: order.created --> [ Lambda: sync_to_crm ]
|
+--> rule: order.failed --> [ Lambda: alert ] --> SNS
|
+--> rule: *.* --> [ Firehose ] --> S3 (audit log)
[ EventBridge Scheduler ] --(cron)--> [ Lambda: reconcile ] --> DynamoDB
Tri sloja koja vredi razdvojiti:
-
Ingest sloj. Jedna mala Lambda iza API Gateway-a. Njen jedini posao je da validira potpis webhook-a, normalizuje payload i baci event na EventBridge. Bez biznis logike. Bez retry-ja. Vraća 200 OK čim event uđe u bus. Ovo je ključno jer eksterni sistemi imaju svoje retry policy-je koje ne kontrolišeš, pa moraš da ih pustiš sa kuke što pre.
-
Processing sloj. EventBridge pravila rutiraju eventove ka specijalizovanim Lambdama. Svaka Lambda radi jednu stvar (CRM sync, alert, transformacija). Ako neka padne, druge nastavljaju da rade. Ovo je suštinska razlika u odnosu na monolit koji u jednom request handler-u radi sve i sve pada zajedno.
-
Scheduled sloj. EventBridge Scheduler (ne stari CloudWatch Events) za reconciliation i cleanup. Jednom dnevno proverim da li je broj eventova koje sam primio jednak broju koji sam obradio. Ako ne, alarm.
Cene koje stvarno plaćaš
Ovo je deo koji niko ne piše iskreno. Cene koje navodim su iz eu-central-1, indikativne i menjaju se, ali daju red veličine.
| Servis | Cena | Realan trošak na 1M događaja/mesec |
|---|---|---|
| API Gateway HTTP API | ~1 USD / 1M zahteva | ~1 USD |
| Lambda (128 MB, 200ms) | ~0.20 USD / 1M poziva + compute | ~2-4 USD |
| EventBridge custom bus | 1 USD / 1M događaja | ~1 USD |
| CloudWatch Logs | ~0.50 USD / GB ingest | ~3-15 USD |
| DynamoDB on-demand | ~1.25 USD / 1M write | varira |
Ukupno za 1M događaja mesečno: realno između 10 i 30 USD, zavisno od Lambda memorije, trajanja, i koliko logova piše. Poređenja radi, t3.medium 24/7 je oko 30 USD samo za instancu, plus EBS, plus load balancer, plus moje vreme za održavanje.
Skrivene stavke koje te ujedu:
- CloudWatch Logs. Ako svaka Lambda loguje ceo payload, račun za logove preraste račun za sve ostalo. Postavi log retention na 14 ili 30 dana, ne na "Never expire". Loguj samo ono što je dijagnostički korisno.
- NAT Gateway. Ako ti Lambda mora u VPC da bi pričala sa RDS-om, NAT Gateway je oko 35 USD mesečno fiksno plus po GB. To pobije celu uštedu serverless-a za male sisteme. Rešenje: VPC endpoint-ovi gde je moguće, ili izbegavanje VPC-a ako Lambda ne mora u njega.
- Data transfer. Cross-region pozivi se naplaćuju. Drži sve u jednoj regiji ako možeš.
Cold start: koliko stvarno boli i šta uraditi
Cold start je realan, ali za 80% sistema nije problem. Node.js Lambda od 128 MB sa malim bundle-om ima cold start od 200-400ms. Python slično. Java i .NET znaju da odu preko 2 sekunde, što je već vidljivo korisniku.
Šta radim po prioritetu:
- Tretiraj cold start kao non-issue za async putanju. Webhook ingest, queue consumer, scheduled poslovi, niko ne čeka. 400ms ne menja ništa.
- Smanji bundle. Koristi
esbuildza TypeScript, ne nosi ceoaws-sdk(Node 18+ već ima v3). Bundle od 200KB se učita brzo, bundle od 20MB ne. - Podigni memoriju. Lambda sa 1024 MB nije samo brža u izvršavanju, ima i brži cold start jer dobija proporcionalno više CPU-a. 512 MB je često sweet spot.
- Provisioned concurrency, ali tek kad dokažeš da treba. Plaćaš za pripremljene instance, što ubija "skalira na nulu". Koristi samo za sinhrone, korisnički-vidljive endpoint-ove sa stabilnim baseline saobraćajem.
- SnapStart za Javu ako moraš Java. Smanji cold start sa 6s na 200ms. Za novije projekte radije izbegavam Javu na Lambdi.
Idempotentnost i retry, glavni izvor noćnih buđenja
Lambda + EventBridge ima at-least-once delivery. To znači da će ti se isti event obraditi dva puta pre ili kasnije. Ako tvoja Lambda nije idempotentna, kreiraćeš duple ordere, poslati dva email-a, naplatiti dvaput.
Šablon koji koristim:
// idempotency check u DynamoDB
const eventId = event.detail.idempotency_key;
try {
await ddb.put({
TableName: 'processed_events',
Item: { event_id: eventId, ttl: now + 7*24*3600 },
ConditionExpression: 'attribute_not_exists(event_id)'
});
} catch (e) {
if (e.name === 'ConditionalCheckFailedException') {
console.log('Duplicate, skipping', eventId);
return { statusCode: 200 };
}
throw e;
}
// pravi posao tek sada
await processOrder(event.detail);
Tabela processed_events ima TTL na 7 dana, pa se sama čisti. Cena: jedan dodatni DynamoDB write po eventu, par mikrocenata.
Drugi deo iste priče: Dead Letter Queue. Svaka Lambda koja konzumira event mora imati DLQ (SQS) za poruke koje su pale posle max retry-ja. Postavi CloudWatch alarm na dubinu DLQ-a > 0. Inače ne znaš da ti je nešto tiho otpalo.
Deployment i lokalni razvoj bez histerije
Ne dirajte Lambda-e ručno u konzoli. Pravilo nula.
Za infrastrukturu koristim AWS SAM za jednostavnije servise i AWS CDK (TypeScript) za sve preko 5 Lambdi. Terraform takođe radi, ali je verboziji za serverless.
Minimalna struktura SAM template-a koja pokriva referentnu arhitekturu odozgo:
Resources:
IngestFunction:
Type: AWS::Serverless::Function
Properties:
Runtime: nodejs20.x
MemorySize: 512
Timeout: 10
Events:
WebhookApi:
Type: HttpApi
Properties:
Path: /webhook
Method: POST
Policies:
- EventBridgePutEventsPolicy:
EventBusName: !Ref AppBus
AppBus:
Type: AWS::Events::EventBus
Properties:
Name: app-events
SyncToCrmFunction:
Type: AWS::Serverless::Function
Properties:
Runtime: nodejs20.x
Events:
OrderCreated:
Type: EventBridgeRule
Properties:
EventBusName: !Ref AppBus
Pattern:
detail-type: ["order.created"]
DeadLetterQueue:
Type: SQS
TargetArn: !GetAtt SyncDlq.Arn
Za lokalni razvoj: sam local invoke radi za jednostavne stvari, ali za realnu integraciju je brže imati dev AWS nalog sa odvojenim stack-om po programeru. sam sync --watch u dev nalogu daje feedback loop od 5-10 sekundi po izmeni, što je sasvim podnošljivo. Pokušaji da se ceo AWS emulira lokalno (LocalStack i slično) za mene su uvek završili u troši-više-vremena-nego-uštedi.
Observability koja stvarno radi
CloudWatch Logs Insights je dovoljan za mali tim, ne treba ti Datadog odmah. Ali moraš da pišeš strukturisane logove.
console.log(JSON.stringify({
level: 'info',
event_id: eventId,
customer_id: customerId,
duration_ms: Date.now() - start,
outcome: 'synced'
}));
Onda u Logs Insights:
fields @timestamp, customer_id, duration_ms, outcome
| filter outcome = "failed"
| stats count() by bin(5m)
Tri metrike koje uvek postavim sa alarmima:
- Lambda Errors > 0 preko 5 minuta, po funkciji.
- DLQ ApproximateNumberOfMessagesVisible > 0, po queue-u.
- EventBridge FailedInvocations > 0, po pravilu.
AWS X-Ray je koristan ali povećava troškove i šum. Uključim ga kad debug-ujem konkretan problem, isključim kad rešim.
Šta bih ja uradio danas, krećući od nule
Ako bih sada postavljao integraciju za firmu od 10 ljudi, ovako bih išao po redu:
- Tri AWS naloga (dev, staging, prod) povezana preko AWS Organizations. Ne mešaj okruženja, košta isto, a štedi živce.
- CDK u TypeScript-u od prvog dana, čak i za jednu Lambda-u. Skaliraće sa projektom bez velikog refaktora.
- HTTP API, ne REST API u API Gateway-u. Jeftiniji, brži, dovoljan za 99% slučajeva.
- EventBridge custom bus od početka, čak i ako imaš samo jednu Lambda-u. Dodavanje sledećeg consumer-a kasnije je trivijalno; refaktorisanje direktnih Lambda invokacija nije.
- DynamoDB on-demand za stanje, ne Aurora Serverless v2 (skuplji za male workload-ove i ima minimalni capacity koji nije nula).
- Power Tools for AWS Lambda (Python ili TypeScript). Daje strukturisano logovanje, tracing, idempotency utility, sve out of the box.
- Budget alarm na 50 USD mesečno za dev nalog, 200 USD za prod, sa email obaveštenjem. Bolje da te SMS probudi zbog runaway Lambde nego račun na kraju meseca.
Šta NE bih radio:
- Ne bih išao multi-region za sistem koji ima 20 korisnika. Active-active je skup i komplikovan, single-region sa dobrim backup-om je dovoljan dok ne dođeš do realnog zahteva.
- Ne bih stavljao Lambdu u VPC ako ne mora. NAT Gateway pojede budžet.
- Ne bih koristio Step Functions za sve. Genijalna stvar za workflow-e sa više koraka i ljudskim approval-om, overkill za jednostavan ingest.
Zatvaranje
Serverless na AWS-u za mali tim nije magija, ali jeste arhitektura koja te oslobađa od posla koji ne donosi vrednost. Ne plaćaš servere koji spavaju, ne radiš OS patch-eve, ne brineš o auto-scaling grupama. U zamenu, moraš da razmišljaš drugačije o idempotentnosti, retry logici i observability-ju, i moraš da poznaješ cene do nivoa CloudWatch logova.
Ako razmišljaš o sličnoj integraciji ili imaš postojeći sistem koji košta više nego što bi trebalo, slobodno me kontaktiraj preko lazar-milicevic.com/#contact. Najradije pričam o konkretnim arhitekturama, ne o opštim slajdovima.
Često postavljana pitanja
Kada serverless arhitektura ima smisla za mali tim?
Serverless se isplati kada je saobraćaj neravnomeran, kada nemate dedicated DevOps inženjera i kada ne želite da plaćate servere koji 90% vremena ništa ne rade. Konkretno odlično radi za integracije, webhook obradu, scheduled poslove, interne alate, low-traffic API-je i event-driven pipeline-ove. Praktičan filter koji koristim je: ako mi je prosečno opterećenje ispod 30% kapaciteta jednog malog servera, idem serverless. Ne preporučujem ga za latency-critical sisteme, workload-ove duže od 15 minuta (Lambda hard limit), WebSocket-e sa hiljadama persistent konekcija i poslove gde CPU radi 24/7 punim gasom.
Koliko stvarno košta AWS Lambda + EventBridge + API Gateway arhitektura?
Za 1 milion događaja mesečno realan trošak je između 10 i 30 USD, zavisno od Lambda memorije, trajanja izvršavanja i količine logova. API Gateway HTTP API košta oko 1 USD na milion zahteva, Lambda 2-4 USD, EventBridge custom bus 1 USD, a CloudWatch Logs između 3 i 15 USD. Poređenja radi, jedna t3.medium EC2 instanca koja radi 24/7 košta oko 30 USD samo za instancu, plus EBS, load balancer i vreme za održavanje. U mom slučaju, integracija koja je ranije koštala 70 USD mesečno na EC2 pala je na manje od 2 USD posle prelaska na serverless.
Koje su skrivene stavke u AWS serverless računu koje ljudi ne očekuju?
Tri stavke najčešće obore budžet. Prva je CloudWatch Logs - ako svaka Lambda loguje ceo payload, račun za logove preraste sve ostalo, pa je obavezno postaviti retention na 14 ili 30 dana umesto 'Never expire'. Druga je NAT Gateway - ako Lambda mora u VPC da bi pričala sa RDS-om, NAT Gateway košta oko 35 USD mesečno fiksno plus po GB, što pobije celu uštedu kod malih sistema. Treća stavka je data transfer između regija, pa je važno držati sve resurse u istoj AWS regiji.
Koliko je cold start na AWS Lambda zaista problem i kako ga smanjiti?
Cold start je realan, ali za oko 80% sistema nije problem. Node.js i Python Lambde od 128 MB imaju cold start od 200-400ms, dok Java i .NET mogu da pređu 2 sekunde. Za async putanje poput webhook ingestiona, queue consumer-a i scheduled poslova cold start je nebitan jer niko ne čeka. Praktične optimizacije su: smanjiti bundle koristeći esbuild, podići memoriju na 512 ili 1024 MB (dobija se i više CPU-a), koristiti SnapStart za Javu, a Provisioned Concurrency uključiti tek kad se dokaže da je neophodno za sinhrone korisničke endpoint-e.
Zašto je idempotentnost ključna kod Lambda i EventBridge arhitekture?
Lambda i EventBridge koriste at-least-once delivery, što znači da će se isti event pre ili kasnije obraditi dva puta. Ako Lambda funkcija nije idempotentna, posledice su duple narudžbine, dva ista email-a poslata korisniku ili dupla naplata kartice. Zato svaki event mora imati jedinstveni identifikator koji se proverava pre obrade, najčešće kroz DynamoDB tabelu sa TTL-om. Idempotentnost se mora ugraditi u dizajn od početka jer je naknadno popravljanje skupo, a problemi su upravo glavni uzrok noćnih buđenja kod produkcijskih serverless sistema.
Gradiš nešto teško sa AI-jem ili automatizacijom? Otvoren sam za razgovor.
Javi se