Skip to content

v3.9.0 — a window that stops where it says it stops

Choose a tag to compare

@productdevbook productdevbook released this 20 Aug 06:26
· 40 commits to main since this release

Fixes six defects in v3.8.0's own code, found by measuring it. Anyone on 3.8.0 should take this.

The event read walked the whole table

  • Each of the two windows is LIMIT 400, and a LIMIT only short-circuits if its window can fill. The kind lived on monitors, so finding it needed a join, and a deployment with no grouped monitors — the default — could never fill the grouped side.
  • Every request to the page, status.json and the feed scanned every event ever kept: 0.97 ms at 1,000 events, 6.96 ms at 50,000, 24.81 ms at 200,000. On D1 that is rows_read — about a dozen page views a day against the free tier's 5M.
  • The kind is on the event row now, with an index over (grouped, at): 0.24 ms at every size, and the plan says SEARCH rather than SCAN.
  • A monitor written with grouped = 2 matched neither window and its history vanished from the page, the JSON and the feed. groupOf reads any non-zero as grouped; the windows now agree with it.

The body cap was set by the runtime, not by the constant

  • Each chunk was enqueued before the total was checked, so the read overshot by one chunk: 112,561 bytes on undici, 69,489 on workerd, against a 65,536 cap. The chunk is cut now.

Latency measured the body

  • An origin whose headers arrive in 3 ms and whose body takes a second reported 937 ms. That number is latency_ms, the 24h sparkline and days.ms_sum, so every deployment's chart took a permanent step at the v3.8.0 upgrade with nothing said about it.
  • It is time to the answer again. The body is still read; what reading it costs is not charged to the number you chart.

timeout had become unreachable

  • The reason asked whether a status had arrived before it asked whether our own signal had fired, so a monitor that blew its timeout while the body trickled reported "answered, then stopped". Our deadline is ours: blaming the host for a budget it never saw is not an answer.

Tests for the features 3.8.0 was named for

  • The cap, incomplete, the two windows and a grouped value that is not 1 had no test. They do now, each verified by making the fix fail.
  • Three ratchets read the source for the mechanisms behind five releases of defects: a claim in prose the code does not keep, a rule kept on one surface and not its sibling, a remembered value whose invalidation misses a writer.

Also

  • /health carries x-nabiz, the build answering. Nothing a deployment served said which version it was.
  • Telegram followed redirects; only the webhook had been fixed. A 3xx makes the POST a bodyless GET, answers 200, and the alert is gone.
  • The 401 and 429 from a write carry CORS like the 201 and 400 beside them.
  • The round warms the shaping, not only the database read — the first reader after each round was still paying a cost proportional to a group's hidden members.
  • llms.txt still called the JSON, the feed and the badge uncached, three releases after they began reading through a held page.
  • docs/api.md counted six endpoints above a table of seven, and /robots.txt was in neither.

Upgrading

  • A container adds the column, fills it and creates the index on start, and says so. On Cloudflare there are three statements in docs/UPGRADING.md — run in the order given.
  • Check expect_body before pulling if it matches something late in a large page. Since v3.8.0 only the first 64 KB is read, so a monitor watching a footer goes down at the first round after the upgrade, with an alert, while the host is fine. The one-liner to check is in the upgrade notes.