Skip to content

id(world): admin_history_bytes re-serializes the entire retained admin history on every accepted event (PR #3998) — track retained bytes incrementally #4633

Closed

Description

@dezren39

id(world): admin_history_bytes re-serializes the entire retained admin history on every accepted event — track retained bytes incrementally

Scope: PR #3998 head 47937fe3e73707eaefbaddc6d196a45f26ddc366 (fix(id): export host events through authorized journal history). The admin-history machinery does not exist on main yet — it is introduced by that PR — so this is a follow-up on the PR's new code, cross-linked both ways. Independent origin: Codex P2 review comment r4437438581 on PR #3998; re-verified line-by-line against the PR head this run, and left unfiled by the 2026-11-02 pr-comment-review run only because that run hit its per-repo filing cap.

Evidence (all line numbers on PR head 47937fe3)

  • pkgs/id/src/world/core.rs:7-12 — the helper itself:

    fn admin_history_bytes<'a>(events: impl IntoIterator<Item = &'a WorldEvent>) -> usize {
        events
            .into_iter()
            .map(|event| serde_json::to_vec(event).map_or(usize::MAX, |bytes| bytes.len()))
            .fold(0usize, usize::saturating_add)
    }

    Every call allocates and JSON-serializes every event it is given, just to sum the byte lengths.

  • pkgs/id/src/world/core.rs:1317-1336 — record_admin_history pushes the new event, then evaluates the helper in the trim-loop condition:

    while self.admin_history.len() > self.limits.retained_events.min(MAX_ADMIN_HISTORY_EVENTS)
        || admin_history_bytes(&self.admin_history) > MAX_ADMIN_HISTORY_BYTES

    In steady state (length within cap) the byte total is computed on every call, and each pop_front re-evaluates the condition — so trimming k events costs k+1 full re-serializations of the remaining deque.

  • Call frequency: record_admin_history runs on the accept path — pkgs/id/src/world/core.rs:1312 (inside event acceptance), also core.rs:457, core.rs:650, and pkgs/id/src/world/actor.rs:445. Every accepted input, chat message, or host event pays the full scan even when no admin export is ever requested.

  • Bounds: pkgs/id/src/world/mod.rs:57 MAX_ADMIN_HISTORY_EVENTS = 4096; mod.rs:59 MAX_ADMIN_HISTORY_BYTES = 64 MiB. With the default 1,024-event retention and valid 16 KiB inputs, one accepted event can re-encode ~16 MiB of JSON; at the 4,096-event cap, up to the full 64 MiB byte budget is re-encoded per event.

  • Secondary edge in the same helper: a single event whose serialization fails counts as usize::MAX, so the total saturates and the trim loop would drain the entire retained history one pop (and one full re-scan) at a time.

  • WorldCore is deliberately serialized: core.rs:28 — "Keep it behind one actor/mutex so event sequencing is serialized." The re-serialization is synchronous work inside that single world actor, so it delays subsequent inputs, ticks, and view requests for everyone in the world.

  • One legitimate one-shot use exists: restore_admin_history (core.rs:1349) validates a checkpoint's history once at restore time. That call can stay, or reuse the same cached sizes.

Why it matters

This is an O(retained-history) tax on the hottest path in the world actor, paid per accepted event, forever, in exchange for a byte total that changes by exactly one event's size per call. Busy worlds (the ones that accumulate a large retained history) get slower per event the more history they retain — precisely backwards. The fix is mechanical and local: cache each event's encoded size when it enters the deque and maintain a running total as events enter and leave.

Acceptance criteria

  • record_admin_history is O(1) in the retained-history size per accepted event (amortized): encoded sizes are cached per retained event and a running byte total is maintained incrementally on push/pop; no per-event full re-serialization on the accept path.
  • Trim behavior is unchanged: the same events are retained/evicted under both the event-count cap (retained_events.min(MAX_ADMIN_HISTORY_EVENTS)) and the byte cap (MAX_ADMIN_HISTORY_BYTES), and admin_history_complete flips exactly as before.
  • restore_admin_history's byte-bound validation still rejects an over-budget checkpoint history (it may reuse the cached sizes or keep the one-shot scan — restore is not the hot path).
  • The serialization-error edge is handled deliberately (an event that cannot be serialized cannot silently poison the running total or drain the whole history); the chosen behavior is pinned by a test.
  • A regression test or benchmark demonstrates the improvement (e.g. a test with a large retained history asserting the trim outcome, plus a comment/measurement showing per-event cost no longer scales with history size), and the existing world test suite passes.

Cross-links: PR #3998 (introduces the code), Codex review comment r4437438581.

Activity

  1. added
    kind/performancePerformance problem or optimization
    task/triageQueue packet kind: verify, reproduce, and route this item
    source/creatorFiled by the new-issue-creator job
    priority/normalOrdinary priority (the default)
    queue/queuedHas a queued task in the Supabase agent queue
    app/idPackage/app: id (pkgs/ or apps/ in this repo)
    effort/sSmall: expected under 2 hours of work
    on Nov 2, 2026
  2. dezren39 commented on Nov 2, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · filed

  3. added
    work/assignedQueue worker lifecycle state
    queue/claimedQueue worker lifecycle state
    and removed
    queue/queuedHas a queued task in the Supabase agent queue
    on Nov 2, 2026
  4. dezren39 commented on Nov 2, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · picked up

  5. 40 remaining items

  6. dezren39 commented on Nov 3, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · parked

  7. added
    queue/claimedQueue worker lifecycle state
    and removed
    status/parkedDeliberately paused with a recorded resume condition
    queue/parkedQueue task is parked (waiting on CI, time, or an event)
    on Nov 3, 2026
  8. dezren39 commented on Nov 3, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · picked up

  9. dezren39 commented on Nov 3, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · picked up

  10. added a commit that references this issue on Nov 3, 2026
  11. dezren39 commented on Nov 3, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · done

  12. dezren39 commented on Nov 3, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · done (correction to the test evidence in the previous note)

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

Metadata

Metadata

Assignees

Labels

app/idPackage/app: id (pkgs/ or apps/ in this repo)effort/sSmall: expected under 2 hours of workkind/performancePerformance problem or optimizationpriority/normalOrdinary priority (the default)source/creatorFiled by the new-issue-creator jobtask/triageQueue packet kind: verify, reproduce, and route this item

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions