Serverless ֆունկցիաներ
Կոդը գործարկեք ըստ պահանջի՝ առանց սերվերներ կառավարելու. Lambda, edge ֆունկցիաներ, trigger-ներ և cold start-եր։
30 վայրկյանում
Serverless ֆունկցիաները թույլ են տալիս վերբեռնել կոդի մի կտոր, և cloud provider-ը այն կգործարկի ամեն անգամ, երբ իրադարձություն տեղի ունենա՝ HTTP հարցում, ֆայլի վերբեռնում, հերթի հաղորդագրություն կամ ժամանակացույց։ Դուք չեք կառավարում սերվերներ, օպերացիոն համակարգեր կամ մասշտաբավորում. պլատֆորմը գործարկում է այնքան օրինակ, որքան պետք է, և վճարում եք ըստ կանչերի և աշխատանքի ժամանակի։ Փոխզիջումներն են cold start-երը, ժամանակի և հիշողության սահմանափակումները և մեկ provider-ի հետ ավելի ամուր կապվածությունը։
Իմ ամենօրյա stack-ում չէ․ գիտեմ հիմնական գաղափարները և մանրամասները կսովորեմ, երբ նախագիծը դա պահանջի։
Ինչու է սա կարևոր
- Թիմերը serverless են օգտագործում փոքր API-ներ, webhook-ներ և ֆոնային job-եր թողարկելու համար՝ առանց սերվերներ գործարկելու կամ patch անելու։
- Այն ավտոմատ մասշտաբավորվում է զրոյից մինչև հազարավոր զուգահեռ կատարումներ, ինչը հարմար է թռիչքաձև կամ անկանխատեսելի տրաֆիկի համար։
- Ըստ օգտագործման վճարումը ցածր տրաֆիկով կամ պոռթկումներով workload-ները դարձնում է շատ էժան՝ միշտ միացված մեքենաների համեմատ։
- Ժամանակակից ֆրոնտենդ պլատֆորմները (Vercel, Netlify, Cloudflare) ֆրեյմվորքի սերվերային կոդը, օրինակ՝ Next.js API route-երը, գործարկում են որպես ֆունկցիաներ, ուստի վեբ ծրագրավորողների մեծ մասն արդեն կախված է serverless-ից, նույնիսկ եթե այդպես չի անվանում։
Ինչպես է աշխատում
01Գաղափարը՝ ֆունկցիաներ սերվերների փոխարեն
Դասական սերվերի վրա ձեր հավելվածն աշխատում է անընդհատ, և վճարում եք մեքենայի համար՝ անկախ նրանից, զբաղված է այն, թե ոչ։ Serverless-ը (ավելի ճշգրիտ՝ Functions as a Service, FaaS) շրջում է դա. դուք deploy եք անում ֆունկցիա, պլատֆորմը այն պահում է քնած վիճակում, և երբ իրադարձություն է գալիս, գործարկում է կատարման միջավայր, աշխատեցնում ձեր handler-ը և հետո սառեցնում կամ ոչնչացնում այն։ Պատկերացրեք տաքսի՝ սեփական մեքենայի փոխարեն. ոչ կայանատեղի, ոչ սպասարկում, վճարում եք ըստ ուղևորության, բայց ամեն ուղևորության համար մի փոքր սպասում եք, և հետևում եք տաքսի ընկերության կանոններին։ «Serverless»-ը չի նշանակում, որ սերվերներ չկան. նշանակում է, որ սերվերները provider-ի խնդիրն են։ Հիմնական պլատֆորմներն են AWS Lambda-ն, Google Cloud Run functions-ը, Azure Functions-ը և edge պլատֆորմներ, ինչպիսիք են Cloudflare Workers-ը, Vercel Functions-ը և Netlify Functions-ը։
02Ինչպես է կանչն աշխատում ներքուստ
Երբ իրադարձություն է գալիս, և ազատ տաք միջավայր չկա, պլատֆորմը կատարում է cold start. ստեղծում է micro-VM (Lambda-ն օգտագործում է Firecracker) կամ V8 isolate (Cloudflare Workers), ներբեռնում ձեր կոդը, գործարկում runtime-ը և handler-ից դուրս գտնվող ինիցիալիզացիայի կոդը։ Հետո կանչում է handler-ը իրադարձությամբ։ Պատասխանից հետո միջավայրը որոշ ժամանակ տաք է մնում և կարող է սպասարկել հաջորդ հարցումը՝ առանց init-ի ծախսի։ Դասական Lambda-ում յուրաքանչյուր միջավայր միաժամանակ մշակում է մեկ հարցում, ուստի 100 զուգահեռ հարցումը նշանակում է 100 միջավայր։ Մոդուլի մակարդակի կոդը կատարվում է մեկ անգամ յուրաքանչյուր միջավայրի համար, և հենց այնտեղ եք ստեղծում բազայի client-ները և բեռնում կոնֆիգուրացիան։ Հիշողությունում կամ /tmp-ում գտնվող ամեն ինչ կարող է պահպանվել կանչերի միջև, բայց դա երբեք երաշխավորված չէ։
- Cold start. init + առաջին կանչ, հաճախ 100 ms-ից մինչև 1 վայրկյանից ավելի՝ կախված runtime-ից և փաթեթի չափից։
- Warm start. միայն handler-ը, սովորաբար մի քանի միլիվայրկյան overhead։
- Concurrency. միևնույն պահին աշխատող միջավայրերի քանակը։
03AWS Lambda ֆունկցիայի գրում
Lambda ֆունկցիան handler է, որը ստանում է event և context։ HTTP-ի համար այն դնում եք Function URL-ի կամ API Gateway-ի հետևում, որը HTTP հարցումը վերածում է event օբյեկտի, իսկ ձեր վերադարձրած արժեքը՝ պատասխանի։ Ներկայիս managed runtime-երից են Node.js 24-ը (nodejs24.x) և Python 3.14-ը Amazon Linux 2023-ի վրա, x86_64 կամ arm64 (Graviton, սովորաբար ավելի էժան) ճարտարապետությամբ։ Հիշողությունը կարգավորվում է 128 MB-ից մինչև 10,240 MB, և CPU-ն մասշտաբավորվում է դրա հետ. առավելագույն timeout-ը 15 րոպե է. սինքրոն հարցման և պատասխանի payload-ները սահմանափակված են 6 MB-ով։ SDK client-ները ինիցիալիզացրեք handler-ից դուրս, որպեսզի տաք կանչերը նորից օգտագործեն դրանք։
// index.mjs (runtime: nodejs24.x, handler: index.handler)
import { DynamoDBClient, GetItemCommand } from "@aws-sdk/client-dynamodb";
const db = new DynamoDBClient({}); // created once per environment
export const handler = async (event) => {
const id = event.pathParameters?.id;
if (!id) return { statusCode: 400, body: JSON.stringify({ error: "id required" }) };
const res = await db.send(new GetItemCommand({
TableName: process.env.TABLE_NAME,
Key: { pk: { S: `product#${id}` } },
}));
if (!res.Item) return { statusCode: 404, body: "{}" };
return { statusCode: 200, headers: { "content-type": "application/json" },
body: JSON.stringify({ id, name: res.Item.name.S }) };
};
04Event trigger-ներ. ավելին, քան HTTP
Serverless-ի իրական ուժը cloud-ի ամբողջ տարածքում իրադարձություններին արձագանքելն է։ AWS-ում ֆունկցիան կարող է գործարկվել, երբ օբյեկտ է հայտնվում S3-ում, հաղորդագրություն է գալիս SQS-ում, տող է փոխվում DynamoDB Streams-ում, EventBridge-ի կանոն կամ ժամանակացույց է աշխատում, կամ Kinesis stream-ը գրառումներ է ստանում։ Կանչերը լինում են սինքրոն (կանչողը սպասում է, ինչպես HTTP-ի դեպքում), ասինխրոն (պլատֆորմը հերթագրում է իրադարձությունը և սխալի դեպքում կրկնում՝ dead-letter նպատակակետով), կամ event source mapping-ների միջոցով, որոնք poll են անում հերթը կամ stream-ը և փոխանցում batch-եր։ Քանի որ ասինխրոն և հերթային trigger-ները կրկնում են, նույն իրադարձությունը կարող է մշակվել մեկից ավելի անգամ. handler-ները պետք է լինեն idempotent։ SQS batch-երի դեպքում հայտնեք մասնակի ձախողումները, որպեսզի կրկնվեն միայն ձախողված հաղորդագրությունները։
// SQS trigger with partial batch failures
// (enable ReportBatchItemFailures on the event source mapping)
export const handler = async (event) => {
const batchItemFailures = [];
for (const record of event.Records) {
try {
const job = JSON.parse(record.body);
await processOnce(job.id, job); // idempotent: skips already-done ids
} catch (err) {
console.error({ messageId: record.messageId, err: String(err) });
batchItemFailures.push({ itemIdentifier: record.messageId });
}
}
return { batchItemFailures };
};
05Edge ֆունկցիաներ. կոդը օգտատիրոջ մոտ
Edge ֆունկցիաներն աշխատում են աշխարհի բազմաթիվ վայրերում՝ օգտատիրոջ մոտ, մեկ տարածաշրջանի փոխարեն։ Cloudflare Workers-ը կոնտեյներների փոխարեն օգտագործում է V8 isolate-ներ. isolate-ը գործարկվում է միլիվայրկյաններում, ուստի cold start-երը գրեթե անտեսանելի են, բայց միջավայրը վեբ ստանդարտների runtime է (fetch, Request, Response, Web Crypto)՝ սահմանափակ Node.js API-ներով և ավելի խիստ CPU սահմաններով։ Դրանք իդեալական են redirect-ների, auth ստուգումների, A/B թեստավորման, header-ների վերագրման, գեոլոկացիայի և edge storage-ով (KV, D1, R2, Durable Objects) աշխատող թեթև API-ների համար։ Բազայի հետ ծանր աշխատանքի դեպքում հեռավոր բազան չեզոքացնում է օգուտը. Սիդնեյում գտնվող edge ֆունկցիան, որը կանչում է Ֆրանկֆուրտում գտնվող բազա, ավելի դանդաղ է, քան բազայի կողքին տեղադրված տարածաշրջանային ֆունկցիան։ Vercel-ի Functions-ն այժմ լռելյայն օգտագործում են Node.js runtime «Fluid compute»-ով, որը թույլ է տալիս մեկ instance-ին սպասարկել մի քանի զուգահեռ հարցում։
// Cloudflare Worker: geo-based redirect at the edge
export default {
async fetch(request: Request): Promise<Response> {
const url = new URL(request.url);
const country = (request as any).cf?.country ?? "US";
if (url.pathname === "/" && country === "DE") {
return Response.redirect(new URL("/de", url).toString(), 302);
}
return fetch(request); // pass through to origin
},
};
06Deploy՝ infrastructure as code-ով
Console-ում ֆունկցիաներ կտտացնելով հավաքելը չի մասշտաբավորվում և չի դիմանում թիմի փոփոխությանը։ Ֆունկցիաները, trigger-ները, թույլտվությունները և միջավայրը սահմանեք կոդով՝ AWS SAM, Serverless Framework, AWS CDK, SST կամ Terraform։ Յուրաքանչյուր ֆունկցիա պետք է ունենա իր IAM role-ը՝ միայն անհրաժեշտ թույլտվություններով։ Գաղտնիքները գալիս են Secrets Manager-ից կամ Parameter Store-ից, ոչ թե ռեպոզիտորիայում պահված սովորական միջավայրի փոփոխականներից։ Կախվածությունները փաթեթավորեք զգուշորեն. փոքր bundle-ներն ավելի արագ են գործարկվում, ուստի bundle արեք esbuild-ով և բացառեք AWS SDK-ն, եթե runtime-ն արդեն տրամադրում է այն (Node.js runtime-երը ներառում են SDK v3)։
# template.yaml (AWS SAM)
AWSTemplateFormatVersion: "2010-09-09"
Transform: AWS::Serverless-2016-10-31
Resources:
GetProduct:
Type: AWS::Serverless::Function
Properties:
Runtime: nodejs24.x
Architectures: [arm64]
Handler: index.handler
MemorySize: 512
Timeout: 10
Environment: { Variables: { TABLE_NAME: !Ref Products } }
Policies: [ { DynamoDBReadPolicy: { TableName: !Ref Products } } ]
Events:
Api: { Type: HttpApi, Properties: { Path: /products/{id}, Method: get } }
Products:
Type: AWS::Serverless::SimpleTable
07Ծախսեր, սահմանափակումներ և երբ այն այլևս էժան չէ
Lambda-ն հաշվարկում է ըստ հարցման գումարած GB-վայրկյաններ (հիշողություն՝ բազմապատկած տևողությամբ, 1 ms քայլով), ամսական անվճար շեմով։ Պոռթկումային կամ ցածր տրաֆիկի դեպքում սա շատ ավելի էժան է, քան միշտ միացված սերվերը։ Կայուն, բարձր տրաֆիկի դեպքում անընդհատ աշխատող ֆունկցիան կարող է ավելի թանկ արժենալ, քան նույն աշխատանքն անող կոնտեյները կամ VPS-ը, իսկ API Gateway-ը, NAT gateway-երը և լոգավորումը հաճախ ավելի թանկ են, քան ֆունկցիան ինքը։ Հետևեք account-ի concurrency սահմաններին (լռելյայն 1,000 մեկ տարածաշրջանում, կարելի է բարձրացնել), reserved concurrency-ին՝ downstream բազաները պաշտպանելու համար, և timeout-ներին։ Օգտակար կանոն. serverless-ը իդեալական է իրադարձությունների «սոսինձ» կոդի, webhook-ների, պլանավորված job-երի և թռիչքաձև API-ների համար. երկարատև, կապերով ծանր կամ կայուն workload-ներին ավելի լավ են համապատասխանում կոնտեյներները։
Օգտագործեք, երբ
- Webhook-ներ, պլանավորված job-եր, ֆայլերի մշակում և այլ իրադարձություններով աշխատող «սոսինձ» կոդ։
- Թռիչքաձև, անկանխատեսելի կամ ցածր տրաֆիկով API-ներ, որտեղ պարապ սերվերների համար վճարելը վատնում է։
- Edge տրամաբանություն, ինչպես redirect-ները, auth դարպասները, A/B թեստերը և անհատականացումը, որը պետք է արագ լինի ամբողջ աշխարհում։
- Փոքր թիմեր, որոնք ուզում են կառավարվող մասշտաբավորում և patch-ում և ընդունում են provider-ից որոշակի կախվածություն։
Մի՛ օգտագործեք, երբ
- 15 րոպեից երկար job-եր, WebSocket սերվերներ կամ պրոցեսներ, որոնց պետք է մշտական in-memory վիճակ։
- Կայուն, բարձր throughput-ով workload-ներ, որտեղ միշտ միացված կոնտեյները կամ VPS-ը ավելի էժան է և կանխատեսելի։
- Latency-ի համար կրիտիկական ուղիներ, որտեղ պարբերական cold start-երն անընդունելի են, իսկ provisioned capacity-ն չափազանց թանկ է։
Այլընտրանքներ
- VPS + Docker Compose
Կանխատեսելի ծախս և լիարժեք վերահսկողություն. patch-ն ու մասշտաբավորումը ձեզ վրա են, բայց երկարատև պրոցեսներն ու WebSocket-ները պարզ են։
- Serverless կոնտեյներներ (Cloud Run, Fargate, Fly.io)
Գործարկեք ցանկացած կոնտեյներ autoscaling-ով՝ ֆունկցիաներից ավելի քիչ runtime սահմանափակումներով։
- Kubernetes
Առավելագույն վերահսկողություն բազմաթիվ ծառայությունների համար. շատ ավելի շատ շահագործման աշխատանք։
- Հերթի worker-ներ (BullMQ, Celery)
Ֆոնային մշակում ձեր սեփական սերվերների վրա՝ concurrency-ի և կրկնակի փորձերի նուրբ վերահսկողությամբ։
Լավագույն պրակտիկաներ
- Client-ները և կոնֆիգուրացիան ինիցիալիզացրեք handler-ից դուրս և bundle-ները պահեք փոքր՝ cold start-երը կրճատելու համար։
- Յուրաքանչյուր handler դարձրեք idempotent. ասինխրոն և հերթային trigger-ները առաքում են առնվազն մեկ անգամ (at least once)։
- Յուրաքանչյուր ֆունկցիայի տվեք իր նվազագույն արտոնություններով IAM role-ը և գաղտնիքները պահեք secrets manager-ում։
- Սահմանեք բացահայտ timeout-ներ և հիշողություն. փորձարկեք հիշողության չափերը, որովհետև ավելի շատ հիշողությունը նշանակում է նաև ավելի շատ CPU և ընդհանուր առմամբ կարող է ավելի էժան լինել։
- Օգտագործեք կառուցվածքային JSON լոգեր, tracing (X-Ray կամ OpenTelemetry) և alarm-ներ սխալների, throttle-ների և տևողության վրա։
- Պաշտպանեք բազաները connection pooling-ով (RDS Proxy, serverless դրայվեր) և reserved concurrency-ով։
- Ամեն ինչ սահմանեք infrastructure as code-ով և deploy արեք CI-ով։
Տարածված սխալներ
- Յուրաքանչյուր կանչի համար բազայի նոր կապ բացելը, ինչը տրաֆիկի առաջին թռիչքի ժամանակ սպառում է բազան։
- Ֆունկցիաները VPC-ում NAT gateway-ով դնելը միայն հանրային API-ներ կանչելու համար, ինչն առանց պատճառի ծախս և latency է ավելացնում։
- Ենթադրելը, որ հիշողության կամ /tmp-ի վիճակը պահպանվում է կանչերի միջև, կամ որ իրադարձությունն առաքվում է ճիշտ մեկ անգամ։
- Թույլ տալը, որ ռեկուրսիվ trigger-ները ցիկլի մեջ ընկնեն (ֆունկցիան գրում է նույն S3 bucket-ում, որը գործարկում է իրեն)։
- Անտեսելը API Gateway-ի, լոգերի և տվյալների փոխանցման հաշիվը, որը կարող է գերազանցել ֆունկցիայի ծախսը։
Խորը վերլուծություն
08Կատարման միջավայրի կյանքի ցիկլի ներսում
Lambda-ի կատարման միջավայրն ունի երեք փուլ։ Init. պլատֆորմը գործարկում է extension-ները, runtime-ը և կատարում ձեր մոդուլի մակարդակի կոդը. այս փուլն ունի իր սեփական 10 վայրկյանի սահմանը։ Invoke. handler-ն աշխատում է յուրաքանչյուր իրադարձության համար՝ մեկ միջավայրում մեկը մյուսի հետևից։ Shutdown. երբ պլատֆորմը վերամշակում է միջավայրը, extension-ները ստանում են կարճ պատուհան՝ տվյալները flush անելու համար։ Կանչերի միջև միջավայրը սառեցված է, ուստի առանց await գործարկված ֆոնային աշխատանքը (await չարված promise, timer) կարող է կանգ առնել կիսատ և վերսկսվել հետագա կանչում, ինչը շփոթեցնող բագեր է առաջացնում։ Միշտ await արեք ամեն ինչ վերադարձնելուց առաջ, կամ օգտագործեք response streaming, երբ ուզում եք բայթերը շուտ ուղարկել։ SnapStart-ը, որը հասանելի է Java-ի, Python-ի և .NET-ի համար, snapshot է անում ինիցիալիզացված միջավայրը և վերսկսում դրանից, ուստի եզակի արժեքները, ինչպես random seed-երը և կապերը, պետք է թարմացվեն վերականգնումից հետո։
09Concurrency, մասշտաբավորում և throttling
Concurrency-ն մոտավորապես վայրկյանում հարցումների քանակն է՝ բազմապատկած միջին տևողությամբ. 200 req/s 250 ms-ով պահանջում է մոտ 50 զուգահեռ միջավայր։ Յուրաքանչյուր տարածաշրջան ունի account-ի մակարդակի concurrency քվոտա, որը կիսում են բոլոր ֆունկցիաները, ուստի մեկ անվերահսկելի ֆունկցիան կարող է throttle անել մնացածին։ Reserved concurrency-ն ֆունկցիայի համար սահմանում է և՛ երաշխավորված բաժին, և՛ առավելագույն սահման՝ պաշտպանելով փխրուն downstream համակարգերը։ Provisioned concurrency-ն որոշակի քանակով միջավայրեր պահում է ինիցիալիզացված՝ վերացնելով cold start-երը այդ թողունակության համար, ժամավճարով։ Throttle-ի դեպքում սինքրոն կանչողները ստանում են 429 սխալներ, իսկ ասինխրոն իրադարձությունները լռելյայն կրկնվում են մինչև վեց ժամ։ Հերթային trigger-ների համար event source mapping-ի maximum concurrency-ն սպառողներին սահմանափակելու ավելի անվտանգ եղանակ է, քան reserved concurrency-ն, որը կարող է հաղորդագրությունները հետ ուղարկել հերթ։
# Cap a function at 50 concurrent executions
aws lambda put-function-concurrency \
--function-name get-product --reserved-concurrent-executions 50
# Keep 10 environments warm on the "live" alias
aws lambda put-provisioned-concurrency-config \
--function-name get-product --qualifier live \
--provisioned-concurrent-executions 10
10Արտադրողականության կարգավորում. հիշողություն, ճարտարապետություն և bundling
Lambda-ում CPU-ն տրամադրվում է հիշողությանը համամասնորեն. 1,769 MB-ի դեպքում ֆունկցիան ստանում է մեկ լիարժեք vCPU-ի համարժեքը։ Ուստի հիշողությունը բարձրացնելը CPU-ով սահմանափակված ֆունկցիաները հաճախ դարձնում է և՛ ավելի արագ, և՛ ավելի էժան, որովհետև տևողությունը նվազում է ավելի շատ, քան աճում է գինը։ Չափեք AWS Lambda Power Tuning-ով, որը գործարկում է ձեր ֆունկցիան հիշողության մի քանի չափերով և գծապատկերում ծախսը արագության համեմատ։ arm64-ը (Graviton) ունի ավելի ցածր գին մեկ GB-վայրկյանի համար և շատ workload-ների համար ավելի արագ է։ Node.js-ի համար bundle արեք esbuild-ով, AWS SDK-ն նշեք որպես external, ինիցիալիզացիայի համար օգտագործեք ES մոդուլներ top-level await-ով և cold ուղիներում խուսափեք ամբողջական SDK-ներ կամ ORM-ներ import անելուց։ Python-ի համար կրճատեք կախվածությունները և մոդուլի մակարդակում խուսափեք ծանր import-ներից, եթե դրանք պետք չեն յուրաքանչյուր կանչի համար։
11Անվտանգության մոդել և տարածված հարձակման մակերեսներ
Յուրաքանչյուր ֆունկցիայի IAM execution role-ը նրա ինքնությունն է. կոտրված ֆունկցիան կարող է անել ամեն ինչ, ինչ թույլ է տալիս այդ role-ը, ուստի սահմանափակեք այն կոնկրետ ռեսուրսներով և գործողություններով։ Իրադարձության տվյալները անվստահելի մուտք են, նույնիսկ երբ գալիս են AWS-ի ներսից. S3 օբյեկտի key-ը կամ SQS-ի body-ն կարող են injection payload կրել։ NONE auth տիպով Function URL-ները հանրայնորեն կանչելի են. օգտագործեք AWS_IAM auth, կամ ինքներդ վալիդացրեք ստորագրությունը կամ token-ը։ Գաղտնիքները պետք է բերվեն Secrets Manager-ից կամ Parameter Store-ից քեշավորմամբ (օգնում է AWS Parameters and Secrets Lambda Extension-ը), ոչ թե ներդրվեն console-ում տեսանելի միջավայրի փոփոխականներում։ Ամրագրեք կախվածությունները և սկանավորեք դրանք, որովհետև supply-chain ռիսկը կենտրոնացած է փոքր bundle-ներում, որոնք ուղիղ գնում են production։
Իրական իրավիճակներ
Տրաֆիկի թռիչքը գցեց բազան
Խնդիր
Մարքեթինգային email-ը հազարավոր օգտատերերի ուղարկեց Lambda-ի և PostgreSQL-ի վրա կառուցված API։ Lambda-ն ակնթարթորեն մասշտաբավորվեց, հետո բազան հասավ կապերի սահմանին, և բոլոր հարցումները ձախողվեցին։
Ինչպես լուծել
Նախ API ֆունկցիաների վրա սահմանեք reserved concurrency այնպիսի թվով, որը բազան կարող է դիմանալ, որպեսզի ավելորդ հարցումները throttle լինեն՝ ամեն ինչ գցելու փոխարեն։ Հետո ֆունկցիաների և բազայի միջև դրեք RDS Proxy (կամ PgBouncer)՝ կապերը pool և մուլտիպլեքսավորելու համար, և համոզվեք, որ client-ները ստեղծվում են handler-ից դուրս։ Թեժ ընթերցման endpoint-ները քեշավորեք CDN-ում կամ Redis-ում, որպեսզի հարցումների մեծ մասը երբեք չհասնի բազային։ Վերջում հաջորդ արշավից առաջ load-test արեք իրատեսական concurrency-ով և alarm դրեք throttle-ների և բազայի կապերի քանակի վրա։
Կրկնակի email-ներ deploy-ից հետո
Խնդիր
SQS-ով գործարկվող ֆունկցիան ուղարկում է պատվերի հաստատման email-ներ։ Երրորդ կողմի դանդաղ անջատումից հետո որոշ հաճախորդներ նույն email-ը ստացան երեք անգամ։
Ինչպես լուծել
Ֆունկցիան idempotent չէր. timeout-ների պատճառով SQS-ը հաղորդագրությունները կրկին տեսանելի դարձրեց և նորից առաքեց։ Լուծեք՝ յուրաքանչյուր պատվերի ID-ն ուղարկելուց առաջ գրանցելով DynamoDB-ում պայմանական put-ով (attribute_not_exists). եթե գրառումը ձախողվում է, բաց թողեք ուղարկումը։ Ֆունկցիայի timeout-ը սահմանեք հերթի visibility timeout-ից ցածր (AWS-ը խորհուրդ է տալիս visibility-ն առնվազն վեց անգամ մեծ լինի ֆունկցիայի timeout-ից), միացրեք ReportBatchItemFailures, որպեսզի մեկ ձախողումը չկրկնի ամբողջ batch-ը, և կցեք dead-letter queue alarm-ով, որպեսզի մշտական ձախողումները ուսումնասիրվեն՝ հավերժ կրկնվելու փոխարեն։
Նկարների չափափոխում վերբեռնման ժամանակ
Խնդիր
Պրոդուկտին պետք է thumbnail-ներ գեներացնել, երբ օգտատերերը լուսանկարներ են վերբեռնում։ Թիմը չի ուզում առանձին worker սերվեր պահել մի աշխատանքի համար, որը տեղի է ունենում օրական մի քանի հարյուր անգամ։
Ինչպես լուծել
Ստեղծեք S3 bucket բնօրինակների համար՝ ObjectCreated-ի վրա event notification-ով, որը գործարկում է Lambda ֆունկցիա։ Ֆունկցիան հոսքով կարդում է բնօրինակը, չափափոխում է native գրադարանով, օրինակ՝ arm64-ի համար փաթեթավորված sharp-ով, և thumbnail-ները գրում է այլ bucket-ում կամ prefix-ում՝ ռեկուրսիվ trigger-ներից խուսափելու համար։ Տվեք նրան 1-2 GB հիշողություն ավելի արագ CPU-ի համար, 30 վայրկյան timeout և IAM role, որը սահմանափակված է աղբյուրից կարդալով և նպատակակետում գրելով։ Thumbnail-ները մատուցեք CDN-ով։ Այս ծավալի դեպքում ծախսը մնում է զրոյին մոտ, և patch անելու բան չկա։
Արագ ուղեցույց
- Կանչել deploy արված ֆունկցիան
aws lambda invoke --function-name fn --payload '{}' --cli-binary-format raw-in-base64-out out.json- Լոգերը հետևել իրական ժամանակում
aws logs tail /aws/lambda/fn --follow- SAM. build և deploy
sam build && sam deploy --guided- SAM. գործարկել լոկալ
sam local invoke GetProduct -e event.json- Սահմանափակել concurrency-ն
aws lambda put-function-concurrency --function-name fn --reserved-concurrent-executions 50- Ստեղծել հանրային HTTPS URL (IAM auth)
aws lambda create-function-url-config --function-name fn --auth-type AWS_IAM- Cloudflare Worker. ստեղծել և deploy անել
npm create cloudflare@latest && npx wrangler deploy- Lambda-ի կոշտ սահմանները
15 րոպե timeout, 128 MB-10,240 MB հիշողություն, 6 MB սինքրոն payload, առավելագույնը 10 GB /tmp
Հարցեր և պատասխաններ
Q1Ի՞նչ է cold start-ը և ինչպե՞ս այն կրճատել։
Cold start-ը տեղի է ունենում, երբ տաք կատարման միջավայր հասանելի չէ, ուստի պլատֆորմը պետք է ստեղծի այն, բեռնի ձեր կոդը, գործարկի runtime-ը և կատարի ձեր ինիցիալիզացիան՝ նախքան հարցումը մշակելը։ Այն կարող է ավելացնել մոտ հարյուր միլիվայրկյանից մինչև մեկ վայրկյանից ավելի։ Կրճատելու համար՝ deploy փաթեթը պահեք փոքր bundling-ով և tree-shaking-ով, խուսափեք ծանր ֆրեյմվորքներից init ուղում, ընտրեք արագ գործարկվող runtime և հազվադեպ օգտագործվող մոդուլները բեռնեք ծույլ կերպով։ Որտեղ դա իսկապես կարևոր է, օգտագործեք provisioned concurrency՝ միջավայրերը տաք պահելու համար, կամ աջակցվող runtime-երում SnapStart՝ snapshot-ից վերսկսելու համար։ Edge isolate պլատֆորմները ըստ դիզայնի հիմնականում խուսափում են cold start-երից։
Q2Ինչպե՞ս են serverless ֆունկցիաներն անվտանգ աշխատում ռելյացիոն բազայի հետ։
Վտանգը կապերի քանակն է. յուրաքանչյուր զուգահեռ միջավայր բացում է իր սեփական կապը, ուստի 500 զուգահեռ կանչի թռիչքը կարող է փորձել բացել 500 կապ և սպառել PostgreSQL-ը կամ MySQL-ը։ Լուծումներ. բազայի առջև դրեք RDS Proxy կամ PgBouncer՝ կապերը pool անելու համար, օգտագործեք HTTP-ի վրա հիմնված կամ serverless դրայվերներ, որոնք առաջարկում են Neon-ի կամ PlanetScale-ի նման provider-ները, ստեղծեք client-ը handler-ից դուրս, որպեսզի տաք կանչերը նորից օգտագործեն այն, և սահմանափակեք concurrency-ն reserved concurrency-ով, որպեսզի ֆունկցիաները չկարողանան ճնշել բազան։ Նաև հարցումները պահեք կարճ. երկար տրանզակցիաները զբաղեցնում են pool-ի կապերը և նվազեցնում իրական թողունակությունը։
Q3Ե՞րբ է serverless-ը սերվերից ավելի թանկ։
Երբ ծանրաբեռնվածությունը կայուն է և բարձր։ Ֆունկցիաների գինը հաշվարկվում է ըստ հարցման և ըստ GB-վայրկյանի, ինչը հիանալի է, երբ տրաֆիկը պոռթկումային է կամ ժամանակի մեծ մասը պարապ, որովհետև պարապ վիճակն ոչինչ չարժե։ Բայց եթե ֆունկցիան փաստացի զբաղված է շուրջօրյա, նույն աշխատանքը կոնտեյների կամ VPS-ի վրա սովորաբար ավելի էժան է։ Թաքնված ծախսերը նույնպես կարևոր են. API Gateway-ի վճարներն ըստ հարցման, NAT gateway-ի ժամավճարներն ու տվյալների վճարները, CloudWatch-ի լոգերի ընդունումը և տարածաշրջանների միջև փոխանցումը հաճախ գերազանցում են compute-ի հաշիվը։ Ես գնահատում եմ իրական տրաֆիկի թվերով և համեմատում ընդհանուր ծախսը, ոչ միայն ֆունկցիայի գինը։
Q4Ի՞նչ է նշանակում «at-least-once delivery»-ն ֆունկցիայի նախագծման համար։
Ասինխրոն կանչերը, SQS-ը, EventBridge-ը և stream trigger-ները երաշխավորում են, որ իրադարձությունը կառաքվի, բայց սխալների, timeout-ների կամ ներքին կրկնակի փորձերի դեպքում կարող են այն առաքել մեկից ավելի անգամ։ Ուստի handler-ները պետք է լինեն idempotent. նույն իրադարձությունը երկու անգամ մշակելը պետք է ունենա նույն էֆեկտը, ինչ մեկ անգամը։ Տիպիկ տեխնիկաներն են մշակված իրադարձությունների ID-ները պայմանական գրառմամբ պահելը, բնական key-երի և upsert-ների օգտագործումը և idempotency key-եր փոխանցելը վճարային provider-ներին։ Անընդհատ կրկնվող ձախողումների համար կարգավորեք dead-letter queue կամ on-failure նպատակակետ, որպեսզի «թունավորված» հաղորդագրությունները մի կողմ դրվեն ուսումնասիրության համար՝ հավերժ կրկնվելու փոխարեն։
Q5Edge ֆունկցիաներ, թե՞ տարածաշրջանային ֆունկցիաներ. ինչպե՞ս եք որոշում։
Նայում եմ, թե որտեղ են ապրում տվյալները և ինչ է պետք կոդին։ Edge ֆունկցիաներն աշխատում են օգտատիրոջ մոտ և գործարկվում գրեթե ակնթարթորեն, ուստի հիանալի են redirect-ների, auth token-ների ստուգման, header-ների, A/B թեստերի, գեոլոկացիայի և edge storage-ից կամ քեշից կառուցված պատասխանների համար։ Բայց դրանք ունեն սահմանափակ runtime-եր և CPU ժամանակ, և եթե յուրաքանչյուր հարցում պետք է դիմի մեկ տարածաշրջանում գտնվող բազային, հեռավոր edge-երից շրջագայությունները դրանք դարձնում են ավելի դանդաղ, քան բազայի կողքին դրված տարածաշրջանային ֆունկցիան։ Այսպիսով՝ edge՝ թեթև, քիչ տվյալներ պահանջող տրամաբանության համար. տարածաշրջանային՝ այն ամենի համար, ինչը բազայով ծանր է կամ պահանջում է Node.js-ի լիարժեք API-ներ։
Q6Ինչպե՞ս եք թեստավորում և debug անում serverless ֆունկցիաները։
Բիզնես տրամաբանությունը պահում եմ սովորական մոդուլներում, որոնք unit-թեստավորվում են առանց որևէ cloud-ի, իսկ handler-ները պահում եմ բարակ. վերլուծել իրադարձությունը, կանչել տրամաբանությունը, ձևավորել պատասխանը։ Ինտեգրացիայի համար AWS SAM-ը կարող է լոկալ կանչել ֆունկցիաները օրինակային իրադարձություններով և էմուլյացնել API Gateway-ը, իսկ LocalStack-ը էմուլյացնում է ավելի շատ ծառայություններ։ Հետո յուրաքանչյուր pull request deploy է լինում առանձին stage-ում կամ account-ում՝ իրական end-to-end թեստերի համար։ Production-ում կառուցվածքային JSON լոգերը request ID-ով, բաշխված tracing-ը և alarm-ները սխալների, throttle-ների և տևողության վրա կենսական են, որովհետև չեք կարող միանալ սերվերին և նայել շուրջը։
Սենիոր մակարդակի հարցեր
S1Նախագծեք serverless պատվերների pipeline, որը հուսալի է մասնակի ձախողումների դեպքում։
API ֆունկցիան վալիդացնում է պատվերը, գրում այն DynamoDB-ում client-ից եկած idempotency key-ով և նույն քայլում հրապարակում OrderPlaced իրադարձություն DynamoDB Streams-ի կամ transactional outbox-ի միջոցով, որպեսզի գրառումն ու իրադարձությունը չկարողանան տարամիտվել։ EventBridge-ը իրադարձությունն ուղղորդում է առանձին SQS հերթեր վճարման, պահեստի և email-ի համար, որոնցից յուրաքանչյուրը սպառում է իր ֆունկցիան՝ batch-ի մասնակի ձախողումների հաշվետվությամբ և dead-letter queue-ով։ Յուրաքանչյուր սպառող idempotent է՝ ըստ պատվերի ID-ի։ Փոխհատուցմամբ բազմաքայլ հոսքերի համար, օրինակ՝ գումարը վերադարձնելը, երբ պահեստը ձախողվում է, Step Functions-ը ղեկավարում է saga-ն՝ բացահայտ կրկնակի փորձերով և timeout-ներով։ Դիտարկելիությունը օգտագործում է correlation ID-ներ բոլոր ֆունկցիաներում, իսկ alarm-ները հետևում են DLQ-ի խորությանը, սխալների հաճախականությանը և ամենահին հաղորդագրության տարիքին։
S2Ինչպե՞ս խուսափել vendor lock-in-ից serverless-ի դեպքում, և արժե՞ արդյոք։
Որոշակի lock-in-ը օգուտի գինն է, ուստի որոշում եմ ըստ շերտի։ Բիզնես տրամաբանությունը ապրում է սովորական մոդուլներում՝ առանց provider-ի import-ների, իսկ handler-ները բարակ adapter-ներ են, ինչը թանկ մասը պահում է տեղափոխելի։ HTTP ֆունկցիաները կարող են օգտագործել վեբ ստանդարտի Request և Response՝ Hono-ի նման ֆրեյմվորքների միջոցով, որոնք աշխատում են Lambda-ի, Workers-ի, Node-ի և Bun-ի վրա։ Ենթակառուցվածքը սահմանվում է Terraform-ով կամ նմանատիպ գործիքով, որպեսզի այն գոնե փաստաթղթավորված և վերանայելի լինի։ Այն, ինչ չեմ անում, ամեն managed ծառայություն ինքնաշեն աբստրակցիայով փաթաթելն է. հերթերը կամ իրադարձությունների routing-ը «cloud-agnostic» լինելու համար վերաիրականացնելը սովորաբար ավելի թանկ է, քան որևէ միգրացիա։ Իրական ելքի պլանը նույն handler-ները կոնտեյներացնելն ու Cloud Run, Fargate կամ VPS տեղափոխելն է, եթե գները կամ սահմանափակումները փոխվեն։
S3Ե՞րբ կտեղափոխեիք workload-ը Lambda-ից, և ու՞ր։
Ազդանշաններն են. կայուն բարձր օգտագործում, որտեղ GB-վայրկյանի գինը գերազանցում է կոնտեյների ծախսը, 15 րոպեի սահմանին մոտ ֆունկցիաներ կամ երկարատև կապերի կարիք, ինչպես WebSocket-ները կամ gRPC stream-երը, յուրաքանչյուր հարցման ծանր ինիցիալիզացիա, որը պատժվում է cold start-երով, կամ բազմաթիվ ֆունկցիաներ, որոնք այնքան շատ կոդ են կիսում, որ deploy-երը դառնում են դանդաղ և իրար կապված։ Սովորական թիրախը serverless կոնտեյներներն են, ինչպես Fargate-ը կամ Cloud Run-ը, որոնք պահպանում են autoscaling-ը և կառավարվող ենթակառուցվածքը, բայց հանում են runtime սահմանափակումները և թույլ տալիս մեկ instance-ում բազմաթիվ զուգահեռ հարցումներ, կամ պարզ VPS Docker Compose-ով՝ կանխատեսելի workload-ների համար։ Ես նախ կչափեի, կտեղափոխեի ամենաթեժ ուղին և իրադարձություններով աշխատող «սոսինձ» կոդը, ինչպես webhook-ներն ու պլանավորված job-երը, կթողնեի ֆունկցիաների վրա, որտեղ դրանք ամենաէժանն են։
Բառարան
- FaaS
- Functions as a Service. provider-ը գործարկում է ձեր առանձին ֆունկցիաները՝ ի պատասխան իրադարձությունների, և հաշվարկում ըստ օգտագործման։
- Cold start
- Լրացուցիչ latency, երբ նոր կատարման միջավայր պետք է ստեղծվի և ինիցիալիզացվի՝ նախքան հարցումը մշակելը։
- Կատարման միջավայր
- Մեկուսացված micro-VM կամ isolate, որտեղ աշխատում է ֆունկցիան. այն կարող է նորից օգտագործվել հետագա կանչերի համար։
- Concurrency
- Թե ֆունկցիայի քանի կանչ է աշխատում միևնույն պահին՝ յուրաքանչյուրն իր միջավայրում։
- Provisioned concurrency
- Նախապես ինիցիալիզացված միջավայրեր, որոնք վճարով պահվում են տաք՝ վերացնելով cold start-երը այդ թողունակության համար։
- Event source mapping
- Lambda-ի ռեսուրս, որը poll է անում հերթը կամ stream-ը և ձեր ֆունկցիան կանչում գրառումների batch-երով։
- Dead-letter queue
- Հերթ, որտեղ անընդհատ ձախողվող իրադարձությունները մի կողմ են դրվում հետագա ուսումնասիրության համար՝ հավերժ կրկնվելու փոխարեն։
- V8 isolate
- Թեթև sandbox մեկ JavaScript engine պրոցեսի ներսում. այն գործարկվում է միլիվայրկյաններում և ընկած է edge պլատֆորմների հիմքում։
- Idempotent
- Գործողություն, որն ունի նույն էֆեկտը՝ անկախ նրանից, կատարվում է մեկ, թե մի քանի անգամ նույն մուտքով։
Շարունակեք սովորել
Պրոդուկտի գաղափար ունե՞ք։Եկեք ստեղծենք այն։
Պատմեք ձեր գաղափարի, պրոդուկտի կամ թիմի մասին։ Սովորաբար պատասխանում եմ մեկ աշխատանքային օրվա ընթացքում։