Skip to content

feat!: implement token-bucket rate limiting per task type and group#82

Merged
deepjoy merged 2 commits into
mainfrom
rate-limiting
Mar 24, 2026
Merged

feat!: implement token-bucket rate limiting per task type and group#82
deepjoy merged 2 commits into
mainfrom
rate-limiting

Conversation

@deepjoy
Copy link
Copy Markdown
Owner

@deepjoy deepjoy commented Mar 24, 2026

Summary

  • Add in-memory token-bucket rate limiting that caps task start rate independently of concurrency, scoped by task type and/or group key
  • Rate limit checks are gate-native (acquire-last in DefaultDispatchGate::admit()) so tokens are never wasted on tasks rejected by earlier checks (backpressure, IO budget, concurrency)
  • Rate-limited tasks get run_after set to the next token availability, preventing head-of-line blocking — other task types dispatch freely while the throttled type waits

Closes #35

Breaking changes

  • DispatchGate::admit() returns Admission enum (Admit / Deny / RateLimited(Instant)) instead of bool — custom gate implementations must update Ok(true)Admission::Admit and Ok(false)Admission::Deny
  • SchedulerSnapshot gains a rate_limits: Vec<RateLimitInfo> field

Public API additions

  • Types: RateLimit (config with per_second, per_minute, with_burst), RateLimitInfo (snapshot), Admission (gate result)
  • Builder: SchedulerBuilder::rate_limit(), group_rate_limit()
  • Runtime: Scheduler::set_rate_limit(), remove_rate_limit(), set_group_rate_limit(), remove_group_rate_limit()
  • Handles: same four methods on DomainHandle and ModuleHandle (auto-prefixed)
  • Store: TaskStore::set_run_after() for deferred requeue
  • Snapshot: SchedulerSnapshot::rate_limits with per-bucket utilization

Files changed

Area Files
Core rate_limit.rs (new), gate.rs, mod.rs, run_loop.rs
API surface builder.rs, control.rs, event.rs, queries.rs
Delegation module.rs, domain.rs, lib.rs
Store store/query/scheduling.rs
Tests tests/integration/rate_limit.rs (new, 9 tests), tests/integration.rs
Docs README.md, configuration.md, priorities-and-preemption.md, glossary.md

deepjoy added 2 commits March 23, 2026 22:44
)

Gate-native rate limiting that caps task start rate independently of
concurrency. Token buckets use f64 fractional tokens for precision and
return the next-available Instant on denial so the run loop can set
run_after to avoid head-of-line blocking.

Breaking: DispatchGate::admit() now returns Admission enum (Admit/Deny/
RateLimited) instead of bool. SchedulerSnapshot gains a rate_limits field.
@deepjoy deepjoy enabled auto-merge (squash) March 24, 2026 06:08
@deepjoy deepjoy merged commit 3d9e158 into main Mar 24, 2026
2 checks passed
@github-actions github-actions Bot mentioned this pull request Mar 24, 2026
@github-actions
Copy link
Copy Markdown
Contributor

Benchmark Comparison

Click to expand
group                                       main                                    pr
-----                                       ----                                    --
backoff_delay/constant                      1.00     44.6±0.29ns 427.5 MElem/sec    1.12     49.8±0.10ns 383.1 MElem/sec
backoff_delay/exponential                   1.00    188.0±1.23ns 101.4 MElem/sec    1.01    189.8±1.18ns 100.5 MElem/sec
backoff_delay/exponential_jitter            1.68    451.3±1.29ns 42.3 MElem/sec     1.00    268.5±1.25ns 71.0 MElem/sec
backoff_delay/linear                        1.00     76.3±0.43ns 249.9 MElem/sec    1.00     76.0±0.27ns 251.0 MElem/sec
batch_submit/1000                           1.01     34.8±3.04ms 28.1 KElem/sec     1.00     34.3±2.83ms 28.5 KElem/sec
byte_progress/byte_reporting_500            1.01    201.5±5.45ms  2.4 KElem/sec     1.00    199.6±4.82ms  2.4 KElem/sec
byte_progress/noop_500                      1.02    196.1±4.78ms  2.5 KElem/sec     1.00    192.4±4.96ms  2.5 KElem/sec
byte_progress_snapshot/100_tasks            1.01     86.2±2.40ms  1160 Elem/sec     1.00     85.0±4.24ms  1177 Elem/sec
concurrency_scaling/1                       1.00    391.3±5.18ms  1277 Elem/sec     1.00    390.3±6.35ms  1280 Elem/sec
concurrency_scaling/2                       1.00    286.3±5.52ms  1746 Elem/sec     1.00    285.4±5.08ms  1751 Elem/sec
concurrency_scaling/4                       1.01    241.9±6.51ms  2.0 KElem/sec     1.00    238.8±7.03ms  2.0 KElem/sec
concurrency_scaling/8                       1.01    194.5±4.16ms  2.5 KElem/sec     1.00    191.7±4.84ms  2.5 KElem/sec
count_by_tags/100                           1.02    133.0±5.27µs  7.3 KElem/sec     1.00    129.9±4.30µs  7.5 KElem/sec
count_by_tags/1000                          1.00    223.1±7.05µs  4.4 KElem/sec     1.00    223.0±6.37µs  4.4 KElem/sec
count_by_tags/5000                          1.02    616.3±9.27µs  1622 Elem/sec     1.00    603.2±8.15µs  1657 Elem/sec
dep_chain_dispatch/10                       1.01     11.6±0.11ms   859 Elem/sec     1.00     11.6±0.13ms   865 Elem/sec
dep_chain_dispatch/25                       1.01     28.6±0.48ms   873 Elem/sec     1.00     28.3±0.79ms   884 Elem/sec
dep_chain_dispatch/50                       1.01     57.4±0.99ms   870 Elem/sec     1.00     56.9±0.90ms   879 Elem/sec
dep_chain_submit/10                         1.02      3.1±0.17ms  3.1 KElem/sec     1.00      3.1±0.15ms  3.2 KElem/sec
dep_chain_submit/200                        1.01     81.0±5.96ms  2.4 KElem/sec     1.00     79.9±5.18ms  2.4 KElem/sec
dep_chain_submit/50                         1.00     17.4±0.92ms  2.8 KElem/sec     1.00     17.4±1.03ms  2.8 KElem/sec
dep_fan_in_dispatch/10                      1.00      6.2±0.11ms  1785 Elem/sec     1.00      6.2±0.12ms  1784 Elem/sec
dep_fan_in_dispatch/100                     1.01     43.2±1.17ms  2.3 KElem/sec     1.00     42.8±0.93ms  2.3 KElem/sec
dep_fan_in_dispatch/50                      1.00     22.6±0.73ms  2.2 KElem/sec     1.00     22.6±0.61ms  2.2 KElem/sec
dispatch_and_complete/1000                  1.02    393.5±6.60ms  2.5 KElem/sec     1.00    385.7±7.28ms  2.5 KElem/sec
dispatch_group_scaling/1                    1.01    447.7±6.94ms  1116 Elem/sec     1.00    442.6±8.14ms  1129 Elem/sec
dispatch_group_scaling/10                   1.01    449.7±7.05ms  1111 Elem/sec     1.00    443.8±7.27ms  1126 Elem/sec
dispatch_group_scaling/100                  1.01    448.9±7.73ms  1113 Elem/sec     1.00    444.2±6.83ms  1125 Elem/sec
dispatch_group_scaling/50                   1.01    449.7±7.77ms  1111 Elem/sec     1.00    444.0±6.82ms  1126 Elem/sec
dispatch_no_groups/500                      1.01    198.0±5.38ms  2.5 KElem/sec     1.00    195.3±4.77ms  2.5 KElem/sec
dispatch_one_group/500                      1.01    447.8±7.36ms  1116 Elem/sec     1.00    445.4±7.32ms  1122 Elem/sec
dispatch_permanent_failure/500              1.01    376.6±5.97ms  1327 Elem/sec     1.00    374.2±5.98ms  1336 Elem/sec
history_by_type/100                         1.00    221.7±6.28µs  4.4 KElem/sec     1.00    222.0±6.74µs  4.4 KElem/sec
history_by_type/1000                        1.05   804.0±47.09µs  1243 Elem/sec     1.00   763.6±48.97µs  1309 Elem/sec
history_by_type/5000                        1.00   809.6±80.12µs  1235 Elem/sec     1.02   823.3±76.59µs  1214 Elem/sec
history_query/100                           1.01   412.4±21.56µs  2.4 KElem/sec     1.00   409.6±17.59µs  2.4 KElem/sec
history_query/1000                          1.04   432.5±25.21µs  2.3 KElem/sec     1.00   416.6±18.68µs  2.3 KElem/sec
history_query/5000                          1.00   425.7±21.01µs  2.3 KElem/sec     1.05   448.6±29.82µs  2.2 KElem/sec
history_stats/100                           1.00    130.6±1.49µs  7.5 KElem/sec     1.01    131.3±1.23µs  7.4 KElem/sec
history_stats/1000                          1.01    197.6±2.11µs  4.9 KElem/sec     1.00    196.0±1.90µs  5.0 KElem/sec
history_stats/5000                          1.00    474.2±2.54µs  2.1 KElem/sec     1.02    484.4±2.84µs  2.0 KElem/sec
mixed_priority_dispatch/500                 1.01    240.7±5.99ms  2.0 KElem/sec     1.00    239.3±6.97ms  2.0 KElem/sec
peek_next/100                               1.03    127.7±5.35µs  7.6 KElem/sec     1.00    123.8±3.79µs  7.9 KElem/sec
peek_next/1000                              1.00    123.5±3.21µs  7.9 KElem/sec     1.02    125.6±4.28µs  7.8 KElem/sec
peek_next/5000                              1.01    125.9±4.97µs  7.8 KElem/sec     1.00    125.0±4.02µs  7.8 KElem/sec
query_ids_by_tags/100                       1.03   191.4±10.52µs  5.1 KElem/sec     1.00    185.8±3.73µs  5.3 KElem/sec
query_ids_by_tags/1000                      1.04   832.2±32.74µs  1201 Elem/sec     1.00    802.2±9.53µs  1246 Elem/sec
query_ids_by_tags/5000                      1.03      3.6±0.07ms   278 Elem/sec     1.00      3.5±0.08ms   286 Elem/sec
retryable_dead_letter/constant              1.02    116.4±0.79ms   858 Elem/sec     1.00    114.2±0.99ms   876 Elem/sec
retryable_dead_letter/exponential           1.02    116.5±1.39ms   858 Elem/sec     1.00    114.6±1.30ms   872 Elem/sec
retryable_dead_letter/exponential_jitter    1.01    115.3±0.72ms   867 Elem/sec     1.00    114.1±1.13ms   876 Elem/sec
retryable_dead_letter/linear                1.02    116.2±1.04ms   860 Elem/sec     1.00    114.5±0.86ms   873 Elem/sec
submit_dedup_hit/1000                       1.01    217.8±8.79ms  4.5 KElem/sec     1.00    214.7±8.45ms  4.5 KElem/sec
submit_tasks/1000                           1.00    188.5±7.33ms  5.2 KElem/sec     1.00    188.0±7.99ms  5.2 KElem/sec
submit_with_tags/0                          1.02     93.7±4.42ms  5.2 KElem/sec     1.00     91.7±4.42ms  5.3 KElem/sec
submit_with_tags/10                         1.01   252.0±14.00ms  1984 Elem/sec     1.00   249.0±15.22ms  2007 Elem/sec
submit_with_tags/20                         1.02   414.5±23.48ms  1206 Elem/sec     1.00   407.2±25.20ms  1227 Elem/sec
submit_with_tags/5                          1.00    172.4±9.29ms  2.8 KElem/sec     1.00   172.9±10.36ms  2.8 KElem/sec
tag_values/100                              1.00    137.4±5.74µs  7.1 KElem/sec     1.00    137.8±5.64µs  7.1 KElem/sec
tag_values/1000                             1.00    199.6±5.63µs  4.9 KElem/sec     1.00    199.0±6.01µs  4.9 KElem/sec
tag_values/5000                             1.00    472.3±7.78µs  2.1 KElem/sec     1.00    470.9±8.77µs  2.1 KElem/sec

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature: Rate limiting per task type / group (token-bucket)

1 participant