Skip to content

perf(store): cache an observation's parsed time in 8 bytes - #27

Closed
A13xB0 wants to merge 2 commits into
masterfrom
perf/obs-timestamp-8-bytes
Closed

A13xB0 wants to merge 2 commits into
masterfrom
perf/obs-timestamp-8-bytes

Conversation

@A13xB0

@A13xB0 A13xB0 commented Oct 11, 2026 •

Copy link
Copy Markdown
Owner

Refs Kpa-clawbot#2148.

Problem

StoreObs is the store's most numerous struct: 400k of them on a week of a busy regional network. Its lazy timestamp cache (Kpa-clawbot#1481) is a time.Time, a sync.Once and a bool, together 40 bytes. On current master that makes the struct 200 bytes, which puts every observation in the 208-byte size class.

Change

The cache is now a single atomic.Int64:

  • 0 before the first call;
  • the parsed time as UTC nanoseconds after it;
  • or one of two sentinels: one for empty or unparseable strings, and one for times it can't hold exactly (a non-UTC offset, or outside UnixNano's range). Those are re-parsed on each call so their location is preserved.

Concurrent first calls may both parse; they store the same value. StoreObs is now 160 bytes, in the 160-byte size class: 48 bytes × 400k observations ≈ 19 MB less heap. The first parse costs the same; cached reads now build the time.Time from an int.

Tests

  • TestStoreObsFitsIn160Bytes: red commit first.
  • TestStoreObsParsedTimeMatchesParse: Z, nanosecond, offset, SQL, empty, garbage and epoch inputs give the same time and location as time.Parse, on the first and the cached call.
  • TestStoreObsParsedTimeConcurrentFill.
  • The existing storeobs_parsedtime_test.go still passes.
  • go test ./cmd/server passes, and -race passes on the ParsedTime, retransmission and observer analytics tests.

This PR or Kpa-clawbot#2149

Kpa-clawbot#2149 fixes Kpa-clawbot#2148 by reordering these same three fields. The two conflict, so here is how they compare. Sizes are unsafe.Sizeof(StoreObs{}) on each branch, with Go's heap size class beside them:

struct heap per observation saved per observation saved at 400k observations
master 200 B 208 B – –
Kpa-clawbot#2149 (field reorder) 192 B 192 B 16 B ~6.4 MB
this PR (one 8-byte field) 160 B 160 B 48 B ~19 MB
  • perf(server): reduce retained observation allocation size Kpa-clawbot/CoreScope#2149: a pure field reorder with no change in behaviour. It is the smallest, safest change and the easiest to review. It saves a third of what this PR does.
  • This PR: three times the saving, but more code. It adds two sentinel values, and re-parses on each call any timestamp with a non-UTC offset. The store writes UTC (Z), so that path is rare. It is tested against time.Parse on edge cases, concurrently, and under -race.

Options:

  1. Take this PR. perf(server): reduce retained observation allocation size Kpa-clawbot/CoreScope#2149 then has nothing left to reorder.
  2. Take perf(server): reduce retained observation allocation size Kpa-clawbot/CoreScope#2149 now, and this PR after it as a follow-up for the further 32 bytes per observation. I'll rebase onto perf(server): reduce retained observation allocation size Kpa-clawbot/CoreScope#2149 if it lands first.
  3. Take only perf(server): reduce retained observation allocation size Kpa-clawbot/CoreScope#2149 if the smaller, simpler change is preferred.

No config or UI changes.

🤖 Generated with Claude Code

https://claude.ai/code/session_01KaR4xu8gVykQBkH8yG8m6H

@A13xB0 A13xB0 added upstream: not submitted Fork only so far; not opened upstream performance Memory or speed improvement live: pre-prod In the image running on CoreScope pre-production labels Oct 11, 2026
A13xB0 and others added 2 commits October 13, 2026 04:12
Red: the timestamp cache (time.Time + sync.Once + bool) makes StoreObs
192 bytes. The parse-equivalence and concurrency tests pass today.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KaR4xu8gVykQBkH8yG8m6H
StoreObs cached its parsed timestamp in a time.Time, a sync.Once and a
bool: 40 bytes that made it 192 bytes, the 192-byte size class. A week of
a busy network holds 400k observations.

The cache is now one atomic.Int64: 0 before the first call, the time in
UTC nanoseconds after it, or a sentinel for unparseable strings and for
times it cannot hold exactly (a non-UTC offset, or out of UnixNano's
range), which are parsed again on each call so their location is kept.
Concurrent first calls may both parse; they store the same value.
StoreObs is 160 bytes, one size class down: 32 bytes x 400k = 12.8MB.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KaR4xu8gVykQBkH8yG8m6H
@A13xB0
A13xB0 force-pushed the perf/obs-timestamp-8-bytes branch from 5c54765 to 682c40a Compare October 13, 2026 03:26
@A13xB0 A13xB0 added upstream: in review Open as a PR on Kpa-clawbot/CoreScope, waiting on review and removed upstream: not submitted Fork only so far; not opened upstream labels Oct 13, 2026
@A13xB0

A13xB0 commented Oct 13, 2026

Copy link
Copy Markdown
Owner Author

Rebased onto upstream master and submitted as Kpa-clawbot#2196.

@A13xB0 A13xB0 added upstream: merged Merged into Kpa-clawbot/CoreScope master and removed upstream: in review Open as a PR on Kpa-clawbot/CoreScope, waiting on review labels Oct 13, 2026
@A13xB0

A13xB0 commented Oct 13, 2026

Copy link
Copy Markdown
Owner Author

Merged upstream as Kpa-clawbot#2196; closing.

@A13xB0 A13xB0 closed this Oct 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

live: pre-prod In the image running on CoreScope pre-production performance Memory or speed improvement upstream: merged Merged into Kpa-clawbot/CoreScope master

Projects

None yet

Development

Successfully merging this pull request may close these issues.

perf: avoid allocation-class padding in retained observations

1 participant