|
| 1 | +# Plan: Update to duroxide 0.1.26 + duroxide-pg-opt 4a6bf6b |
| 2 | + |
| 3 | +## Status: Preparatory |
| 4 | + |
| 5 | +This plan documents the work required to move pg_durable to duroxide 0.1.26 and |
| 6 | +duroxide-pg-opt commit `4a6bf6b`. |
| 7 | + |
| 8 | +**Do not merge the implementation until there is a new duroxide-pg-opt release tag |
| 9 | +(expected: v0.1.24) that includes the `MigrationPolicy` commit `4a6bf6b`.** At that |
| 10 | +point this plan will likely need an update to account for any additional changes to |
| 11 | +duroxide-pg-opt between now and that tag, and possibly additional duroxide versions |
| 12 | +as well. |
| 13 | + |
| 14 | +## Current vs. Target |
| 15 | + |
| 16 | +| Dependency | Current | Target | |
| 17 | +|---|---|---| |
| 18 | +| `duroxide` (crates.io) | `=0.1.20` | `=0.1.26` | |
| 19 | +| `duroxide-pg-opt` (submodule) | `8ce65bd` (topic branch, based off ~v0.1.18) | `4a6bf6b` (origin/main, one commit past v0.1.23) | |
| 20 | + |
| 21 | +The current submodule commit (`8ce65bd`) is the `MigrationPolicy` feature branch |
| 22 | +that adds `MigrationPolicy::VerifyOnly`. The target commit (`4a6bf6b`) is the |
| 23 | +mainline commit on `origin/main` that merged the same feature — so `VerifyOnly` |
| 24 | +is already present in pg_durable and nothing about that support changes. |
| 25 | + |
| 26 | +**Note (commit 397fb93):** pg_durable no longer checks in duroxide migration files. |
| 27 | +The BGW now applies all duroxide migrations at startup via `MigrationPolicy::ApplyAll`; |
| 28 | +the backend connection uses `VerifyOnly`. `sql/duroxide_install.sql`, |
| 29 | +`sql/duroxide_upstream/`, and the scripts `gen-duroxide-install-sql.sh` and |
| 30 | +`verify-duroxide-migrations.sh` were deleted in that commit. Implementation steps |
| 31 | +that previously synced those files are therefore eliminated (see checklist below). |
| 32 | + |
| 33 | +## What Changed in duroxide (0.1.20 → 0.1.26) |
| 34 | + |
| 35 | +### 0.1.21 — Orphan queue message drop (no impact) |
| 36 | + |
| 37 | +Drops `QueueMessage` work items that arrive before orchestration starts. Internal |
| 38 | +provider-level fix; no pg_durable code changes needed. |
| 39 | + |
| 40 | +### 0.1.22 — Activity Tags |
| 41 | + |
| 42 | +New `TagFilter` enum, `RuntimeOptions.worker_tag_filter`, and |
| 43 | +`DurableFuture::with_tag()`. The `Provider::fetch_work_item()` signature gains a |
| 44 | +`tag_filter: &TagFilter` parameter — a breaking change for provider implementors, |
| 45 | +fully absorbed by duroxide-pg-opt 0.1.20+. The default is `TagFilter::DefaultOnly` |
| 46 | +(process only untagged activities), so all existing `schedule_activity` calls are |
| 47 | +completely unaffected. No pg_durable source changes required. |
| 48 | + |
| 49 | +### 0.1.23 — Bug fixes and docs only (no impact) |
| 50 | + |
| 51 | +### 0.1.24 — Durable KV Store |
| 52 | + |
| 53 | +New `ctx.set_value()` / `ctx.get_value()` orchestration API and a new |
| 54 | +`Provider::get_kv_value()` trait method. pg_durable does not use the KV API. |
| 55 | +Provider-level changes absorbed by duroxide-pg-opt 0.1.21+. |
| 56 | + |
| 57 | +### 0.1.25 — KV API Rename + Bulk Reads (no pg_durable impact) |
| 58 | + |
| 59 | +`ctx.set_value()` → `ctx.set_kv_value()` etc. New required Provider method |
| 60 | +`get_kv_all_values()`. `OrchestrationItem.kv_snapshot` type changes to |
| 61 | +`HashMap<String, KvEntry>`. All absorbed by duroxide-pg-opt. |
| 62 | + |
| 63 | +### 0.1.26 — KV Delta Table (fixes RMW replay poisoning) |
| 64 | + |
| 65 | +KV mutations during the current execution now stage to a `kv_delta` table; only |
| 66 | +merged to `kv_store` at execution boundaries (Completed/CAN/Failed). Fixes |
| 67 | +nondeterminism errors in read-modify-write patterns. Provider-and-migration-only |
| 68 | +change, fully absorbed by duroxide-pg-opt. |
| 69 | + |
| 70 | +**Net impact on pg_durable Rust source code: zero required changes.** |
| 71 | + |
| 72 | +## What Changed in duroxide-pg-opt (8ce65bd → 4a6bf6b) |
| 73 | + |
| 74 | +The target includes duroxide-pg-opt versions v0.1.19–v0.1.23 plus the |
| 75 | +`MigrationPolicy` PR merged on top. |
| 76 | + |
| 77 | +### New migrations (all additive/safe for rolling upgrades) |
| 78 | + |
| 79 | +| Migration | Feature | Schema Changes | |
| 80 | +|---|---|---| |
| 81 | +| 0006 | Activity tag routing | `worker_queue.tag TEXT` column + index; updated `enqueue_worker_work` and `fetch_work_item` SPs | |
| 82 | +| 0007 | Retry on fetch contention | `fetch_orchestration_item` SP body change only (no schema change) | |
| 83 | +| 0008 | KV store | New `kv_store` table; updated `fetch_orchestration_item`, `ack_orchestration_item`, deletion/pruning SPs | |
| 84 | +| 0009 | KV timestamps | `kv_store.last_updated_at_ms BIGINT` column; updated KV materialization SPs | |
| 85 | +| 0010 | KV delta table | New `kv_delta` table; two-table KV write model; delta→store merge on terminal transition | |
| 86 | + |
| 87 | +### API stability |
| 88 | + |
| 89 | +All pg_durable call sites against duroxide-pg-opt remain valid: |
| 90 | + |
| 91 | +- `PostgresProvider::new_with_config(url, config)` — unchanged |
| 92 | +- `ProviderConfig { schema_name, migration_policy, long_poll }` — unchanged |
| 93 | +- `MigrationPolicy::ApplyAll` (BGW) and `MigrationPolicy::VerifyOnly` (backend) — both present and in use |
| 94 | +- **New at `4a6bf6b`**: `schema_name` values are now validated against |
| 95 | + `[A-Za-z_][A-Za-z0-9_]*` on construction. `"duroxide"` passes this check. |
| 96 | + |
| 97 | +## Implementation Checklist |
| 98 | + |
| 99 | +### 1. Update submodule pointer |
| 100 | + |
| 101 | +```bash |
| 102 | +cd duroxide-pg-opt |
| 103 | +git fetch origin |
| 104 | +git checkout 4a6bf6b |
| 105 | +cd .. |
| 106 | +``` |
| 107 | + |
| 108 | +### 2. Update `Cargo.toml` |
| 109 | + |
| 110 | +```toml |
| 111 | +# Change: |
| 112 | +duroxide = "=0.1.20" |
| 113 | +# To: |
| 114 | +duroxide = "=0.1.26" |
| 115 | +``` |
| 116 | + |
| 117 | +~~### 3. Sync migrations to `sql/duroxide_upstream/`~~ **Eliminated by commit 397fb93.** |
| 118 | + |
| 119 | +~~### 4. Regenerate install SQL~~ **Eliminated by commit 397fb93.** |
| 120 | + |
| 121 | +Because the BGW now applies all duroxide migrations via `MigrationPolicy::ApplyAll`, |
| 122 | +pg_durable no longer needs to copy migration files or maintain `sql/duroxide_install.sql`. |
| 123 | +The extension SQL only creates an empty `duroxide` schema; the BGW populates it at runtime. |
| 124 | + |
| 125 | +### 3. Update the pg_durable upgrade script |
| 126 | + |
| 127 | +v0.2.0 has not yet shipped, so edit the existing `sql/pg_durable--0.1.1--0.2.0.sql` |
| 128 | +upgrade script. **Do not add the duroxide migrations to the upgrade script** — the BGW |
| 129 | +will apply any missing migrations (0006–0010) via `ApplyAll` at startup. |
| 130 | +The only df-schema DDL needed is the `df.is_ready()` function (already added by |
| 131 | +commit 397fb93). |
| 132 | + |
| 133 | +### 4. Update `docs/upgrade-testing.md` |
| 134 | + |
| 135 | +Add a "Version-Specific Changes" entry for the new version covering: |
| 136 | + |
| 137 | +- **Scenario A**: The `df` schema equivalent contract holds; the `duroxide` schema |
| 138 | + is explicitly excluded from snapshot diffs because fresh installs start with an |
| 139 | + empty `duroxide` schema (BGW fills it in at runtime) while upgrades carry forward |
| 140 | + the fully-populated schema from v0.1.1. This divergence is expected and documented. |
| 141 | +- **Scenario B1 (binary backward compat)**: The BGW uses `MigrationPolicy::ApplyAll`. |
| 142 | + A database with only migrations 0001–0005 is handled gracefully — the BGW applies |
| 143 | + the missing migrations at startup rather than refusing to initialize. |
| 144 | +- **Scenario B2**: No new pg_durable DDL in this release (all new tables are |
| 145 | + duroxide-owned). Existing `df.*` data survives upgrade unchanged. |
| 146 | + |
| 147 | +(Note: the BGW-applies-migrations entry was already added to `docs/upgrade-testing.md` |
| 148 | +by commit 397fb93; this step is to document any remaining version-specific notes.) |
| 149 | + |
| 150 | +### 5. Run `cargo update` |
| 151 | + |
| 152 | +```bash |
| 153 | +cargo update |
| 154 | +``` |
| 155 | + |
| 156 | +This refreshes all transitive dependencies, not just duroxide and duroxide-pg-opt. |
| 157 | + |
| 158 | +### 6. Build and test |
| 159 | + |
| 160 | +```bash |
| 161 | +cargo build --features pg17 |
| 162 | +cargo clippy --features pg17 |
| 163 | +./scripts/test-unit.sh |
| 164 | +./scripts/test-e2e-local.sh |
| 165 | +./scripts/test-upgrade.sh |
| 166 | +``` |
| 167 | + |
| 168 | +## Upgrade & Migration Impact |
| 169 | + |
| 170 | +### B1 — Binary Backward Compatibility |
| 171 | + |
| 172 | +The BGW uses `MigrationPolicy::ApplyAll`, so it will apply any missing duroxide |
| 173 | +migrations (0006–0010) at startup rather than refusing to initialize. A database |
| 174 | +that still has only migrations 0001–0005 is safe: the BGW detects the gap and |
| 175 | +applies the remaining migrations in order. The backend uses `MigrationPolicy::VerifyOnly` |
| 176 | +and will wait (via `df.is_ready()` polling) until the BGW has completed all |
| 177 | +migrations before accepting work. |
| 178 | + |
| 179 | +### B2 — Data Compatibility After Upgrade |
| 180 | + |
| 181 | +All five new migrations are additive (new tables, new columns with defaults or |
| 182 | +nullable). Existing `df.vars`, `df.nodes`, `df.instances`, and `df.graphs` data |
| 183 | +is untouched. No data migration is required. |
| 184 | + |
| 185 | +## Opportunities for pg_durable Improvements |
| 186 | + |
| 187 | +These are not required for this update but become available once the dependency |
| 188 | +versions are bumped. Consider as follow-on work: |
| 189 | + |
| 190 | +1. **Activity Tags** (duroxide 0.1.22): Expose a `tag` parameter on `df.sql()`, |
| 191 | + `df.http()`, etc. to route activities to specialized worker pools. Workers |
| 192 | + would declare their `TagFilter` via a GUC or `pg_durable.worker_tag` setting. |
| 193 | + |
| 194 | +2. **Durable KV Store** (duroxide 0.1.24): Expose `df.kv_set(key, value)` and |
| 195 | + `df.kv_get(key)` as first-class DSL nodes, allowing orchestrations to accumulate |
| 196 | + state durably without a side table pattern. Useful for loop counter and |
| 197 | + accumulator patterns. |
| 198 | + |
| 199 | +3. **Custom Status** (duroxide 0.1.19): Enrich `df.status()` to surface a custom |
| 200 | + status string published from within an orchestration, enabling richer progress |
| 201 | + reporting beyond the Pending/Running/Completed/Failed states. |
0 commit comments