Skip to content

Commit 9fcffa6

Browse files
dkamclaude
andcommitted
Docs: add a changelog, synthesised from 1.16.0 through 1.18.0
ADR 0004 says the version "goes in a changelog" and there wasn't one. Root rather than docs/, because docs/README.md sorts by decay rate and explicitly calls "we fixed X" a changelog entry that git already has — so the header states the boundary: news here, durable tool facts in learnings/, reasoning in the commit messages. Three releases back plus Unreleased. Earlier ones are pointed at `git log v1.15.7` rather than back-synthesised from commits nobody is going to read again. Entries keep the measured numbers the commit messages lead with (34,521ms -> 11ms on the level filter, 4 KB/day of vacuum, 84.6ms -> 9.1ms on the sparkline), since that is what makes an entry worth reading over the one-line subject. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HQe6euCNbu7ZgeJe3KLQNB
1 parent 546904e commit 9fcffa6

1 file changed

Lines changed: 146 additions & 0 deletions

File tree

CHANGELOG.md

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
# Changelog
2+
3+
Splat's releases, newest first. Versions are SemVer, hand-set in
4+
`config/version.rb` and tagged `vX.Y.Z` (see
5+
[ADR 0004](docs/decisions/0004-file-driven-releases.md)).
6+
7+
This file is news: what changed, when, and what it was worth. Durable facts
8+
about SQLite, tuber or the Sentry protocol that came out of a release belong in
9+
[`docs/learnings/`](docs/README.md) instead, and the reasoning behind each
10+
change stays in its commit message.
11+
12+
Releases before 1.16.0 predate this file — `git log v1.15.7` has them.
13+
14+
## Unreleased
15+
16+
- Swept nine stray files out of the repo root (scraped listings, log pastes, and
17+
five `test_*.rb` one-off scripts that were never run by `bin/rails test`).
18+
All arrived via an over-broad `git add` in 2025-11 and nothing referenced them.
19+
- `docs/learnings/` gained the SQLite and tuber facts that came out of the
20+
1.18.0 incident.
21+
22+
## 1.18.0 — 2026-09-12
23+
24+
The release answering the 2026-09-12 Booko report: MCP log search wedging the
25+
instance's whole tool surface for ~20 minutes at a time. Both reported query
26+
pathologies turned out to be the same bug wearing different indexes — a filter
27+
SQLite costed as selective, applied across the full retention window,
28+
materialised and sorted before `LIMIT` could exit.
29+
30+
### Added
31+
32+
- `search_logs` takes `service` and `server_name` arguments. The query that
33+
actually unblocked the Booko investigation (`service = 'postgresql' AND
34+
server_name = 'pg01'`) previously had to be run by hand against the SQLite
35+
file over SSH.
36+
37+
### Fixed
38+
39+
- **Full-text log search is bounded by rowid.** The time window used to
40+
contribute nothing: every matching rowid across 14.4M rows was materialised,
41+
each row fetched at random against a 40 GB table, and the lot sorted in a temp
42+
B-tree before `LIMIT` applied. A 30-minute window cost exactly what a 30-day
43+
one did. `search_logs query="duration"` over 30 minutes: never returned → 13.4s.
44+
Bounds come from `MIN(id)`/`MAX(id)` over the window's own rows, so a delayed
45+
OTLP batch widens the range instead of being dropped.
46+
- **Level and environment filters are bounded by the timestamp window.** A bare
47+
single-column index on a low-cardinality column made SQLite cost `level = ?` as
48+
the selective term; carrying the ordering column in the same index lets the
49+
range and the sort share one traversal. `level=error` over 30 minutes for 3
50+
rows: 34,521ms → 11ms, and it no longer queues ingest POSTs behind it. The new
51+
indexes are guarded by name so a large instance can build them out-of-band
52+
before deploying.
53+
- **The hourly histogram rollup survives lock contention instead of burying
54+
itself.** Retention holds write locks on `transactions_spans` for 3+ hours; the
55+
rollup collided, raised five times and was buried — and a buried job holds its
56+
tuber idempotency key, so the next ~70 hourly puts were silently suppressed
57+
for three days while the tube read `ready:0`. Busy errors are now swallowed, a
58+
default run re-counts the last 6 hours newest-first (every write is
59+
`ON CONFLICT DO UPDATE`, so this is free), and a non-busy error still raises.
60+
No data was lost in the gap — live ingest bumps kept the hours populated.
61+
- **Incremental vacuum actually reclaims space.** `PRAGMA incremental_vacuum(N)`
62+
reclaims exactly one page whatever `N` is through the Ruby driver, and the job
63+
called it once per database per day: 4 KB/day against deletes freeing several
64+
GB. `issues_events` held 3.58 GB of live data in a 26 GB file. The loop is now
65+
keyed off `freelist_count`, checkpoints first, pauses every 200 steps rather
66+
than every step, and gives up its budget rather than aborting on contention.
67+
- **A stalled vacuum now checkpoints and retries rather than stopping.** The WAL
68+
pins free pages, so a freelist that stops falling isn't a finished one —
69+
`LogsFtsOptimizeJob` reclaimed 131,141 pages from the same database three
70+
minutes after the vacuum gave up on it. The pre-loop checkpoint is promoted
71+
from PASSIVE to TRUNCATE, and the budget goes 120s → 900s, sized to a night's
72+
~776k log deletes (~1.8 GB) rather than losing ground nightly against a 31.6 GB
73+
backlog.
74+
75+
### Changed
76+
77+
- **Nightly maintenance runs at 2am Melbourne, not 2am UTC** — which was midday.
78+
Retention was holding its multi-hour write locks across the working day, and
79+
the weekly deep storage scan ran 13:40–21:34 local. Every job pinned to an
80+
hour of the night now names `Australia/Melbourne`, so DST is handled rather
81+
than drifting an hour twice a year. Interval and hourly jobs stay zone-free on
82+
purpose.
83+
84+
## 1.17.0 — 2026-09-11
85+
86+
### Added
87+
88+
- **Transaction ingest reads the fields a non-Rails SDK actually sends.** Ingest
89+
had been written against sentry-ruby and quietly assumed that was the protocol,
90+
so Go, Python and Node transactions arrived with no HTTP method, status,
91+
`db_time` or query analysis. HTTP method/status/URL now promote from
92+
`contexts.trace.data` (both OTel-aligned and older flat spellings) when the
93+
dedicated contexts are absent; the rest of `trace.data` is kept as `span_data`
94+
and surfaced in the detail view, the JSON API and `get_transaction`; db op
95+
matching widened from `db.sql.active_record` to the `db.` prefix; and db spans
96+
became a second source of query counts where there are no SQL breadcrumbs. The
97+
Rails path through all four is byte-identical to before.
98+
- **Projects index cards carry what you'd otherwise open the project to find
99+
out** — cron monitor badge, issues first seen in 24h, a 24h error sparkline,
100+
throughput, p95 and 5xx rate. Cards drag into any order by the grip handle
101+
(backfilled from the old ordering, so nothing moves on deploy); the handle is a
102+
real button, so arrow keys reorder too.
103+
104+
### Fixed
105+
106+
- **`db:migrate` stopped emptying the cache database.** Solid Cache ships
107+
schema-only, but `database.yml` pointed the cache database at an absent
108+
`db/cache_migrate`, so `db:migrate` had nothing to run and then dumped the
109+
empty database over `db/cache_schema.rb` — three times, with a fresh deploy in
110+
between booting with no cache table. `solid_cache_entries` is now owned by a
111+
guarded migration, so the file round-trips.
112+
113+
### Performance
114+
115+
- The projects index sparkline caches each hour bucket under a key that names
116+
the hour, so a warm load scans 3 hours instead of 24: 84.6ms → 9.1ms per
117+
project over 240k events/day. The settling window and in-progress hour are
118+
always recomputed, because an event's timestamp is when it happened, not when
119+
it landed.
120+
121+
## 1.16.0 — 2026-08-29
122+
123+
### Changed
124+
125+
- **Settings: a real copy widget for the MCP command.** The `claude mcp add`
126+
command sat in a fixed-height `<pre>` that scrolled horizontally, hiding the
127+
`--header "Authorization: Bearer ..."` half that matters. It's now one widget —
128+
code block with its own chrome bar, Copy confirming in place, and a Show/Hide
129+
token toggle so the panel survives a screenshot. Display and clipboard share a
130+
source, so what you read and what you paste can't drift apart.
131+
- The Settings Counts tile steps 2 → 3 → 5 columns instead of jumping straight
132+
to five narrow ones, with truncation as a backstop for 9-digit counts.
133+
134+
### Fixed
135+
136+
- A 40-character revision SHA overran its grid column on the About page and
137+
printed over the Rails version. Full hex SHAs trim to 12 (tags pass through),
138+
full SHA in the title.
139+
- `transactions#show` had a `feedback` span the clipboard controller never used,
140+
so copying a transaction ID replaced the ID text with `<id> Copied!`.
141+
- Cleared two standardrb offences failing the lint job.
142+
143+
### Dependencies
144+
145+
- Routine bundle update: net-protocol 0.3.0, pagy 43.6.2, rbs 4.2.0,
146+
rubyzip 3.5.0, thruster 0.1.26. Patch and minor only; bundler-audit clean.

0 commit comments

Comments
 (0)