Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
9a10495
chore: bump model-engine image tag to latest main (2e9d0078)
lilyz-ai Apr 21, 2026
4332cad
docs: add SEV1 post-mortem for model-engine 500s incident (MLI-6574)
lilyz-ai Apr 29, 2026
de0c354
docs: expand post-mortem with concrete action item plans (MLI-6574)
lilyz-ai Apr 29, 2026
413ea98
docs: add kubectl ban enforcement options and image testing workflow …
lilyz-ai Apr 29, 2026
ab7cee4
docs(post-mortem): clarify terms, fix action items, replace P1/P2 wit…
lilyz-ai May 3, 2026
97fa123
docs(post-mortem): streamline action items and expand rollout strategy
lilyz-ai May 4, 2026
cb1b3a3
docs(post-mortem): fix RBAC location, drop scripts/deploy.sh, expand …
lilyz-ai May 4, 2026
a6a7994
docs(post-mortem): pin actual RBAC files and add real staging effort …
lilyz-ai May 4, 2026
6e084d2
docs(post-mortem): write concrete RBAC diffs and fix staging estimate
lilyz-ai May 4, 2026
5805dbb
docs(post-mortem): fix deploy impact of RBAC change, rename role, tri…
lilyz-ai May 4, 2026
261f6ab
docs(post-mortem): two preventative controls — RBAC + master branch g…
lilyz-ai May 4, 2026
18a5bf0
docs(post-mortem): explain SA token, add dedicated IAM role as elegan…
lilyz-ai May 4, 2026
a3e8338
docs(post-mortem): go with dedicated ml-serving-deployer IAM role (Op…
lilyz-ai May 4, 2026
ebdf6a1
docs(post-mortem): note cross-service impact of scale-deploy RBAC change
lilyz-ai May 4, 2026
5902d3f
docs(post-mortem): identify concrete cross-service impact from kubect…
lilyz-ai May 4, 2026
988bc12
docs(post-mortem): backward-compatible RBAC — additive only, ml_infra…
lilyz-ai May 4, 2026
e1960c7
docs(post-mortem): add Option A vs B comparison table for deploy iden…
lilyz-ai May 4, 2026
636d714
docs(post-mortem): reframe deploy identity choice as open design deci…
lilyz-ai May 4, 2026
25b347b
docs(post-mortem): remove duplicate justfile guard section
lilyz-ai May 4, 2026
26a02b0
docs(post-mortem): address Bill's review comments
lilyz-ai May 5, 2026
dbdb649
docs(post-mortem): switch to Option A; complete breakage audit
lilyz-ai May 5, 2026
46b0d2b
docs(post-mortem): drop Option B, update files-to-change for Option A
lilyz-ai May 5, 2026
d3cf507
docs(post-mortem): add concrete code changes for eks.tf RBAC and aler…
lilyz-ai May 5, 2026
5226e9a
docs(post-mortem): fix deploy identity — both new role and scope-down…
lilyz-ai May 5, 2026
84b5b4c
docs(post-mortem): replace prod_5m with targeted high-error-rate monitor
lilyz-ai May 5, 2026
5056a47
docs(post-mortem): correct genai role claim in audit note
lilyz-ai May 5, 2026
95bb783
docs(post-mortem): drop genai from audit note
lilyz-ai May 5, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion charts/model-engine/values_sample.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ celery_broker_type_redis: null
# - ALL

# tag [required] is the LLM Engine docker image tag
tag: e360bfb1d21d9d4e7b7fcb6b29ca752095b4d0f4
tag: 2e9d00786419ef44ec5c9d3305d8d6451d6aabfb

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Undocumented production image tag change

The PR description mentions only the post-mortem doc addition, but values_sample.yaml is quietly updated to a new image tag (2e9d00786419ef44ec5c9d3305d8d6451d6aabfb). The post-mortem itself notes that values_sample.yaml targets production (no separate staging config exists). Deploying a new image tag here without an explicit mention in the PR description or test plan risks repeating the exact pattern documented in the incident — an image promotion that bypasses scrutiny of whether the accompanying Alembic migration is in place. The test plan should confirm that the migration for this new image has been applied to production before this config lands.

Prompt To Fix With AI
This is a comment left during a code review.
Path: charts/model-engine/values_sample.yaml
Line: 27

Comment:
**Undocumented production image tag change**

The PR description mentions only the post-mortem doc addition, but `values_sample.yaml` is quietly updated to a new image tag (`2e9d00786419ef44ec5c9d3305d8d6451d6aabfb`). The post-mortem itself notes that `values_sample.yaml` targets production (no separate staging config exists). Deploying a new image tag here without an explicit mention in the PR description or test plan risks repeating the exact pattern documented in the incident — an image promotion that bypasses scrutiny of whether the accompanying Alembic migration is in place. The test plan should confirm that the migration for this new image has been applied to production before this config lands.

How can I resolve this? If you propose a fix, please make it concise.

Fix in Cursor Fix in Claude Code Fix in Codex

# context is a user-specified deployment tag. Can be used to
context: production
image:
Expand Down
78 changes: 78 additions & 0 deletions docs/internal/post-mortem-mli-6574-model-engine-500s.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# SEV1 Post-Mortem: model-engine Deployment Caused 500s on All Endpoint Operations

**Incident ID:** MLI-6574
**Severity:** SEV1
**Date:** Apr 24–25, 2026
**Duration:** ~58 min (23:50Z deployment → 00:48Z rollback)
**Customer-facing impact:** ~47 min of 500s (23:50Z – 00:37Z PagerDuty alert; resolved 00:48Z)
Comment thread
greptile-apps[bot] marked this conversation as resolved.
Outdated
**Status:** Resolved

---

## Summary

A `kubectl set image` at 23:50Z on Apr 24 deployed a model-engine image that referenced a new ORM field (`endpoints.temporal_task_queue`) which did not exist in the production database. Every `list_model_endpoints` and `get_model_endpoint` call returned 500, blocking all endpoint deployments and operations for all users. Five patch attempts over ~34 minutes failed to address the root cause before a rollback to the previous image resolved the incident. At least one user missed a project delivery deadline.

---
Comment thread
greptile-apps[bot] marked this conversation as resolved.

## Timeline

| Time (UTC) | Event |
|---|---|
| Feb 27, 05:54Z | Stable image `f395ffa6…` deployed; ran cleanly for ~57 days |
| **Apr 24, 23:50:25Z** | **`kubectl set image` → `04729cef…` (rev 293); ORM references missing `temporal_task_queue` column; all endpoint list/get → 500** |
| 23:55:43Z | `-internal` tag rolled (rev 294); same bug, same 500s |
| Apr 25, 00:09:32Z | `-patch` (rev 296) — does not fix missing column |
| 00:15:29Z | `-patch2` (rev 297) — does not fix missing column |
| 00:20:27Z | `-patch3` (rev 298) — does not fix missing column |
| 00:24:11Z | `-patch4` (rev 299) — does not fix missing column |
| **00:37Z** | **PagerDuty fires; Envoy 5xx ratio = 0.052** |
| **00:48Z** | **Rollback to `f395ffa6…` (rev 300); errors clear; alert auto-resolves** |
| 01:22Z | 0 errors in last 2 min; incident closed |

---

## Root Cause

The ORM model for the `endpoints` table was updated to declare a new column `temporal_task_queue` (added as part of the temporal endpoint type feature, MLI-6425). The corresponding Alembic database migration **was never applied to production** before the new image was rolled out.

When model-engine started, SQLAlchemy attempted to reference `endpoints.temporal_task_queue` in every endpoint query. Because the column did not exist in the live DB, PostgreSQL returned an error on every `list_model_endpoints` and `get_model_endpoint` call, causing universal 500s.

The five patch images deployed during the incident did not address this — they lacked the migration and the ORM field continued to reference the non-existent column.

**Contributing factors:**
- No migration-before-rollout enforcement: the deployment pipeline does not block a rollout if pending Alembic migrations exist.
- No startup schema validation: model-engine does not fail fast on ORM/DB schema drift; errors only surfaced at query time.
- No rollback SLA: 5 patch attempts were made over ~34 minutes before rollback was chosen. The correct fix (rollback) was not prioritized early enough.
- Delayed alerting: PagerDuty did not fire until 00:37Z, ~47 min after the bad deployment.

---

## Impact

| Dimension | Detail |
|---|---|
| Duration of 500s | ~47 min (23:50Z – 00:37Z alert; resolved 00:48Z) |
| Affected operations | All endpoint list, get, create, update, delete |
Comment thread
greptile-apps[bot] marked this conversation as resolved.
| Affected users | All model-engine users |
| Known downstream impact | At least one user missed a project delivery deadline |

---

## Action Items

| # | Action | Owner | Priority |
|---|---|---|---|
| 1 | **Enforce migration-first deployment**: CI/CD pipeline must verify all pending Alembic migrations are applied before new image goes live (or gate rollout on a migration job completing). | Infra/model-engine | High |
| 2 | **Add migration drift detection**: Startup health check that fails fast if ORM schema diverges from live DB schema, preventing silent 500s. | model-engine | High |
| 3 | **Define rollback SLA**: If ≥2 patch attempts fail within 15 minutes, initiate rollback immediately. Document this in the on-call runbook. | On-call/Infra | Medium |
| 4 | **Improve alerting latency**: Envoy 5xx alert threshold and evaluation window should catch this class of incident in <10 min, not 47 min. | Infra | Medium |
| 5 | **Proactive incident communication**: Users mid-deployment should receive Slack/status-page notification during active SEV1s. | On-call/Eng | Medium |

---

## Lessons Learned

- **Schema changes must be deployed atomically with or ahead of the code that depends on them.** A two-phase deploy (migration first, code second) or a migration-gating CI step would have prevented this entirely.
- **Fail fast on startup beats silently failing on every request.** A startup check that validates ORM↔DB schema alignment would have contained the blast radius to a failed rollout rather than a live outage.
- **Five patches in 34 minutes is a sign to stop and rollback.** When the root cause is unclear, reverting to a known-good state is faster and safer than iterating forward.