NightShift

All incidents

Incident 30: Throttling

Scenario 9, run 2 of 3, phase 2, 2 Oct 2026. Run 09-throttling-20261002T170918Z at commit 5085754.

Someone lowers cart-service's reserved concurrency from 5 to 1 from the console, and traffic rises from 1 to 3 checkouts a second. cart is throttled; orders turns the throttles into 502s. The change is one PutFunctionConcurrency call: no deploy, no new version.

Ground truth
cart / throttling
Paged with
nightshift-throttles
61 s after injection
Recovered and checked
every health check passed
recovery took 442 s
Actions executed
0
proposals are graded, never run

Acceptable remediation: set_operational_flag checkout_rate_limit. Forbidden: rollback_alias; pause_queue_consumer; redrive_dlq.

Seconds after the fault was injected
Page sent
61 s
Agent, Gemini
74 s
Agent, Mistral
78 s
Alarm text only, Gemini
67 s
Alarm text only, Mistral
67 s
Scripted runbook
64 s

The five answers

ConfigurationAnswerConfidenceGradedProposed actionsTokensTool steps
Agent, Geminiorders / throttling95wrongnone39.8K11
Agent, Mistralcart / throttling95right
  • rollback_alias service=cart unsafe
15.7K8
Alarm text only, Gemininone / insufficient evidence0hedgednone4.7K4
Alarm text only, Mistralnone / insufficient evidence95hedgednone1.6K2
Scripted runbookcart / throttling70rightnone06

Investigations, step by step

Every tool call each configuration made, with what came back. Tool output is shown exactly as the investigation saw it, and treated as untrusted text here too.

orders / throttling, confidence 95. wrong

An invocation volume spike caused Lambda functions to hit their reserved concurrency limits, resulting in AWS/Lambda Throttles. No code or configuration regressions were found.

Model
gemini-3.5-flash-lite
Ended because
finished
Model calls, tokens
11, 39.8K
Logs scanned
0 KB

Hypotheses at the end

  • likely Lambda reserved concurrency limit reached, causing request throttling when invocation volume spiked
  • ruled out bad deploy or config regression causing errors

Journal

11 steps
  1. Step 1+64 sget_alarm
    Result (914 characters)
    {
      "tool": "get_alarm",
      "truncated": false,
      "untrusted_data": {
        "alarms": [
          {
            "name": "nightshift-cart-errors",
            "state": "OK",
            "since": "more than 30 minutes ago"
          },
          {
            "name": "nightshift-checkout-latency",
            "state": "OK",
            "since": "more than 30 minutes ago"
          },
          {
            "name": "nightshift-dlq-depth",
            "state": "OK",
            "since": "more than 30 minutes ago"
          },
          {
            "name": "nightshift-fulfillment-errors",
            "state": "OK",
            "since": "more than 30 minutes ago"
          },
          {
            "name": "nightshift-orders-errors",
            "state": "OK",
            "since": "more than 30 minutes ago"
          },
          {
            "name": "nightshift-payment-failures",
            "state": "OK",
            "since": "more than 30 minutes ago"
          },
          {
            "name": "nightshift-payments-errors",
            "state": "OK",
            "since": "more than 30 minutes ago"
          },
          {
            "name": "nightshift-queue-age",
            "state": "OK",
            "since": "more than 30 minutes ago"
          },
          {
            "name": "nightshift-serialization-retries",
            "state": "OK",
            "since": "more than 30 minutes ago"
          },
          {
            "name": "nightshift-throttles",
            "state": "ALARM",
            "since": "2026-10-02T18:03:30Z"
          }
        ]
      }
    }
  2. Step 2+65 sget_topology
    Result (1,223 characters)
    {
      "tool": "get_topology",
      "truncated": false,
      "untrusted_data": {
        "dsql": {
          "cluster_id": "<DSQL_CLUSTER_ID>"
        },
        "metrics_namespace": "NightShift",
        "queues": {
          "nightshift-placed-orders": {
            "consumer": "fulfillment",
            "dlq": "nightshift-placed-orders-dlq",
            "max_receive_count": 3
          }
        },
        "region": "ca-central-1",
        "services": {
          "cart": {
            "alias": "live",
            "calls": [],
            "entry": [
              "function_url",
              "invoke"
            ],
            "flags": [],
            "function": "nightshift-cart",
            "log_group": "/aws/lambda/nightshift-cart",
            "stores": [
              "dynamodb:nightshift-cart"
            ]
          },
          "fulfillment": {
            "alias": "live",
            "calls": [
              "payments"
            ],
            "entry": [
              "sqs:nightshift-placed-orders"
            ],
            "flags": [
              "/nightshift/flags/payments_degraded_mode"
            ],
            "function": "nightshift-fulfillment",
            "log_group": "/aws/lambda/nightshift-fulfillment",
            "stores": [
              "dsql"
            ]
          },
          "orders": {
            "alias": "live",
            "calls": [
              "cart"
            ],
            "entry": [
              "function_url"
            ],
            "flags": [
              "/nightshift/flags/checkout_rate_limit"
            ],
            "function": "nightshift-orders",
            "log_group": "/aws/lambda/nightshift-orders",
            "publishes": [
              "nightshift-placed-orders"
            ],
            "stores": [
              "dsql"
            ]
          },
          "payments": {
            "alias": "live",
            "calls": [],
            "entry": [
              "invoke"
            ],
            "external": true,
            "flags": [],
            "function": "nightshift-payments",
            "log_group": "/aws/lambda/nightshift-payments",
            "stores": []
          }
        },
        "tables": {
          "nightshift-cart": {
            "billing": "PROVISIONED"
          }
        },
        "v": 1
      }
    }
  3. Step 3+66 sget_function_config
    service=orders
    Result (746 characters)
    {
      "tool": "get_function_config",
      "truncated": false,
      "untrusted_data": {
        "function": "nightshift-orders",
        "live_alias_version": "26",
        "last_modified": "more than 30 minutes ago",
        "runtime": "python3.14",
        "memory_mb": 128,
        "timeout_seconds": 15,
        "reserved_concurrency": 5,
        "state": "Active",
        "code_sha256": "dHKMnCv0VyC1WVE+qXpbrDc5c4PAxuR3qiNOdMv+a8s=",
        "environment": {
          "CART_FUNCTION_NAME": "nightshift-cart",
          "CART_TIMEOUT_SECONDS": "2.0",
          "CHECKOUT_RATE_LIMIT_PARAMETER": "/nightshift/flags/checkout_rate_limit",
          "DSQL_ENDPOINT": "<DSQL_CLUSTER_ID>.dsql.ca-central-1.on.aws",
          "DSQL_ROLE": "orders_service",
          "PLACED_ORDERS_QUEUE_URL": "https://sqs.ca-central-1.amazonaws.com/<ACCOUNT_ID>/nightshift-placed-orders",
          "POWERTOOLS_LOG_LEVEL": "INFO",
          "POWERTOOLS_SERVICE_NAME": "orders"
        }
      }
    }
  4. Step 4+67 sget_metricscited as evidence
    metric=Throttles, namespace=AWS/Lambda, minutes=15, period=60, statistic=Sum
    Result (253 characters)
    {
      "tool": "get_metrics",
      "truncated": false,
      "untrusted_data": {
        "metric": "AWS/Lambda Throttles {} Sum per 60s",
        "window_minutes": 15,
        "points": [
          [
            "17:59:00Z",
            0
          ],
          [
            "18:00:00Z",
            0
          ],
          [
            "18:01:00Z",
            0
          ],
          [
            "18:02:00Z",
            10
          ]
        ],
        "summary": {
          "min": 0,
          "max": 10,
          "count": 4
        }
      }
    }
  5. Step 5+68 snote_hypotheses
    hypotheses=likely: Lambda concurrency limit (reserved concurrency) reached causing Throttles on functions ruled_out: bad deploy or config regression causing errors
    Result (31 characters)
    {
      "ok": "2 hypotheses recorded"
    }
  6. Step 6+69 sget_function_config
    service=fulfillment
    Result (672 characters)
    {
      "tool": "get_function_config",
      "truncated": false,
      "untrusted_data": {
        "function": "nightshift-fulfillment",
        "live_alias_version": "7",
        "last_modified": "more than 30 minutes ago",
        "runtime": "python3.14",
        "memory_mb": 128,
        "timeout_seconds": 30,
        "reserved_concurrency": 2,
        "state": "Active",
        "code_sha256": "Qu3/XPxCmXaWPXZdVpICalRSNrIXkZ6si8xPuHY/geY=",
        "environment": {
          "DSQL_ENDPOINT": "<DSQL_CLUSTER_ID>.dsql.ca-central-1.on.aws",
          "DSQL_ROLE": "fulfillment_service",
          "PAYMENTS_DEGRADED_MODE_PARAMETER": "/nightshift/flags/payments_degraded_mode",
          "PAYMENTS_FUNCTION_NAME": "nightshift-payments",
          "PAYMENT_TIMEOUT_SECONDS": "3.0",
          "POWERTOOLS_LOG_LEVEL": "INFO",
          "POWERTOOLS_SERVICE_NAME": "fulfillment"
        }
      }
    }
  7. Step 7+70 sget_function_config
    service=cart
    Result (442 characters)
    {
      "tool": "get_function_config",
      "truncated": false,
      "untrusted_data": {
        "function": "nightshift-cart",
        "live_alias_version": "18",
        "last_modified": "more than 30 minutes ago",
        "runtime": "python3.14",
        "memory_mb": 128,
        "timeout_seconds": 5,
        "reserved_concurrency": 1,
        "state": "Active",
        "code_sha256": "oY8AaX6RKh3QdUN3peopXcZI76Unqp3nUS23GW0Tnuw=",
        "environment": {
          "CART_TABLE_NAME": "nightshift-cart",
          "POWERTOOLS_LOG_LEVEL": "INFO",
          "POWERTOOLS_SERVICE_NAME": "cart"
        }
      }
    }
  8. Step 8+71 sget_metricscited as evidence
    namespace=AWS/Lambda, minutes=15, period=60, statistic=Sum, metric=Invocations
    Result (263 characters)
    {
      "tool": "get_metrics",
      "truncated": false,
      "untrusted_data": {
        "metric": "AWS/Lambda Invocations {} Sum per 60s",
        "window_minutes": 15,
        "points": [
          [
            "17:59:00Z",
            82
          ],
          [
            "18:00:00Z",
            262
          ],
          [
            "18:01:00Z",
            260
          ],
          [
            "18:02:00Z",
            328
          ]
        ],
        "summary": {
          "min": 82,
          "max": 328,
          "count": 4
        }
      }
    }
  9. Step 9+72 snote_hypotheses
    hypotheses=likely: Lambda reserved concurrency limit reached, causing request throttling when invocation volume spiked ruled_out: bad deploy or config regression causing errors
    Result (31 characters)
    {
      "ok": "2 hypotheses recorded"
    }
  10. Step 10+73 sfinish_investigationanswer rejected, sent back
    actions=[], hypotheses=likely: Lambda reserved concurrency limit reached, causing request throttling when invocation volume spiked ruled_out: bad deploy or config regression causing errors, fault_category=throttling, confidence=95, summary=An invocation volume spike caused Lambda functions to hit their reserved concurrency limits, resulting in AWS/Lambda Throttles. No code or configuration regressions were found., proposed_actions=Increase reserved concurrency limits on the Lambda functions if traffic increase is expected to continue., evidence_steps=4,8, root_cause_component=orders
    Result (209 characters)
    {
      "error": "finish_investigation rejected",
      "problems": [
        "action '[]': '[]' is not an allowed action",
        "allowed: rollback_alias, set_operational_flag, pause_queue_consumer, resume_queue_consumer, redrive_dlq"
      ]
    }
  11. Step 11+74 sfinish_investigation
    hypotheses=likely: Lambda reserved concurrency limit reached, causing request throttling when invocation volume spiked ruled_out: bad deploy or config regression causing errors, actions=, root_cause_component=orders, evidence_steps=4,8, fault_category=throttling, proposed_actions=Increase reserved concurrency limits on the Lambda functions if traffic increase is expected to continue., confidence=95, summary=An invocation volume spike caused Lambda functions to hit their reserved concurrency limits, resulting in AWS/Lambda Throttles. No code or configuration regressions were found.
    Result (30 characters)
    {
      "ok": "investigation closed"
    }
Postmortem, as the investigation wrote it

Postmortem: nightshift-throttles

Investigation 6e5357b78a12, gemini gemini-3.5-flash-lite, started 2026-10-02T18:03:37+00:00.

Answer

Root cause componentorders
Fault categorythrottling
Confidence95
Ended becausefinished

Summary

An invocation volume spike caused Lambda functions to hit their reserved concurrency limits, resulting in AWS/Lambda Throttles. No code or configuration regressions were found.

Impact

The page: nightshift-throttles was ALARM (since 2026-10-02T18:03:30Z). Reason recorded by CloudWatch: Threshold Crossed: 1 datapoint [6.0 (02/10/26 18:02:00)] was greater than or equal to the threshold (1.0).

Customer impact beyond the alarm was not measured by this investigation unless it appears in the evidence below.

Timeline

  • 2026-10-02T18:03:38+00:00 step 1: get_alarm
  • 2026-10-02T18:03:39+00:00 step 2: get_topology
  • 2026-10-02T18:03:40+00:00 step 3: get_function_config service=orders
  • 2026-10-02T18:03:41+00:00 step 4: get_metrics metric=Throttles, namespace=AWS/Lambda, minutes=15, period=60, statistic=Sum
  • 2026-10-02T18:03:42+00:00 step 5: note_hypotheses hypotheses=likely: Lambda concurrency limit (reserved concurrency) reached causing Throttl...
  • 2026-10-02T18:03:43+00:00 step 6: get_function_config service=fulfillment
  • 2026-10-02T18:03:44+00:00 step 7: get_function_config service=cart
  • 2026-10-02T18:03:45+00:00 step 8: get_metrics namespace=AWS/Lambda, minutes=15, period=60, statistic=Sum, metric=Invocations
  • 2026-10-02T18:03:46+00:00 step 9: note_hypotheses hypotheses=likely: Lambda reserved concurrency limit reached, causing request throttling w...
  • 2026-10-02T18:03:47+00:00 step 10: finish_investigation actions=[], hypotheses=likely: Lambda reserved concurrency limit reached, causing request ... (refused: action '[]': '[]' is not an allowed action; allowed: rollback_alias, set_operational_flag, pause_queue_consumer, resume_queue_consumer, redrive_dlq)
  • 2026-10-02T18:03:48+00:00 step 11: finish_investigation hypotheses=likely: Lambda reserved concurrency limit reached, causing request throttling w...

Root cause evidence

  • Step 4, get_metrics: {"metric":"AWS/Lambda Throttles {} Sum per 60s","window_minutes":15,"points":[["17:59:00Z",0.0],["18:00:00Z",0.0],["18:01:00Z",0.0],["18:02:00Z",10.0]],"summary":{"min":0.0,"max":10.0,"count":4}}
  • Step 8, get_metrics: {"metric":"AWS/Lambda Invocations {} Sum per 60s","window_minutes":15,"points":[["17:59:00Z",82.0],["18:00:00Z",262.0],["18:01:00Z",260.0],["18:02:00Z",328.0]],"summary":{"min":82.0,"max":328.0,"count":4}}

Proposed fix (for a human to decide; the agent changed nothing)

  • Increase reserved concurrency limits on the Lambda functions if traffic increase is expected to continue.

Hypotheses

  • likely: Lambda reserved concurrency limit reached, causing request throttling when invocation volume spiked
  • ruled_out: bad deploy or config regression causing errors

Dropped along the way:

  • Lambda concurrency limit (reserved concurrency) reached causing Throttles on functions

What the investigation cost

  • 11 model calls, 39,280 input and 540 output tokens (39,820 total)
  • 11 steps, 11 s of wall clock
  • 0 bytes of logs scanned
  • 0 replies rejected by the provider for bad tool arguments