Բոլոր դասերը
DevOps և ամպՄիջին12 րոպե ընթերցումԱրժե իմանալ

Serverless ֆունկցիաներ

Կոդը գործարկեք ըստ պահանջի՝ առանց սերվերներ կառավարելու. Lambda, edge ֆունկցիաներ, trigger-ներ և cold start-եր։

  • Lambda
  • Edge functions
  • Cold starts
  • Event triggers
Քայլ 1/5
Հարցումներ չկան՝ զրո instance։ Ոչինչ չի աշխատում, ու ոչինչ չես վճարում։ Պլատֆորմը խոր քնած է։

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-ից դուրս, որպեսզի տաք կանչերը նորից օգտագործեն դրանք։

js
// 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-երի դեպքում հայտնեք մասնակի ձախողումները, որպեսզի կրկնվեն միայն ձախողված հաղորդագրությունները։

js
// 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-ին սպասարկել մի քանի զուգահեռ հարցում։

ts
// 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)։

yaml
# 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-ի և կրկնակի փորձերի նուրբ վերահսկողությամբ։

Լավագույն պրակտիկաներ

  1. Client-ները և կոնֆիգուրացիան ինիցիալիզացրեք handler-ից դուրս և bundle-ները պահեք փոքր՝ cold start-երը կրճատելու համար։
  2. Յուրաքանչյուր handler դարձրեք idempotent. ասինխրոն և հերթային trigger-ները առաքում են առնվազն մեկ անգամ (at least once)։
  3. Յուրաքանչյուր ֆունկցիայի տվեք իր նվազագույն արտոնություններով IAM role-ը և գաղտնիքները պահեք secrets manager-ում։
  4. Սահմանեք բացահայտ timeout-ներ և հիշողություն. փորձարկեք հիշողության չափերը, որովհետև ավելի շատ հիշողությունը նշանակում է նաև ավելի շատ CPU և ընդհանուր առմամբ կարող է ավելի էժան լինել։
  5. Օգտագործեք կառուցվածքային JSON լոգեր, tracing (X-Ray կամ OpenTelemetry) և alarm-ներ սխալների, throttle-ների և տևողության վրա։
  6. Պաշտպանեք բազաները connection pooling-ով (RDS Proxy, serverless դրայվեր) և reserved concurrency-ով։
  7. Ամեն ինչ սահմանեք 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-ն, որը կարող է հաղորդագրությունները հետ ուղարկել հերթ։

bash
# 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։

Իրական իրավիճակներ

01

Տրաֆիկի թռիչքը գցեց բազան

Խնդիր

Մարքեթինգային email-ը հազարավոր օգտատերերի ուղարկեց Lambda-ի և PostgreSQL-ի վրա կառուցված API։ Lambda-ն ակնթարթորեն մասշտաբավորվեց, հետո բազան հասավ կապերի սահմանին, և բոլոր հարցումները ձախողվեցին։

Ինչպես լուծել

Նախ API ֆունկցիաների վրա սահմանեք reserved concurrency այնպիսի թվով, որը բազան կարող է դիմանալ, որպեսզի ավելորդ հարցումները throttle լինեն՝ ամեն ինչ գցելու փոխարեն։ Հետո ֆունկցիաների և բազայի միջև դրեք RDS Proxy (կամ PgBouncer)՝ կապերը pool և մուլտիպլեքսավորելու համար, և համոզվեք, որ client-ները ստեղծվում են handler-ից դուրս։ Թեժ ընթերցման endpoint-ները քեշավորեք CDN-ում կամ Redis-ում, որպեսզի հարցումների մեծ մասը երբեք չհասնի բազային։ Վերջում հաջորդ արշավից առաջ load-test արեք իրատեսական concurrency-ով և alarm դրեք throttle-ների և բազայի կապերի քանակի վրա։

02

Կրկնակի 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-ով, որպեսզի մշտական ձախողումները ուսումնասիրվեն՝ հավերժ կրկնվելու փոխարեն։

03

Նկարների չափափոխում վերբեռնման ժամանակ

Խնդիր

Պրոդուկտին պետք է 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
Գործողություն, որն ունի նույն էֆեկտը՝ անկախ նրանից, կատարվում է մեկ, թե մի քանի անգամ նույն մուտքով։

Պրոդուկտի գաղափար ունե՞ք։Եկեք ստեղծենք այն։

Պատմեք ձեր գաղափարի, պրոդուկտի կամ թիմի մասին։ Սովորաբար պատասխանում եմ մեկ աշխատանքային օրվա ընթացքում։