dispatch_policy 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +98 -28
- data/MIT-LICENSE +16 -17
- data/README.md +452 -388
- data/app/assets/images/dispatch_policy/logo-large.svg +9 -0
- data/app/assets/images/dispatch_policy/logo-small.svg +7 -0
- data/app/assets/javascripts/dispatch_policy/turbo.es2017-umd.min.js +35 -0
- data/app/assets/stylesheets/dispatch_policy/application.css +294 -0
- data/app/controllers/dispatch_policy/application_controller.rb +45 -1
- data/app/controllers/dispatch_policy/assets_controller.rb +31 -0
- data/app/controllers/dispatch_policy/dashboard_controller.rb +91 -0
- data/app/controllers/dispatch_policy/partitions_controller.rb +122 -0
- data/app/controllers/dispatch_policy/policies_controller.rb +94 -267
- data/app/controllers/dispatch_policy/staged_jobs_controller.rb +9 -0
- data/app/models/dispatch_policy/adaptive_concurrency_stats.rb +11 -81
- data/app/models/dispatch_policy/inflight_job.rb +12 -0
- data/app/models/dispatch_policy/partition.rb +21 -0
- data/app/models/dispatch_policy/staged_job.rb +4 -97
- data/app/models/dispatch_policy/tick_sample.rb +11 -0
- data/app/views/dispatch_policy/dashboard/index.html.erb +109 -0
- data/app/views/dispatch_policy/partitions/index.html.erb +63 -0
- data/app/views/dispatch_policy/partitions/show.html.erb +106 -0
- data/app/views/dispatch_policy/policies/index.html.erb +15 -37
- data/app/views/dispatch_policy/policies/show.html.erb +139 -223
- data/app/views/dispatch_policy/shared/_capacity.html.erb +67 -0
- data/app/views/dispatch_policy/shared/_hints.html.erb +13 -0
- data/app/views/dispatch_policy/shared/_partition_row.html.erb +12 -0
- data/app/views/dispatch_policy/staged_jobs/show.html.erb +31 -0
- data/app/views/layouts/dispatch_policy/application.html.erb +164 -231
- data/config/routes.rb +21 -2
- data/db/migrate/20260501000001_create_dispatch_policy_tables.rb +103 -0
- data/lib/dispatch_policy/assets.rb +38 -0
- data/lib/dispatch_policy/bypass.rb +23 -0
- data/lib/dispatch_policy/config.rb +85 -0
- data/lib/dispatch_policy/context.rb +50 -0
- data/lib/dispatch_policy/cursor_pagination.rb +121 -0
- data/lib/dispatch_policy/decision.rb +22 -0
- data/lib/dispatch_policy/engine.rb +5 -27
- data/lib/dispatch_policy/forwarder.rb +63 -0
- data/lib/dispatch_policy/gate.rb +10 -38
- data/lib/dispatch_policy/gates/adaptive_concurrency.rb +99 -97
- data/lib/dispatch_policy/gates/concurrency.rb +45 -26
- data/lib/dispatch_policy/gates/throttle.rb +65 -41
- data/lib/dispatch_policy/inflight_tracker.rb +174 -0
- data/lib/dispatch_policy/job_extension.rb +155 -0
- data/lib/dispatch_policy/operator_hints.rb +126 -0
- data/lib/dispatch_policy/pipeline.rb +48 -0
- data/lib/dispatch_policy/policy.rb +61 -59
- data/lib/dispatch_policy/policy_dsl.rb +120 -0
- data/lib/dispatch_policy/railtie.rb +35 -0
- data/lib/dispatch_policy/registry.rb +46 -0
- data/lib/dispatch_policy/repository.rb +723 -0
- data/lib/dispatch_policy/serializer.rb +36 -0
- data/lib/dispatch_policy/tick.rb +260 -256
- data/lib/dispatch_policy/tick_loop.rb +59 -26
- data/lib/dispatch_policy/version.rb +1 -1
- data/lib/dispatch_policy.rb +72 -52
- data/lib/generators/dispatch_policy/install/install_generator.rb +70 -0
- data/lib/generators/dispatch_policy/install/templates/create_dispatch_policy_tables.rb.tt +95 -0
- data/lib/generators/dispatch_policy/install/templates/dispatch_tick_loop_job.rb.tt +53 -0
- data/lib/generators/dispatch_policy/install/templates/initializer.rb.tt +11 -0
- metadata +134 -42
- data/app/models/dispatch_policy/partition_inflight_count.rb +0 -42
- data/app/models/dispatch_policy/partition_observation.rb +0 -76
- data/app/models/dispatch_policy/throttle_bucket.rb +0 -41
- data/db/migrate/20260424000001_create_dispatch_policy_tables.rb +0 -80
- data/db/migrate/20260424000002_create_adaptive_concurrency_stats.rb +0 -22
- data/db/migrate/20260424000003_create_adaptive_concurrency_samples.rb +0 -25
- data/db/migrate/20260424000004_rename_samples_to_partition_observations.rb +0 -32
- data/db/migrate/20260425000001_add_duration_to_partition_observations.rb +0 -8
- data/lib/dispatch_policy/active_job_perform_all_later_patch.rb +0 -32
- data/lib/dispatch_policy/dispatch_context.rb +0 -53
- data/lib/dispatch_policy/dispatchable.rb +0 -123
- data/lib/dispatch_policy/gates/fair_interleave.rb +0 -32
- data/lib/dispatch_policy/gates/global_cap.rb +0 -26
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 24ab8c2fe85abc57507f84edc955c8263f59a96505522ecd9ceb6ce60e14bcba
|
|
4
|
+
data.tar.gz: 152dc560f5b1169d5ef6f4a27065ae629da426ceefb0725fa6bc7a8d13c62a3f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 88f5adb73f3e7bb1893eab32e098a5dda1d7b147c0cd000ee35eb6771b2d2c21f0fe1541c16725b537a1e3a1121166b2681331e7fcea79de26e3d3926137bea0
|
|
7
|
+
data.tar.gz: c2b17c50ccd765c95bb6d316a68f3e7dece5134ae9ca26fcb918356e0baf611ebbfba19f695e41b1b7402f0021f0b9d3d08111c7938448efb9fb5accda0a4131
|
data/CHANGELOG.md
CHANGED
|
@@ -1,43 +1,113 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## 0.
|
|
3
|
+
## 0.3.0
|
|
4
4
|
|
|
5
5
|
### Added
|
|
6
|
-
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
-
|
|
12
|
-
(
|
|
13
|
-
|
|
6
|
+
- TX-atomic admission: the DELETE on `staged_jobs`, the pre-INSERT
|
|
7
|
+
in `inflight_jobs` and the adapter handoff (`good_job` /
|
|
8
|
+
`solid_queue`) all run inside the same transaction, so any failure
|
|
9
|
+
rolls everything back with no loss window between admission COMMIT
|
|
10
|
+
and adapter enqueue.
|
|
11
|
+
- `:adaptive_concurrency` gate that auto-tunes per-partition
|
|
12
|
+
`current_max` via AIMD against an EWMA of `queue_lag` (time from
|
|
13
|
+
admission to perform start), with a safety valve that floors
|
|
14
|
+
`remaining` at `initial_max` when `in_flight == 0` so idle
|
|
15
|
+
partitions can recover after a shrink.
|
|
16
|
+
- In-tick fairness layer: claimed partitions are reordered by
|
|
17
|
+
`decayed_admits` (EWMA, default `half_life = 60s`) and capped by
|
|
18
|
+
`fair_share = ceil(tick_cap / N)`. Composes with
|
|
19
|
+
`:adaptive_concurrency` — fairness writes
|
|
20
|
+
`partitions.decayed_admits`, adaptive writes
|
|
21
|
+
`dispatch_policy_adaptive_concurrency_stats.current_max`, no
|
|
22
|
+
shared locks.
|
|
23
|
+
- `shard_by` to split a policy's partitions across parallel tick
|
|
24
|
+
loops; the shard is pinned on first write so partitions don't jump
|
|
25
|
+
between tick workers.
|
|
26
|
+
- Policy-level `partition_by`: a single canonical scope shared by
|
|
27
|
+
the staged job's `partition_key` and the concurrency gate's
|
|
28
|
+
`inflight_partition_key`, so no gate suffers scope dilution.
|
|
29
|
+
- Gates are no longer required — a policy with `partition_by` and
|
|
30
|
+
no gates is valid and still benefits from in-tick fairness.
|
|
31
|
+
- `dispatch_policy_inflight_jobs` is populated for every admitted
|
|
32
|
+
job (not only concurrency-gated ones), with a heartbeat thread
|
|
33
|
+
refreshing `heartbeat_at` during perform.
|
|
34
|
+
- Bulk handoff via `ActiveJob.perform_all_later` and bulk-flush of
|
|
35
|
+
deny-path partition state at the end of a tick (single
|
|
36
|
+
`UPDATE…FROM(VALUES…)` instead of N per-partition statements).
|
|
37
|
+
- Per-tick metrics layer (`dispatch_policy_tick_samples`) feeding
|
|
38
|
+
the admin UI: throughput, P50/P95 round-trip ages, capacity
|
|
39
|
+
headroom, pending trend, fail %, and operator hints.
|
|
40
|
+
- Admin UI improvements: cursor-based pagination of `/partitions`,
|
|
41
|
+
sort + only-pending filter, auto-refresh control (off / 2s / 5s /
|
|
42
|
+
10s) via Turbo Drive, per-partition and per-policy Drain action,
|
|
43
|
+
redesigned dummy demo page with cards + storm controls.
|
|
44
|
+
- `config.enabled` master switch for cutovers.
|
|
45
|
+
- `TickLoop` `busy_pause` to throttle busy iterations.
|
|
46
|
+
- `bin/release` wrapper around `rake release`.
|
|
47
|
+
- Manual benchmark suite, plus a real-adapter end-to-end bench
|
|
48
|
+
covering `good_job` and `solid_queue`.
|
|
14
49
|
|
|
15
50
|
### Changed
|
|
16
|
-
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
51
|
+
- **Breaking:** `partition_by` is policy-level only. Per-gate
|
|
52
|
+
`partition_by:` was removed; if omitted, `Policy#validate!` raises
|
|
53
|
+
`InvalidPolicy: partition_by required`. For different per-gate
|
|
54
|
+
scopes, use separate policies.
|
|
55
|
+
- `partitions.context` is refreshed on every `perform_later` via
|
|
56
|
+
UPSERT, so changes in the host DB take effect on the next enqueue
|
|
57
|
+
without redeploys. Gates read this ctx, not the historical
|
|
58
|
+
`staged_jobs.context`.
|
|
59
|
+
- Tick-claim ordering kept at `last_checked_at NULLS FIRST, id`
|
|
60
|
+
(anti-stagnation): each partition with pending is processed every
|
|
61
|
+
⌈N/B⌉ ticks. Fairness reorder happens after the claim, in memory.
|
|
62
|
+
- Non-PG adapters now warn at boot (`warn_unsupported_adapter`)
|
|
63
|
+
instead of hard-failing — a custom PG-backed adapter still works.
|
|
64
|
+
- `config.database_role` lets the admission TX target a specific
|
|
65
|
+
Rails multi-DB role (e.g. `solid_queue` on a separate DB).
|
|
20
66
|
|
|
21
67
|
### Fixed
|
|
22
|
-
-
|
|
23
|
-
to
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
68
|
+
- `BulkEnqueue.perform_all_later` checks `Bypass.active?` and
|
|
69
|
+
delegates to `super` when active, breaking an infinite re-staging
|
|
70
|
+
loop on the deserialize + `perform_all_later` path under Bypass.
|
|
71
|
+
- `JobExtension.ensure_arguments_materialized!` is called before
|
|
72
|
+
reading `job.arguments` in both single and bulk paths — previously
|
|
73
|
+
the public `arguments` getter returned `[]` for deserialised jobs
|
|
74
|
+
until `perform_now` triggered private materialization, so the
|
|
75
|
+
context proc fell back to its defaults.
|
|
76
|
+
- `:adaptive_concurrency` updates `current_max` in a single SQL
|
|
77
|
+
statement that uses the post-update `ewma_latency_ms` value in
|
|
78
|
+
its CASE expression, removing read-modify-write races between
|
|
79
|
+
concurrent workers.
|
|
80
|
+
- Adaptive's feedback signal is measured in `InflightTracker.track`
|
|
81
|
+
before `block.call` so perform duration doesn't pollute the
|
|
82
|
+
`queue_lag` signal.
|
|
83
|
+
- Heartbeat thread refreshes `inflight_jobs.heartbeat_at` during
|
|
84
|
+
perform so long-running jobs aren't reaped as stale.
|
|
85
|
+
- Deny-only ticks persist `next_eligible_at`.
|
|
86
|
+
- Tick samples query no longer depends on `date_bin` (works on
|
|
87
|
+
Postgres 13).
|
|
88
|
+
- Admin UI preserves scroll position on auto-refresh, and skips
|
|
89
|
+
auto-refresh while a Turbo visit is in flight.
|
|
90
|
+
- P95/P50 round-trip ages were inverted in the metrics view.
|
|
91
|
+
- Railtie no longer auto-merges the gem's `db/migrate` into the
|
|
92
|
+
host's paths.
|
|
93
|
+
- "Pending is growing" hint silenced when the backlog has drained.
|
|
29
94
|
|
|
30
95
|
### Removed
|
|
31
|
-
-
|
|
32
|
-
|
|
96
|
+
- Per-gate `partition_by:` declarations (see Changed).
|
|
97
|
+
- Denormalised `partitions.in_flight_count` counter — `inflight_jobs`
|
|
98
|
+
is the source of truth.
|
|
99
|
+
- `unclaim!` / `preinserted_inflight_ids` — TX rollback covers the
|
|
100
|
+
failure case.
|
|
33
101
|
|
|
34
102
|
## 0.1.0
|
|
35
103
|
|
|
36
104
|
Initial release.
|
|
37
105
|
|
|
38
|
-
- Rails engine + ActiveJob integration
|
|
39
|
-
|
|
40
|
-
-
|
|
41
|
-
-
|
|
42
|
-
|
|
43
|
-
-
|
|
106
|
+
- Rails engine + ActiveJob integration intercepting `perform_later`
|
|
107
|
+
via `JobExtension`.
|
|
108
|
+
- Gates: `:throttle`, `:concurrency`.
|
|
109
|
+
- Staged jobs admitted by a periodic tick loop, per-partition
|
|
110
|
+
counters, and token-bucket throttle state.
|
|
111
|
+
- Admin UI showing partitions, pending counts, and recent ticks.
|
|
112
|
+
- PostgreSQL required (uses `FOR UPDATE SKIP LOCKED`, `ON CONFLICT`,
|
|
113
|
+
and `jsonb`).
|
data/MIT-LICENSE
CHANGED
|
@@ -1,21 +1,20 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
1
|
Copyright (c) 2026 José Galisteo
|
|
4
2
|
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining
|
|
6
|
-
of this software and associated documentation files (the
|
|
7
|
-
in the Software without restriction, including
|
|
8
|
-
to use, copy, modify, merge, publish,
|
|
9
|
-
copies of the Software, and to
|
|
10
|
-
furnished to do so, subject to
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
4
|
+
a copy of this software and associated documentation files (the
|
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
9
|
+
the following conditions:
|
|
11
10
|
|
|
12
|
-
The above copyright notice and this permission notice shall be
|
|
13
|
-
copies or substantial portions of the Software.
|
|
11
|
+
The above copyright notice and this permission notice shall be
|
|
12
|
+
included in all copies or substantial portions of the Software.
|
|
14
13
|
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
|
20
|
-
|
|
21
|
-
SOFTWARE.
|
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
17
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
|
18
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
|
19
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
|
20
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|