seanmuth opened a new pull request, #69985:
URL: https://github.com/apache/airflow/pull/69985

   Closes #69980.
   
   ## What's the problem
   
   Migration `0094_3_2_0_replace_deadline_inline_callback_with_fkey` moves 
inline deadline callbacks into the `callback` table, whose `data` column is 
`ExtendedJSON` — read back at runtime via `BaseSerialization.deserialize`, 
which requires every nested dict to be wrapped as `{"__type": "dict", "__var": 
{...}}`.
   
   Both upgrade paths built `callback.data` without recursively 
extended-serializing the callback `kwargs`:
   
   - **Postgres** (`_upgrade_postgresql`): `json_build_object` wrapped only the 
top level, embedding the old callback's `kwargs` jsonb raw.
   - **MySQL/SQLite** (`_upgrade_mysql_sqlite`): wrote the callback dict via a 
plain `JSON` column with no extended wrapping at all.
   
   So any callback whose `kwargs` contain a nested dict (e.g. metric `tags`) is 
stored unreadable. When the scheduler loads it in the deadline-processing loop, 
`BaseSerialization.deserialize` recurses into the outer `dict` node, hits the 
bare nested object, runs `encoded_var[Encoding.VAR]`, and raises 
`KeyError(<Encoding.VAR: '__var'>)` — crashing the scheduler on every loop 
(CrashLoopBackOff), before it heartbeats, with no OOM and no other logged 
exception.
   
   ## What's in this PR
   
   - Both **upgrade** paths now extended-serialize the callback data, matching 
`BaseSerialization.serialize`:
     - Postgres: a recursive, session-local `pg_temp.encode_extended(jsonb)` 
function used inside the existing batch CTE (keeps the one-round-trip design).
     - MySQL/SQLite: a `_serialize_extended()` helper applied to the callback 
dict.
   - Both **downgrade** paths decode `kwargs` back to raw 
(`pg_temp.decode_extended` / `_deserialize_extended`), so an upgrade→downgrade 
round-trip is preserved. Both decoders are lenient, so they also correctly 
downgrade data written by the pre-fix version of this migration.
   - **Tests** (`test_0094_deadline_callback_migration.py`): existing 
assertions now verify `callback.data` round-trips through the real 
`BaseSerialization`; added a nested-`kwargs`/`tags` regression test and an 
encode/decode helper round-trip test.
   
   ## Already-affected deployments
   
   This hotfix protects deployments that have **not yet** run `0094`. 
Deployments already upgraded across `0094` with nested-dict callback kwargs 
have latent corrupt `callback.data` rows (a scheduler crash that fires when 
such a deadline becomes due); those rows must be repaired out of band — e.g. 
re-encode the affected rows:
   
   ```sql
   -- find affected rows
   SELECT count(*) FROM callback c
   WHERE jsonb_path_exists(c.data::jsonb, '$.**.__var.* ? (@.type()=="object" 
&& !exists(@.__type))');
   ```
   
   ---
   🤖 Opened with [Claude Code](https://claude.com/claude-code) (model: Claude 
Opus 4.8, `claude-opus-4-8[1m]`).
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to