Skip to content

Commit 863dea6

Browse files
cardmagicclaude
andcommitted
docs: point scaling limits at Pro, not at this roadmap
`fit.md` told a reader that a request-path rate limiter is a poor actor and sold the shape that answers it, while `roadmap.md` promised "distributed rate limits, global admission hooks, and cache-capacity eviction" in this gem. The two documents gave opposite advice about one use case, and the roadmap gave a reader a reason to wait for something this gem should not build. Each of the three is hot, request-critical, and loss-tolerant, and every invocation here writes one permanent message row, so the cost model in `fit.md` already rules out checking on the request path. They are answered by Solid Objects Pro's grouped and ephemeral operations, and `fit.md`, `roadmap.md`, and `architecture.md` now say so in the same words. Turbo append intents stay an open milestone. They cost nothing at high QPS, the reactive layer implements every other verb, and the renderer already emits `turbo-stream action="append"` for batch refreshes and payload delivery, so what remains is letting an application direct one. The README listed "Rate limits and account quotas" among the fits. The low-rate quota is the case that fits, so it reads that way now. Validation: bundle exec rake (795 runs, 0 failures). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent 3111ddc commit 863dea6

4 files changed

Lines changed: 21 additions & 11 deletions

File tree

‎README.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ Solid Object Rails Actors elegantly fit anything where one identifiable thing mu
1616
- Ticket holds and reservations
1717
- Multiplayer games and shared rooms
1818
- Shopping carts and checkout recovery
19-
- Rate limits and account quotas
19+
- Low-rate quotas and account limits
2020
- Session expiration
2121
- Job leases and workflows
2222
- Connected devices

‎docs/architecture.md‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ through the client.
7272

7373
### Client and mailbox
7474

75-
The client finds or creates the actor instance and atomically allocates a sequence. It inserts one durable message-history row and one ready-membership row. It validates operations and JSON payloads before writing and enforces idempotency-key uniqueness, payload limits, and the per-actor mailbox cap. It also authorizes and coordinates actor destruction. Distributed rate limiting and global admission control are not implemented.
75+
The client finds or creates the actor instance and atomically allocates a sequence. It inserts one durable message-history row and one ready-membership row. It validates operations and JSON payloads before writing and enforces idempotency-key uniqueness, payload limits, and the per-actor mailbox cap. It also authorizes and coordinates actor destruction. Distributed rate limiting and global admission control are not implemented and are not planned here.
7676

7777
Message execution state is table membership, not a status column. The durable message remains for results, retention, and diagnostics. Only live work occupies `ready_messages` or `claimed_messages`, so completed history cannot inflate the polling index.
7878

@@ -781,11 +781,11 @@ Enqueue counts unfinished rows under the locked actor instance and rejects with
781781

782782
### Per-actor rate limits
783783

784-
The initial implementation supplies the mailbox cap. Distributed token buckets or time-window counters are a hardening milestone.
784+
This runtime supplies the mailbox cap. Distributed token buckets and time-window counters are not planned here, because a request-path limiter is hot and loss-tolerant while every invocation writes one permanent message row. Solid Objects Pro answers that shape with grouped and ephemeral operations, which [fit](fit.md) describes.
785785

786786
### Global enqueue limits
787787

788-
Global admission hooks are not implemented. A future hook can reject based on database health or application policy without introducing a strict global counter as a contention hotspot.
788+
Global admission hooks are not implemented and are not planned here, for the same reason as per-actor rate limits. A strict global counter would also be a contention hotspot. Reject on database health or application policy in front of the actor instead.
789789

790790
### Payload size
791791

@@ -900,7 +900,7 @@ All backends use unique identity and sequence constraints, short transactions, a
900900
14. **How does synchronous invocation work across processes?** The caller first tries to claim and execute the actor locally. If another process owns it, a wake-up adapter prompts a durable result query and bounded polling remains the fallback.
901901
15. **What happens after caller timeout?** A committed message continues and its eventual result can be recovered with the timeout's authorized message reference, or with `find_by` from the request id or the idempotency key when that reference is gone. An enqueue timeout leaves no message. Running Ruby code is not preempted.
902902
16. **How are results cleaned up?** `prune_messages` deletes eligible terminal history in bounded batches after global or per-actor retention. It previews by default and preserves live work, dead letters, retry links, and unfinished outboxes.
903-
17. **How are large mailboxes managed?** The implemented controls are the per-actor mailbox cap, payload caps, and fair activation yields; rate and global admission controls remain roadmap work.
903+
17. **How are large mailboxes managed?** The implemented controls are the per-actor mailbox cap, payload caps, and fair activation yields. Rate and global admission controls are not planned here; Solid Objects Pro answers that shape.
904904
18. **How are completed messages pruned?** Operators schedule the dry-run-reviewed `prune_messages --execute` command. Solid Objects does not run deletion automatically.
905905
19. **How are state migrations performed?** Explicit one-step actor migrations on activation, persisted only with a successful fenced commit.
906906
20. **What happens during rolling deploys?** Newer state can make old workers incompatible; deploys must preserve backward readability or drain old workers.

‎docs/fit.md‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,12 @@ a presence signal, or a view count. Reactive projections materialize a read
6868
model from the durable broadcast outbox, so request-path reads stop competing
6969
with mailbox work.
7070

71+
Distributed per-actor rate limits, global admission control, and cache-capacity
72+
eviction are answered there rather than in this gem. Each one is hot and
73+
request-critical, and this gem writes one permanent message row for every
74+
invocation, so the cost model above rules out a limiter that checks on the
75+
request path. They are not open roadmap items here.
76+
7177
## Cost model
7278

7379
Every synchronous or asynchronous invocation:

‎docs/roadmap.md‎

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,11 @@
170170
loads them in every process, and a rejected subscription reports which
171171
condition caused it instead of closing the socket silently.
172172
- Backpressure: mailbox/payload/state/result caps and fair yields exist;
173-
distributed per-actor rate limits and global admission control do not. The
173+
distributed per-actor rate limits, global admission control, and
174+
cache-capacity eviction are not planned here. They are hot, request-path, and
175+
loss-tolerant, so one durable ordered message per check is the wrong shape,
176+
which [fit](fit.md) already says. Solid Objects Pro answers them with grouped
177+
and ephemeral operations. The
174178
state cap is a limit rather than an operating point. `max_state_bytes`
175179
defaults to 5 MB, and committed throughput measured on SQLite falls about 53
176180
times between an empty state and 1 MB of state, which `docs/benchmarks.md`
@@ -208,12 +212,12 @@
208212
## Next milestones
209213

210214
1. Broaden deadlock retry classification.
211-
2. Add Turbo append intents.
212-
3. Add distributed rate limits, global admission hooks, and cache-capacity
213-
eviction.
214-
4. Expand security scanning beyond the Brakeman scan, such as dependency
215+
2. Add Turbo append intents. The renderer already emits the `append` action for
216+
batch refreshes and payload delivery, so what remains is letting an
217+
application direct one.
218+
3. Expand security scanning beyond the Brakeman scan, such as dependency
215219
auditing and secret scanning.
216-
5. Benchmark all workloads under documented hardware/database settings and
220+
4. Benchmark all workloads under documented hardware/database settings and
217221
publish adapter-specific adoption measurements. Throughput, synchronous
218222
latency, query counts, and the three reactive delivery paths are measured on
219223
SQLite; adapter-specific and end-to-end browser measurements are not.

0 commit comments

Comments
 (0)