[Limiter] Add RuleBook persistent data model and writer/diff logic#4677
[Limiter] Add RuleBook persistent data model and writer/diff logic#4677tillrohrmann wants to merge 2 commits intorestatedev:mainfrom
Conversation
9af151a to
4d8d0ad
Compare
| /// here as a constant for now — Step 6 makes this configurable via | ||
| /// `worker.rule_book.max_rules`. |
|
@codex review |
| /// Collisions are detectable at admin write time and surface as a 409 from | ||
| /// the create endpoint. |
There was a problem hiding this comment.
But nothing can be done about them if they occur.
|
Codex Review: Didn't find any major issues. 🚀 ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
| # Enables the persistent rule book (RuleBook, PersistedRule, RuleId, | ||
| # diff/apply_change machinery). Implies `bilrost`. | ||
| rule-book = [ | ||
| "bilrost", | ||
| "dep:bytes", | ||
| "dep:generic-array", | ||
| "dep:restate-clock", | ||
| "dep:restate-encoding", | ||
| "dep:serde", | ||
| "dep:xxhash-rust", | ||
| ] |
There was a problem hiding this comment.
This feature is probably an overkill and we should either remove the features or move the rule-book to a different crate. Will become clearer as the user facing API for the rule book materializes.
| } | ||
| /// Appends a u64 value as a padded base62 encoded string to the underlying buffer | ||
| pub(crate) fn push_u64(&mut self, i: u64) { | ||
| pub fn push_u64(&mut self, i: u64) { |
There was a problem hiding this comment.
Used to implement ResourceIds outside of the restate-types crate.
4d8d0ad to
cf8c83b
Compare
| pub disabled: bool, | ||
| /// Last time this rule was modified | ||
| #[bilrost(tag(6))] | ||
| pub last_modified: RoughTimestamp, |
There was a problem hiding this comment.
What's the reasoning behind choosing RoughTimestamp here?
There was a problem hiding this comment.
Primarily to safe a few bytes as I thought that a resolution of seconds for when the rule was last modified is fine enough for all practical purposes.
| #[bilrost(tag(1))] | ||
| version: Version, | ||
| #[bilrost(tag(2), encoding(map<general, general>))] | ||
| rules: HashMap<RuleId, PersistedRule>, |
There was a problem hiding this comment.
One option is to use the string representation of the rule pattern as the key directly and remove it from the value. We can leave it as as a raw ReString or RulePattern` if it's strictly necessary.
There was a problem hiding this comment.
The idea for introducing the RuleId was to have a handle for the REST API to refer to rules when patching them for example. The stringified version of the pattern would also work but it will result in slightly less nice looking URLs:
curl -X PATCH localhost:9070/rules/rul_XYZ
vs.
curl -X PATCH localhost:9070/rules/*/foobar/*
Alternatively, we can send the pattern as part of the request body if specifying it as part of the path is not so nice.
There was a problem hiding this comment.
Another reason was that @slinkydeveloper has concerns about exposing the RulePattern to users as the wildcards can be confusing. We had a discussion whether a higher level language would be better to express the rule patterns https://www.notion.so/restatedev/Concurrency-limits-API-31ebfec2629180e3b091cb72e506b86d?source=copy_link#321bfec2629180719a0fc7f099b22f2d. So far we didn't find a good alternative, though.
There was a problem hiding this comment.
The character * cannot be used in an url, needs to be percent encoded. I suggest the rule goes in the body
Introduces the cluster-global rule book that backs the in-memory `Rules` store of each partition processor's `UserLimiter`. This commit lands the foundational data layer for issue restatedev#4655 (steps 1 and 2 of the plan): * `RuleBook` / `PersistedRule` / `PersistedUserLimits` in `restate-limiter::rule_book`, bilrost-encoded, `Versioned`. The `RuleBook` is keyed by `RuleId` (xxh3-64 of the rule pattern's canonical display form) rendered as `rul_…` resource ids. A deliberate 64-bit hash is used instead of the 128-bit norm for rendered-id brevity; the doc comment captures the trade-off. * Soft-tombstone semantics: `PersistedRule.disabled: bool` defaults to `false` so an active rule is bilrost's empty state and gets omitted from the wire. * Writer logic: `RuleBook::apply_change` for `Create` / `Patch` / `Delete`, with the version-bump contract — create/recreate uses the new book version, runtime-relevant patches advance the per-rule version, reason-only edits bump only `last_modified` and the book version, no-ops bump nothing. Hard cap on total rules (`MAX_RULES_PER_BOOK`) — configurable knob comes later. * `RuleBook::diff`: presence + per-rule version drives `Vec<RuleUpdate>` for the runtime, with `disabled` rules treated as absent. `diff_from_empty` for bootstrap consumers. Supporting infra: * Adds `Rule("rul")` to `IdResourceType`; promotes `restate-types::id_util` and `base62_util` to public modules and `IdEncoder::{new,push_u64,push_u128}` to public so external crates can implement `ResourceId`. * Moves `UserLimits` and `RuleUpdate` from `restate-worker-api` into `restate-limiter` * Adds `generic-array` to the workspace deps. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Introduces the cluster-global rule book that backs the in-memory
Rulesstore of each partition processor'sUserLimiter. This commit lands the foundational data layer for issue #4655:RuleBook/PersistedRule/PersistedUserLimitsinrestate-limiter::rule_book, bilrost-encoded,Versioned. TheRuleBookis keyed byRuleId(xxh3-64 of the rule pattern's canonical display form) rendered asrul_…resource ids. A deliberate 64-bit hash is used instead of the 128-bit norm for rendered-id brevity; the doc comment captures the trade-off.Soft-tombstone semantics:
PersistedRule.disabled: booldefaults tofalseso an active rule is bilrost's empty state and gets omitted from the wire.Writer logic:
RuleBook::apply_changeforCreate/Patch/Delete, with the version-bump contract — create/recreate uses the new book version, runtime-relevant patches advance the per-rule version, reason-only edits bump onlylast_modifiedand the book version, no-ops bump nothing. Hard cap on total rules (MAX_RULES_PER_BOOK) — configurable knob comes later.RuleBook::diff: presence + per-rule version drivesVec<RuleUpdate>for the runtime, withdisabledrules treated as absent.diff_from_emptyfor bootstrap consumers.Supporting infra:
Adds
Rule("rul")toIdResourceType; promotesrestate-types::id_utilandbase62_utilto public modules andIdEncoder::{new,push_u64,push_u128}to public so external crates can implementResourceId.Moves
UserLimitsandRuleUpdatefromrestate-worker-apiintorestate-limiterAdds
generic-arrayto the workspace deps.