Animated Architecture Patterns

AWS Step Functions

workflow ในรูป state machine: ทำงานตามลำดับ แยก branch, retry, รอ และรันขนานกันข้ามหลาย service โดยบันทึกทุก step ไว้

หรือที่เรียกกันว่า Step Functions, SFN

AWS Step Functions state machine PlaceOrder · Standard Catch Retry > 50,000 Default Orders API Acme Shop answers the shopper 202 starts o-984 with an invoice StartExecution name "o-981"{"orderId":"o-981",…,"total":1280} o-982 {…,"total":420}o-983 {…,"total":68400} 2 MB invoice inline: refusedS3 key of the invoice: started ValidateOrderLambda ReserveStockDynamoDB ChargeCardLambda ReleaseStockDynamoDB WaitForApprovaltoken SendConfirmationSNS ArrangeShippingECS .sync NeedsApproval Fulfil Parallel Succeeded OrderFailed Fail state 1,280 ≤50,000 2 s 4 s o-982 o-982 o-983 Lambda validate-order charge-card payment provider outside AWS DynamoDB table Stock · UpdateItem KB-104: 12 KB-104: 11 MS-310: 8 MS-310: 7 Approver back office app, reads the queue approvals over THB 50,000 o-983: approve? SendTaskSuccess SNS topic order-confirmations ECS on Fargate task arrange-shipping RUNNING STOPPED, exit code 0 callresulterrortask tokenstart ORCHESTRATIONPlaceOrder is a statemachine, written in theAmazon States Language.Step Functions runs eachexecution: it calls theservices in turn, handseach state’s output tothe next state andrecords every step.Without it, glue code:each function calls thenext one and keeps itsown retries, waits andstate. ONE EXECUTIONReserveStock callsDynamoDB UpdateItemitself, an optimizedintegration: no functionin between. Its condition(available ≥ qty) makesthe call fail when stockis short.ArrangeShipping uses.sync: Step Functionswaits until the ECS taskstops. Fulfil waits forboth branches. RETRY, CATCH, WAITo-982: the provider doesnot answer, so charge-cardthrows Payment.Timeout.Retried after 2 s and 4 s,it fails a third time andCatch runs ReleaseStock(the compensation), thenthe Fail state OrderFailed.o-983 (THB 68,400) waitsat WaitForApproval untilthe approver’s app callsSendTaskSuccess with itstask token, for up to ayear in a Standard workflow. ALTERNATIVESDurable execution incode: Temporal, orLambda durable functions.Scheduled batch datapipelines: Apache Airflow(Amazon MWAA).Many short, high-volumeruns: an Express workflow,nested in a Standard onewhen parts need a year,.sync or a task token. AMAZON STATES LANGUAGE (JSON)"QueryLanguage": "JSONata","StartAt": "ValidateOrder","States": { "ValidateOrder": { "Type": "Task", "Resource": "arn:aws:states:::lambda:invoke", "Arguments": {…}, "Next": "ReserveStock" }, … CHARGECARD, IN JSONATA"Arguments": { "FunctionName": "charge-card", "Payload": "{% $states.input %}" },"Assign": { "chargeId": "{% $states.result.Payload.chargeId %}" },"Output": "{% $states.input %}" CHARGECARD: RETRY AND CATCH"Retry": [{ "ErrorEquals": ["Payment.Timeout"], "IntervalSeconds": 2, "BackoffRate": 2, "MaxAttempts": 2 }],"Catch": [{ "ErrorEquals": ["States.ALL"], "Next": "ReleaseStock" }] LIMITS (OCTOBER 2026)256 KiB per input or output: pass S3 keys25,000 history events, then it failsevery state entered and every retry is billed EXECUTION HISTORYo-981 · each state's input and output, kept 90 days EXECUTION HISTORYo-982 · the error is recorded with its cause ExecutionStarted ValidateOrder ✓ ReserveStock ✓ ChargeCard ✓ NeedsApproval ✓ SendConfirmation ✓ ArrangeShipping ✓ Fulfil ✓ ExecutionSucceeded input {"orderId":"o-981","sku":"KB-104","qty":1,"total":1280} ChargeCard: output = its input · assigned chargeId "ch-5512" ExecutionStarted ValidateOrder ✓ ReserveStock ✓ ChargeCard ✕ ✕ ✕ Catch → ReleaseStock ✓ OrderFailed ExecutionFailed error "Payment.Timeout" · cause "no answer from the provider" STANDARD (PlaceOrder)EXPRESSprices: us-east-1up to 1 year · exactly oncehistory 90 days · .sync, task tokens$0.025 per 1,000 state transitionsup to 5 minutes · request-responseat least once (async), at most once (sync)$1.00 per million + GB-s · logs only StartExecution executionArn 2 MB S3 key

ขั้นที่ 1 · Step Functions อยู่ตรงไหน Orders API ของ Acme Shop เรียก StartExecution บน Standard state machine PlaceOrder ตั้งชื่อ execution ตาม order (o-981) และส่ง order เข้าไปเป็น input ตัว call คืน execution ARN กลับมาทันที ทำให้ API ตอบคนซื้อได้โดยไม่ต้องรอ จากนั้น Step Functions ก็ไล่ทำทีละ state: เรียก Lambda, DynamoDB, SNS และ ECS ตามลำดับ ส่ง output ของแต่ละ state ต่อให้ state ถัดไป และเขียนทุก step ลง execution history ตัว flow อยู่ใน definition เดียวที่เขียนด้วย Amazon States Language แทนที่จะเป็น glue code ที่เรียก Lambda function ต่อกันเป็นทอด ๆ แบบนี้ Step Functions เลยเป็นตัว orchestrator ของ saga

ขั้นที่ 2 · รัน o-981 ValidateOrder invoke Lambda function ส่วน ReserveStock เรียก DynamoDB UpdateItem พร้อม condition ว่าต้องเหลือสต็อกพอ โดยเรียกเองผ่าน optimized integration เลยไม่มี function คั่นกลาง แล้ว ChargeCard ก็เก็บ charge ID ไว้ใน variable ด้วย Assign ตัว Choice state NeedsApproval เห็นว่า THB 1,280 ต่ำกว่าเกณฑ์ THB 50,000 เลยไปทาง Default สู่ Fulfil ตัวนี้เป็น Parallel state ที่รันทุก branch พร้อมกัน: branch หนึ่ง publish คำยืนยันไปที่ SNS อีก branch รัน ECS task แบบ .sync แล้วรอจนกว่า task จะหยุด ระหว่างทาง execution history ก็เก็บ input และ output ของแต่ละ state รวมถึง variable ที่มัน assign ไว้ด้วย โดย workflow นี้ใช้ JSONata (Arguments, Output, Assign) ตามที่ AWS แนะนำสำหรับ workflow ใหม่ แต่ค่าตั้งต้นยังเป็น JSONPath อยู่

ขั้นที่ 3 · Retry, catch และ wait สำหรับ o-982 ผู้ให้บริการ payment ไม่ตอบ แล้ว charge-card ก็ throw Payment.Timeout ตัว retrier (IntervalSeconds 2, BackoffRate 2, MaxAttempts 2) รัน ChargeCard ใหม่หลัง 2 s และหลัง 4 s พอครั้งที่สามพังอีก Catch ก็ส่ง execution ไปที่ ReleaseStock เพื่อคืนสต็อก (step ชดเชย) แล้วต่อไปที่ Fail state OrderFailed โดยที่ history บันทึก error และสาเหตุของมันไว้ ระหว่างนั้น o-983 (THB 68,400) มาถึง WaitForApproval ที่ส่ง task token ไปที่ approvals queue แล้วหยุดรอจนกว่า app ของคนอนุมัติจะเรียก SendTaskSuccess พร้อม token นั้น ตัว Standard execution รอแบบนี้ได้นานถึงหนึ่งปี เพราะฉะนั้นให้ตั้ง TimeoutSeconds ไว้ที่ task ด้วย

ขั้นที่ 4 · Standard, Express และข้อจำกัด workflow แบบ Standard อย่าง PlaceOrder รันได้นานถึงหนึ่งปี ไม่รัน state ไหนซ้ำสองครั้ง ยกเว้น retrier สั่ง เก็บ history ไว้ 90 วัน และคิดเงิน $0.025 ต่อ 1,000 state transition รวม retry ด้วย (US East, N. Virginia, ตุลาคม 2026) ส่วน workflow แบบ Express รันได้ไม่เกิน 5 นาที แบบ at least once ถ้าเริ่มแบบ asynchronous และ at most once ถ้าเริ่มแบบ synchronous มันไม่เก็บ history ใน Step Functions (มีแค่ใน CloudWatch Logs) ไม่รองรับทั้ง .sync และ task token และคิดเงิน $1.00 ต่อล้าน request บวกค่า duration เลยเหมาะกับงาน process event ปริมาณมาก ข้อจำกัดที่ต้องออกแบบรอบ ๆ คือ 256 KiB สำหรับ input หรือ output ของ state ไหนก็ตาม (invoice ขนาด 2 MB ต้องส่งไปเป็น S3 key), 25,000 event ใน history ของ execution หนึ่ง และบิลที่โตตามทุก transition ใน workflow ที่มี step ถี่ ๆ ส่วนทางเลือกที่คนมักใช้แทนคือ Temporal (durable execution ในโค้ด) และ Apache Airflow (batch pipeline ที่ตั้งเวลาไว้)

ปัญหา

การส่ง order ของ Acme Shop แตะห้าระบบ: Lambda function ตรวจ order, DynamoDB เก็บสต็อก, ผู้ให้บริการ payment ตัดบัตร, SNS ส่งคำยืนยันออกไป และ container task จองบริษัทขนส่ง ส่วน order ใหญ่ ๆ ยังต้องมีคนอนุมัติด้วย วิธีที่คิดออกก่อนคือเชื่อมทั้งหมดด้วย glue code: Lambda function แต่ละตัวเรียกตัวถัดไป หรือวาง message ลง queue ถัดไป แต่แบบนี้ process จะมีอยู่แค่ในรูปของ call ที่ต่อกันเป็นทอด ๆ กระจายอยู่ในหลาย code base แต่ละ function ต้องมี retry และ timeout ของตัวเอง การรออนุมัติต้องถูกเก็บไว้ที่ไหนสักแห่งเป็นวัน ๆ ไม่มีใครเห็นว่า order o-982 หยุดอยู่ตรงไหน และถ้าพังกลางทาง สต็อกก็จะถูกจองค้างไว้ให้ order ที่ไม่มีวันได้จ่ายเงิน

AWS Step Functions คือ workflow service แบบ managed ของ AWS เราเขียน process ไว้ครั้งเดียวเป็น state machine ใน Amazon States Language (ASL) ที่เป็น format แบบ JSON แล้ว Step Functions ก็รันทุก execution ของมันให้: เรียก service, ส่งข้อมูลจาก step หนึ่งไปอีก step, ทำ retry และ catch error ตามที่ definition บอก, รอนานเท่าที่ต้องรอ และบันทึกทุก step ไว้ ส่วน function ก็กลับไปทำงานอย่างเดียวของตัวเองเหมือนเดิม

ทำงานยัง⁠ไง

State และ execution

  • state machine คือ JSON document หนึ่งตัว โดย StartAt บอกชื่อ state แรก และ States เก็บ state ทั้งหมดตามชื่อ แต่ละ state มี Type และส่วนใหญ่มี Next ด้วย ตัว definition ใหญ่ได้ถึง 1 MB และ Workflow Studio ใน console ก็วาดและแก้มันเป็น graph ได้
  • Task คือตัวทำงาน: มันเรียก AWS service, HTTPS API หรือ activity worker ส่วน Choice แยก branch ตามข้อมูล Parallel รันชุด branch ที่กำหนดไว้พร้อมกันแล้วไปต่อเมื่อทุก branch เสร็จ Map รัน step ชุดเดียวกันกับทุก item ใน list Wait หยุดรอเป็นจำนวนวินาทีหรือจนถึง timestamp ที่กำหนด Pass ส่งต่อหรือปรับรูปข้อมูลโดยไม่ได้ทำงานอะไร และ Succeed กับ Fail ก็จบ execution
  • Map มีสองโหมด โหมด Inline รันได้ถึง 40 iteration พร้อมกันภายใน execution แม่ และ event ของมันก็ลงใน history ของ execution แม่ ส่วนโหมด Distributed รันแต่ละ item หรือแต่ละ batch ของ item เป็น child execution ที่มี history ของตัวเอง ได้ถึง 10,000 ตัวขนานกัน และอ่าน item ได้ตรงจาก Amazon S3 (เช่น CSV, JSON Lines, Parquet และ Athena manifest)
  • execution คือการรันหนึ่งครั้งด้วย JSON input หนึ่งชุด Acme ตั้งชื่อแต่ละ execution ตาม order ของมัน (o-981) ถ้าเป็น Standard state machine แล้วเรียก StartExecution ด้วยชื่อและ input เดียวกับ execution ที่กำลังรันอยู่ มันจะคืน response เดิมแทนที่จะเริ่มตัวที่สอง แต่ถ้า input ต่างไปหรือ execution ปิดไปแล้ว ก็จะได้ ExecutionAlreadyExists และชื่อนั้นจะใช้ซ้ำได้อีกหลัง execution ปิดไป 90 วัน
  • state machine ทุกตัวเป็นได้แบบเดียว คือ Standard หรือ Express (ดูด้านล่าง) และเปลี่ยนประเภททีหลังไม่ได้

การเรียก service

Task state บอกสิ่งที่มันเรียกไว้ใน Resource:

  • Optimized integration (ประมาณยี่สิบ service เช่น Lambda, DynamoDB, ECS และ Fargate, SNS, SQS, EventBridge, AWS Batch, AWS Glue, Athena, SageMaker AI และ Amazon Bedrock) มี resource อย่าง arn:aws:states:::dynamodb:updateItem พร้อมการจัดการเพิ่ม: ผลลัพธ์ JSON ของ Lambda จะถูก parse ให้ และถ้าใช้ .sync ตัว ECS RunTask ที่รายงานว่าพังก็จะทำให้ task พังด้วย ส่วน DynamoDB integration รองรับ GetItem, PutItem, UpdateItem และ DeleteItem แบบนี้เองที่ ReserveStock และ ReleaseStock แก้ table Stock ได้โดยไม่มี function คั่นกลาง
  • AWS SDK integration (arn:aws:states:::aws-sdk:service:action) เรียก API ได้แทบทุกตัวของ AWS service กว่า 200 ตัว ด้วย parameter ของ SDK เอง ในเดือนมีนาคม 2026 AWS เพิ่มอีก 28 service เช่น Amazon Bedrock AgentCore และ S3 Vectors
  • HTTP Task (arn:aws:states:::http:invoke) เรียก HTTPS API ที่อยู่นอก AWS มันเอา credential มาจาก EventBridge connection และต้องได้คำตอบภายใน 60 วินาที ส่วน Acme เลือกเก็บ call ไปหา payment ไว้ใน function charge-card แทน เพราะ SDK ของผู้ให้บริการและ error code ของมันอยู่ที่นั่น
  • Activity คือ worker ของเราเองที่ poll Step Functions ด้วย GetActivityTask แล้วรายงานผลกลับไป

แต่ละ call ใช้ integration pattern หนึ่งในสามแบบ:

Pattern Resource ลงท้ายด้วย Step Functions ไปต่อเมื่อ ใช้ใน PlaceOrder
Request Response ไม่มีอะไร (ค่าตั้งต้น) ทันทีที่ service ตอบ call ValidateOrder, ReserveStock, ChargeCard, SendConfirmation
Run a Job .sync เมื่อ job เสร็จแล้ว: ECS task หยุดแล้ว หรือ Batch หรือ Glue job จบแล้ว ArrangeShipping (ecs:runTask.sync)
Wait for Callback .waitForTaskToken เมื่อมีคนเรียก SendTaskSuccess หรือ SendTaskFailure พร้อม task token WaitForApproval (sqs:sendMessage.waitForTaskToken)

สำหรับ call แบบ .sync ใน account เดียวกัน Step Functions จะตามดู job ผ่าน EventBridge event และการ poll ส่วนถ้า task แบบนี้ถูกทิ้งกลางทาง (execution ถูกหยุด หรือ branch อื่นใน Parallel state เดียวกันพัง) Step Functions จะพยายามหยุด job ให้ แต่ทำแค่แบบ best-effort

นี่คือ ReserveStock และ ChargeCard ตามที่ PlaceOrder นิยามไว้ ด้วย JSONata (ดูหัวข้อถัดไป):

{
  "ReserveStock": {
    "Type": "Task",
    "Resource": "arn:aws:states:::dynamodb:updateItem",
    "Arguments": {
      "TableName": "Stock",
      "Key": { "sku": { "S": "{% $states.input.sku %}" } },
      "UpdateExpression": "SET available = available - :qty",
      "ConditionExpression": "available >= :qty",
      "ExpressionAttributeValues": { ":qty": { "N": "{% $string($states.input.qty) %}" } }
    },
    "Output": "{% $states.input %}",
    "Next": "ChargeCard"
  },
  "ChargeCard": {
    "Type": "Task",
    "Resource": "arn:aws:states:::lambda:invoke",
    "Arguments": { "FunctionName": "charge-card", "Payload": "{% $states.input %}" },
    "Assign": { "chargeId": "{% $states.result.Payload.chargeId %}" },
    "Output": "{% $states.input %}",
    "TimeoutSeconds": 30,
    "Retry": [ {
      "ErrorEquals": [ "Payment.Timeout" ],
      "IntervalSeconds": 2, "BackoffRate": 2, "MaxAttempts": 2
    } ],
    "Catch": [ {
      "ErrorEquals": [ "States.ALL" ],
      "Output": "{% $merge([$states.input, { 'error': $states.errorOutput }]) %}",
      "Next": "ReleaseStock"
    } ],
    "Next": "NeedsApproval"
  }
}

ถ้าสต็อกไม่พอ condition จะทำให้ UpdateItem พังด้วย DynamoDB.ConditionalCheckFailedException แล้ว Catch บน ReserveStock ก็ส่งต่อไปทางของกรณีสินค้าหมดได้ ส่วน Choice state ที่ตามหลัง ChargeCard มีแค่ rule เดียว: "Condition": "{% $states.input.total > 50000 %}" ไปที่ WaitForApproval และที่เหลือทั้งหมดไปที่ Default ของมัน คือ Fulfil

ข้อมูลระหว่าง state

  • output ของแต่ละ state จะกลายเป็น input ของ state ถัดไป และ input หรือ output แต่ละตัวใหญ่ได้ไม่เกิน 256 KiB (ณ ตุลาคม 2026) ข้อมูลที่ใหญ่กว่านั้นต้องไปอยู่ใน S3 แล้วส่งต่อกันเป็น key ตาม pattern Claim Check ส่วนผลลัพธ์ที่ใหญ่เกินจะทำให้ state พังด้วย States.DataLimitExceeded
  • field QueryLanguage เลือกว่า state จะเลือกและปรับรูปข้อมูลยัง⁠ไง ตั้งได้ทั้ง state machine หรือทีละ state แล้วถ้าใช้ JSONata ตัว state จะมีสอง field คือ Arguments (สิ่งที่จะส่ง) และ Output (สิ่งที่จะส่งต่อ) ค่าของมันใส่ expression ใน {% %} ได้ โดยอ้างถึง $states.input, $states.result, $states.errorOutput (ใน Catch) และ $states.context (ชื่อ execution, เวลาเริ่ม และ task token) ส่วน JSONPath ใช้ห้า field แทน (InputPath, Parameters, ResultSelector, ResultPath, OutputPath) ส่วนถ้าไม่ได้ใส่ field ที่เลือกภาษาไว้ ค่าตั้งต้นก็ยังเป็น JSONPath แต่ตั้งแต่ JSONata มาในเดือนพฤศจิกายน 2024 AWS ก็แนะนำให้ใช้ JSONata กับ workflow ใหม่ และ PlaceOrder ก็ใช้มันทั้งหมด
  • Variable (มาในเดือนพฤศจิกายน 2024 เหมือนกัน) เก็บค่าไว้ให้ state ไหนก็ได้ที่มาทีหลัง ไม่ใช่แค่ state ถัดไป ตัว Assign ของ ChargeCard เก็บ chargeId จากผลลัพธ์ของ Lambda แล้ว SendConfirmation ก็อ่าน $chargeId ได้ในอีกสาม state ต่อมา ในขณะที่ตัว order เองไหลต่อไปโดยไม่ถูกแก้ ตัว variable อยู่ใน scope ของ state machine ของมัน: branch ของ Parallel และ iteration ของ Map อ่าน variable ข้างนอกได้แต่มี variable ของตัวเองแยกไว้ ส่วน Distributed Map อ่าน variable ข้างนอกไม่ได้ ส่วนขนาด variable หนึ่งตัวและ Assign หนึ่งตัวเก็บได้ถึง 256 KiB และ variable ทั้งหมดของ execution หนึ่งรวมกันได้ถึง 10 MiB

Error, retry และ timeout

  • error มีชื่อ ชนิดของ exception จาก Lambda function จะกลายเป็นชื่อ error (charge-card throw Payment.Timeout) ส่วน error ของ service จะมี prefix อย่าง Lambda.ServiceException หรือ DynamoDB.ConditionalCheckFailedException แล้ว Step Functions ก็มี error ของตัวเอง: States.Timeout, States.HeartbeatTimeout, States.TaskFailed, States.DataLimitExceeded และ wildcard States.ALL
  • Retry (บน Task, Parallel และ Map state) match error ตามชื่อแล้วลองใหม่: รอ IntervalSeconds ก่อน retry ครั้งแรก (ค่าตั้งต้น 1) คูณด้วย BackoffRate (ค่าตั้งต้น 2) ทุกครั้ง และ retry ทั้งหมด MaxAttempts ครั้ง (ค่าตั้งต้น 3 ส่วน 0 แปลว่าไม่ retry เลย) ตัว MaxDelaySeconds กำหนดเพดานของเวลารอ และ JitterStrategy FULL สุ่มเวลารอ ทำให้ retry จาก execution จำนวนมากกระจายออกไป นี่คือ Retry with Backoff แบบไม่ต้องเขียนเอง ส่วน MaxAttempts 2 ของ ChargeCard แปลว่าลองทั้งหมดสามครั้ง โดยรอ 2 s และ 4 s ตามที่เห็นใน step 3
  • Catch มารับช่วงต่อเมื่อ retry หมดแล้ว: มันส่ง execution ไปที่ fallback state พร้อม error output ที่เป็น object มี Error และ Cause ส่วนใน PlaceOrder ตัว state นั้นคือ ReleaseStock ที่เป็น compensating transaction ของ ReserveStock แล้วตามด้วย Fail state OrderFailed ที่ Error และ Cause ของมันจะกลายเป็นความล้มเหลวของ execution ส่วน States.ALL ไม่ได้ catch States.DataLimitExceeded หรือ States.Runtime เลยต้องใส่ชื่อมันไว้ตรง ๆ ในจุดที่มันสำคัญ
  • Timeout โดยค่าตั้งต้น Task รอได้ถึง 99,999,999 วินาที เพราะฉะนั้นให้ตั้ง TimeoutSeconds บนทุก Task ไม่อย่างนั้น callback ที่หายไปจะทำให้ execution รอค้างอยู่หนึ่งปี worker ที่รันนานเรียก SendTaskHeartbeat ได้ แล้ว HeartbeatSeconds ก็จะทำให้ task พังด้วย States.Timeout เมื่อ heartbeat หยุดมา
  • ใน Parallel state ถ้า branch ไหนพังโดยไม่มีใคร catch ทั้ง state จะพังและ branch อื่นจะถูกหยุด แต่ Lambda function ที่รันอยู่แล้วก็ยังรันต่อไปจนจบ

การรอคนและระบบอื่น

WaitForApproval ส่ง order พร้อม task token ไปที่ queue approvals แล้วหยุดรอ app หลังบ้านของ Acme อ่าน queue นี้ แสดง order ให้คนอนุมัติดู แล้วพอเขาตัดสินใจ app ก็เรียก SendTaskSuccess (หรือ SendTaskFailure) พร้อม token นั้น ตัว Standard execution รอแบบนี้ได้นานถึงหนึ่งปี และเพราะ Standard workflow คิดเงินตาม state transition ไม่ใช่ตามเวลา การรอเองก็เลยไม่ได้เพิ่มบิลเลย ตัว task มี TimeoutSeconds 3 วัน ที่ Catch เปลี่ยนให้เป็นการปฏิเสธอัตโนมัติได้ ส่วน token จะใช้ได้ก็ต่อเมื่อ principal ที่ส่งมันกลับมาอยู่ใน AWS account เดียวกัน และใช้ task token ใน Express workflow ไม่ได้

{
  "WaitForApproval": {
    "Type": "Task",
    "Resource": "arn:aws:states:::sqs:sendMessage.waitForTaskToken",
    "Arguments": {
      "QueueUrl": "https://sqs.us-east-1.amazonaws.com/111122223333/approvals",
      "MessageBody": {
        "orderId": "{% $states.input.orderId %}",
        "total": "{% $states.input.total %}",
        "taskToken": "{% $states.context.Task.Token %}"
      }
    },
    "Output": "{% $states.input %}",
    "TimeoutSeconds": 259200,
    "Next": "Fulfil"
  }
}
# the Orders API starts an execution named after the order
aws stepfunctions start-execution \
  --state-machine-arn arn:aws:states:us-east-1:111122223333:stateMachine:PlaceOrder \
  --name o-983 \
  --input '{"orderId":"o-983","sku":"LT-900","qty":1,"total":68400}'

# later, the back-office app reports the approver's decision with the token from the message
aws stepfunctions send-task-success \
  --task-token "$TASK_TOKEN" \
  --task-output '{"approved":true}'

Standard และ Express

Standard Express
execution ที่นานที่สุด 1 ปี 5 นาที
Execution semantics exactly once: state จะรันซ้ำก็ต่อเมื่อ Retry สั่งเท่านั้น แบบ asynchronous: at least once ส่วนแบบ synchronous (StartSyncExecution): at most once
Execution history Step Functions เก็บไว้ 90 วัน (30 วันถ้าขอ) ได้ถึง 25,000 event และอ่านได้ด้วย GetExecutionHistory ไม่เก็บ มีแค่สิ่งที่เราส่งไป CloudWatch Logs ที่ส่งแบบ best-effort
Integration pattern Request Response, .sync, .waitForTaskToken, activity, Distributed Map Request Response เท่านั้น
Start rate (ค่าตั้งต้น) StartExecution: burst ได้ 1,300 แล้วตามด้วย 300 ต่อวินาทีใน N. Virginia, Oregon และ Ireland (ที่อื่น 800 และ 150) 6,000 ต่อวินาที
State transition 5,000 ต่อวินาทีในสาม Region นั้น ที่อื่น 800 (ขอเพิ่มได้) ไม่จำกัด
Redrive ได้ ภายใน 14 วัน ไม่ได้ (แต่ Express child ของ Distributed Map redrive ได้)
ราคา (US East, ตุลาคม 2026) $0.025 ต่อ 1,000 state transition $1.00 ต่อล้าน request บวกค่า duration ตาม memory

rate ทั้งหมดข้างบนคิดต่อ account และ Region และ account ใหม่จะเริ่มด้วย quota ของ state transition ที่ต่ำกว่านี้ Standard เหมาะกับตัว order เอง: งานยาว ตรวจสอบย้อนหลังได้ และเต็มไปด้วย step ที่ห้ามรันซ้ำ เช่นการตัดบัตร ส่วน Express เหมาะกับงานสั้นปริมาณมาก เช่น process event หรือข้อมูล IoT ที่ทุก step idempotent สองแบบนี้ใช้ร่วมกันได้: AWS แนะนำให้เก็บ step ที่ไม่ idempotent ไว้ใน Standard ตัวแม่ แล้วซ้อน Express workflow ไว้ข้างในสำหรับ step ที่ idempotent และมาเป็น burst

Redrive, version และ alias

  • Redrive (RedriveExecution, พฤศจิกายน 2023) เริ่ม Standard execution ที่พัง ถูก abort หรือ timeout ใหม่ได้ภายใน 14 วันหลังมันจบ มันทำต่อจาก step ที่ไม่สำเร็จ ด้วย input เดิมและ definition เดิม และไม่ทำ step ที่สำเร็จไปแล้วซ้ำ ใน Parallel state จะรันใหม่แค่ branch ที่พัง และตัวนับ retry ก็เริ่มนับใหม่ ถ้า ArrangeShipping พังโดยไม่มี Catch การ redrive หลังแก้เสร็จจะจองบริษัทขนส่งได้โดยไม่ตัดบัตรซ้ำ ส่วน execution ที่ชดเชยไปแล้วและจบที่ Fail state อย่าง o-982 ควรเริ่มใหม่ตั้งแต่ต้นเมื่อผู้ให้บริการกลับมาใช้ได้
  • Version คือ snapshot ของ state machine ที่มีเลขกำกับและแก้ไม่ได้ (ได้ถึง 1,000 ตัว) ส่วน alias อย่าง PROD ชี้ไปที่ version เดียว หรือแบ่ง execution ใหม่ระหว่างสอง version ตามน้ำหนัก (ได้ถึง 100 alias) การเริ่ม execution ผ่าน alias ทำให้ version ใหม่รับ order ไป 10% ก่อนได้ แบบ canary release และทุก execution รวมถึงตัวที่ redrive ก็จะอยู่บน version ที่มันเริ่มต้นไปตลอด

การทดสอบ

API TestState รัน state ตัวเดียว จะส่งมาเดี่ยว ๆ หรือเลือกออกมาจาก definition ทั้งก้อนด้วย stateName ก็ได้ โดยไม่ต้อง deploy อะไร แล้วรายงาน output, state ถัดไป และถ้าใช้ระดับ DEBUG ก็จะบอกทุกขั้นของการ process ข้อมูลด้วย ตั้งแต่เดือนพฤศจิกายน 2025 มันรับ mock ของผลลัพธ์หรือ error ของ service ได้ ทำให้ test ไม่ต้องใช้ทั้ง service จริงและ IAM role และยังทดสอบ Map, Parallel, .sync และ callback state รวมถึง retry ครั้งที่เลือกไว้ได้ด้วย ส่วน Step Functions Local ที่เป็น emulator ให้ดาวน์โหลดไปใช้ ก็ไม่ได้รับการ support แล้ว

# with both retries used up, a timeout from the provider should go to the Catch
aws stepfunctions test-state \
  --definition file://place-order.asl.json --state-name ChargeCard \
  --input '{"orderId":"o-982","sku":"MS-310","qty":1,"total":420}' \
  --mock '{"errorOutput": {"error": "Payment.Timeout", "cause": "no answer from the provider"}}' \
  --state-configuration '{"retrierRetryCount": 2}'
# expected: "status": "CAUGHT_ERROR", "nextState": "ReleaseStock"

การดู execution

  • execution history ของ Standard execution แสดงทุก event: แต่ละ state ที่เข้าและออกพร้อม input, output และ variable ที่มัน assign รวมถึงแต่ละ task ที่ถูก schedule, เริ่ม, สำเร็จ หรือพัง ตัว console วาดมันลงบน graph ทำให้ทีม support เปิด o-982 ขึ้นมาแล้วเห็นได้ว่ามันพังตรงไหนและเพราะอะไร
  • CloudWatch Logs รับ history ตามระดับที่เลือก (ALL, ERROR, FATAL หรือ OFF) จะมีข้อมูลด้วยหรือไม่ก็ได้ (includeExecutionData) และ Express workflow ไม่มีบันทึกที่อื่นนอกจากนี้ ส่วน CloudWatch metrics นับ execution ที่เริ่ม สำเร็จ พัง timeout และโดน throttle รวมถึง execution ที่เปิดอยู่เทียบกับ limit ของ account และตั้งแต่เดือนตุลาคม 2025 console ก็แสดงมันพร้อมตัวเลขค่าใช้จ่ายบน metrics dashboard
  • AWS X-Ray trace execution ไปพร้อมกับ Lambda function และ service ที่มันเรียก ส่วน Standard workflow ส่ง EventBridge event ทุกครั้งที่ status เปลี่ยน ทีมก็ route event นี้ไปที่ alarm หรือไปหา client ที่รออยู่ได้

Pricing

ณ ตุลาคม 2026 ใน US East (N. Virginia):

  • Standard: $0.025 ต่อ 1,000 state transition หลังจาก free tier 4,000 ต่อเดือนที่ไม่มีวันหมดอายุ ทุก state ที่รันนับหมด รวมถึงทุก retry และทุก redrive ด้วย แบบนี้ workflow ที่ใช้สิบ transition ต่อ order จะเสียประมาณ $25 สำหรับ 100,000 order ต่อเดือน
  • Express: $1.00 ต่อล้าน execution บวกค่า duration ที่ $0.00001667 ต่อ GB-second สำหรับ 1,000 GB-hour แรกของเดือน ปัดขึ้นเป็น 100 ms และคิดตาม memory เป็นขั้นละ 64 MB โดย memory ขึ้นกับขนาดของ definition และข้อมูล ส่วน execution ยาว 30 วินาทีที่ 64 MB หนึ่งล้านครั้ง จะเป็นค่า duration ประมาณ $31
  • ยังต้องบวกค่า service ที่ workflow เรียก (Lambda, DynamoDB, ECS) และ CloudWatch Logs เข้าไปอีก

อยู่ตรงไหนใน solution

  • Solution: workflow ของ order และ payment แบบ PlaceOrder, process อนุมัติและ process อื่นที่มีคนอยู่ใน loop, data และ ETL pipeline ที่รัน Distributed Map บน S3 object เป็นล้านตัว, pipeline ของ machine learning และ media (SageMaker AI, MediaConvert), automation ด้าน IT และ security และ AI workflow ที่เรียก model ของ Amazon Bedrock หรือตั้งแต่เดือนมิถุนายน 2026 ก็เรียก agent ของ Bedrock AgentCore เป็น step หนึ่งได้ (managed harness ที่อยู่เบื้องหลังยังเป็น preview)
  • Pattern ที่มัน implement หรือช่วยรองรับ: Saga (Orchestration) โดยมี Step Functions เป็น orchestrator และ Catch state คอยรันแต่ละ Compensating Transaction (step 1 และ 3), Retry with Backoff และ Timeout and Fallback ในรูปของ Retry, TimeoutSeconds และ Catch, Asynchronous Request-Reply เพราะ StartExecution ตอบกลับทันที แล้ว client ก็ poll DescribeExecution หรือรอฟัง event ตอน status เปลี่ยน, Pipes and Filters ในรูปของ Task state ที่ต่อกันเป็นทอด ๆ โดยแต่ละตัวแปลงข้อมูล และ Claim Check สำหรับอะไรก็ตามที่เกิน 256 KiB ส่วน step ของ Express workflow และ step ไหนก็ตามที่ Retry ทำซ้ำ ควร idempotent
  • เพื่อนบ้านที่มักเจอ: API Gateway หรือ Lambda function ที่เริ่ม execution, rule ของ EventBridge และ EventBridge Scheduler ที่เริ่ม execution ตาม schedule แบบ cron หรือ rate, service ที่มันเรียก เช่น Lambda, DynamoDB, SQS, SNS, ECS และ Fargate, AWS Batch, AWS Glue และ S3, CloudWatch และ X-Ray และ execution role ของ IAM ที่ถือ permission ของ state machine
  • Managed offering: Step Functions เองก็คือ managed service อยู่แล้ว ส่วนบริการที่ใกล้เคียงที่สุดในที่อื่นคือ Amazon MWAA ที่รัน Apache Airflow (version ถึง 3.3.1 ณ กันยายน 2026), Temporal Cloud สำหรับ Temporal และใน Lambda เองก็มี Lambda durable functions ส่วน cloud อื่นมี Azure Durable Functions และ Google Cloud Workflows

ใช้ตอนไหนดี

ใช้ Step Functions เมื่อ business process ข้ามหลาย service และหลาย step, ต้องรอดได้ถ้าพังกลางทาง, อาจต้องรอเป็นนาทีไปจนถึงเป็นเดือน และมีคนต้องเห็นว่าแต่ละรอบไปถึงไหนแล้ว มันเหมาะที่สุดกับระบบที่ใช้ AWS เยอะ เพราะ AWS API ส่วนใหญ่อยู่ห่างไปแค่ Task state เดียว ให้มองหาตัวเลือกอื่นเมื่อ logic ของ workflow ซับซ้อนจนอยากได้ภาษาโปรแกรมจริง ๆ เมื่อระบบต้องรันนอก AWS ด้วย สำหรับ batch data pipeline ที่ตั้งเวลาไว้และทีม data ดูแลด้วย Python และสำหรับ loop ถี่ ๆ ของ step เล็ก ๆ ที่ทุก transition เสียทั้งเงินและพื้นที่ใน history

เขียน flow เป็น เก็บความคืบหน้าไว้ที่ไหน การรอนาน ๆ เลือกใช้กับ
AWS Step Functions JSON state machine (ASL) หรือ graph ใน Workflow Studio service เก็บให้: history ของแต่ละ Standard execution ได้ 25,000 event ได้ถึงหนึ่งปี (Standard) และมี task token สำหรับ callback orchestrate AWS service, การอนุมัติ, saga, process ที่ต้อง audit ได้
Temporal โค้ดธรรมดา (Go, Java, Python, TypeScript, .NET และอื่น ๆ) ใช้ license MIT จะ self-host หรือใช้ Temporal Cloud ก็ได้ event history ที่ถูก replay กลับเข้าโค้ดของ workflow ได้ถึง 51,200 event หรือ 50 MB ต่อ execution timer และ signal และ workflow รันได้เป็นปี workflow ที่ซับซ้อน รันนาน และเน้นเขียนเป็นโค้ด บน cloud ไหนก็ได้
Apache Airflow (Amazon MWAA) Python DAG ใช้ Apache License 2.0 metadata database ของการรัน DAG และ task รันตามเวลาหรือตาม event ไม่ใช่ต่อ request data pipeline ที่ตั้งเวลาไว้, ETL และการ train ML
Lambda durable functions โค้ดใน Lambda function ตัวเดียว พร้อม durable execution SDK (JavaScript, TypeScript, Python, Java) checkpoint ที่ถูก replay หลังหยุดพักหรือ crash ได้ถึงหนึ่งปี และการรอไม่เสียค่า compute workflow ที่ส่วนใหญ่เป็นโค้ด Lambda
Lambda function ที่ต่อกันด้วย queue แต่ละ function ส่งงานไปที่ queue ถัดไป ไม่มีที่ไหนเป็นพิเศษ: log และ table ของเราเอง ได้แค่เท่าที่เราสร้างเอง งานสองสาม step ที่ workflow service จะกลายเป็นภาระเกินจำเป็น

ได้อะไร เสียอะไร

  • process มองเห็นได้และทนทาน ทุก Standard execution มี graph และ history ครบ และ platform ดูแล state, retry และการรอ ทำให้ function เล็กอยู่ได้ แต่แลกกับการที่ flow ไปอยู่ใน JSON definition แทนที่จะอยู่ในโค้ด ทำให้การ review, refactor และ type check ทำได้แย่กว่าโปรแกรม ส่วน CDK และเครื่องมือคล้าย ๆ กันก็ generate definition จากโค้ดได้
  • limit เป็นตัวกำหนดการออกแบบ 256 KiB ต่อ input หรือ output, 25,000 event ต่อ Standard history (Inline Map ที่วนบน item หลักพันตัวก็ชนแล้ว), 5 นาทีต่อ Express execution และ definition 1 MB ทำให้ข้อมูลใหญ่ต้องส่งแบบ reference ส่วน loop ยาว ๆ ต้องย้ายไปใช้ Distributed Map หรือ execution ตัวใหม่
  • ค่าใช้จ่ายตามจำนวน step Standard คิดเงินต่อ transition รวม retry ด้วย เลยถูกเมื่อเป็น order สิบ step แต่แพงเมื่อเป็น workflow ที่วน loop บน step เล็ก ๆ จำนวนมาก ตรงนั้น Express ถูกกว่า แต่ต้องยอมเสีย history, .sync, task token และ exactly-once semantics
  • throughput มี quota rate ของการเริ่มและ transition โดน throttle ต่อ account และ Region ด้วย soft limit และ Standard ให้มี execution ที่เปิดอยู่ได้ล้านตัวต่อ Region ถ้า burst ใหญ่มาก ๆ ต้องใช้ Express, batching หรือขอเพิ่ม quota
  • exactly once มีขอบเขต มันครอบคลุมแค่ transition ของ Step Functions เอง ตัว Retry ตั้งใจเรียกซ้ำ ส่วน Lambda function หรือ ECS task ที่ถูก abort ก็อาจยังรันจนจบ และ Express execution แบบ asynchronous ก็อาจรันสองครั้ง เพราะฉะนั้น step ที่แก้ข้อมูลต้องรับมือกับการทำซ้ำได้
  • ใช้ได้แค่บน AWS service นี้รันแค่ใน AWS และ integration ของมันก็เรียก AWS API ส่วน spec ของ ASL เปิดเป็นสาธารณะก็จริง แต่ถ้าจะย้ายออกจาก AWS ก็ยังต้องเขียน workflow ใหม่หรือหาที่ host ใหม่ ในขณะที่ Temporal และ Airflow รันได้ทุกที่

ข้อควรรู้ตอนลงมือทำ

  • ตั้ง timeout ทุกที่: TimeoutSeconds บนทุก Task (ค่าตั้งต้นคือ 99,999,999 วินาที), timeout ของทั้ง state machine ถ้าเหมาะ และ HeartbeatSeconds สำหรับ worker ที่รันนาน
  • retry ให้ถูก error: ใส่ Lambda service error ที่ AWS แนะนำให้ retry (Lambda.ServiceException, Lambda.AWSLambdaException, Lambda.SdkClientException, Lambda.ClientExecutionTimeoutException) ใช้ backoff กับ MaxDelaySeconds และ JitterStrategy และเก็บ States.ALL ไว้ให้ catcher ตัวสุดท้าย
  • ชดเชยให้ชัด: ให้ทุก step ที่เปลี่ยนอะไรสักอย่างมี state สำหรับ undo แล้ว route Catch ไปที่ตัวนั้น และจบที่ Fail state ที่มี Error และ Cause ที่ทีม support เอาไปทำอะไรต่อได้ แต่อย่าใส่ secret และรายละเอียดภายในลงใน Cause
  • ตั้งชื่อ execution ตาม business key เช่น order ID ทำให้ StartExecution ที่ถูก retry เริ่ม order เดิมสองครั้งไม่ได้ (ใน Standard workflow จนถึง 90 วันหลัง execution ปิด)
  • ส่ง reference ไม่ใช่ document: ใช้ S3 key กับอะไรก็ตามที่อาจโตเกิน 256 KiB และใช้ variable แทนการแบกทุกผลลัพธ์ไว้ใน payload
  • ดูขนาด history: ใช้ Distributed Map กับ list ใหญ่ ๆ หรือเริ่ม execution ใหม่เพื่อทำ loop ที่รันนานต่อ ก่อนที่มันจะถึง 25,000 event
  • deploy ด้วย version และ alias นิยาม state machine เป็นโค้ด (CloudFormation, SAM, CDK หรือ Terraform โดยที่ console export template ของ CloudFormation และ SAM ได้) และทดสอบแต่ละ state ด้วย TestState และ mock ก่อน deploy
  • ให้ execution role แค่สิ่งที่มันเรียก เข้ารหัสด้วย customer managed KMS key ถ้า policy บังคับ และ log ที่ระดับ ERROR หรือไม่ใส่ execution data เมื่อ payload มีข้อมูลส่วนบุคคล