-
Notifications
You must be signed in to change notification settings - Fork 80
docs: SEV1 post-mortem for model-engine 500s incident (MLI-6574) #817
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
lilyz-ai
wants to merge
27
commits into
main
Choose a base branch
from
lilyz-ai/mli-6574-sev1-post-mortem
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+284
−1
Open
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 4332cad
docs: add SEV1 post-mortem for model-engine 500s incident (MLI-6574)
lilyz-ai de0c354
docs: expand post-mortem with concrete action item plans (MLI-6574)
lilyz-ai 413ea98
docs: add kubectl ban enforcement options and image testing workflow …
lilyz-ai ab7cee4
docs(post-mortem): clarify terms, fix action items, replace P1/P2 wit…
lilyz-ai 97fa123
docs(post-mortem): streamline action items and expand rollout strategy
lilyz-ai cb1b3a3
docs(post-mortem): fix RBAC location, drop scripts/deploy.sh, expand …
lilyz-ai a6a7994
docs(post-mortem): pin actual RBAC files and add real staging effort …
lilyz-ai 6e084d2
docs(post-mortem): write concrete RBAC diffs and fix staging estimate
lilyz-ai 5805dbb
docs(post-mortem): fix deploy impact of RBAC change, rename role, tri…
lilyz-ai 261f6ab
docs(post-mortem): two preventative controls — RBAC + master branch g…
lilyz-ai 18a5bf0
docs(post-mortem): explain SA token, add dedicated IAM role as elegan…
lilyz-ai a3e8338
docs(post-mortem): go with dedicated ml-serving-deployer IAM role (Op…
lilyz-ai ebdf6a1
docs(post-mortem): note cross-service impact of scale-deploy RBAC change
lilyz-ai 5902d3f
docs(post-mortem): identify concrete cross-service impact from kubect…
lilyz-ai 988bc12
docs(post-mortem): backward-compatible RBAC — additive only, ml_infra…
lilyz-ai e1960c7
docs(post-mortem): add Option A vs B comparison table for deploy iden…
lilyz-ai 636d714
docs(post-mortem): reframe deploy identity choice as open design deci…
lilyz-ai 25b347b
docs(post-mortem): remove duplicate justfile guard section
lilyz-ai 26a02b0
docs(post-mortem): address Bill's review comments
lilyz-ai dbdb649
docs(post-mortem): switch to Option A; complete breakage audit
lilyz-ai 46b0d2b
docs(post-mortem): drop Option B, update files-to-change for Option A
lilyz-ai d3cf507
docs(post-mortem): add concrete code changes for eks.tf RBAC and aler…
lilyz-ai 5226e9a
docs(post-mortem): fix deploy identity — both new role and scope-down…
lilyz-ai 84b5b4c
docs(post-mortem): replace prod_5m with targeted high-error-rate monitor
lilyz-ai 5056a47
docs(post-mortem): correct genai role claim in audit note
lilyz-ai 95bb783
docs(post-mortem): drop genai from audit note
lilyz-ai File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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) | ||
|
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. | ||
|
|
||
| --- | ||
|
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 | | ||
|
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. | ||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The PR description mentions only the post-mortem doc addition, but
values_sample.yamlis quietly updated to a new image tag (2e9d00786419ef44ec5c9d3305d8d6451d6aabfb). The post-mortem itself notes thatvalues_sample.yamltargets 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