Repository navigation
Conversation
A restart read the minute pages through latest-first pages, and every page call read the descriptor of every group of the day's minute catalog (up to ~720 a day). The day catalog is now listed once (control objects only), the newest acquisition of each minute within 40 minutes is kept, and only those groups are restored. CatalogStore::lookup_members reads one catalog and then only the named members' groups; lookup_acquisition is its one-member case. Reader::listed_acquisitions, moved from the image forecast, lists a catalog's acquisitions for both callers. ordered_candidates and lookup_page are unchanged for the other cursor readers. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A restart restored every stored AWS hourly and city page of the 8-day window and parsed each again, about 1 s per day on a t3.small, so the first request answered without past days (#2866). AK chose the day product design on 2026-11-07. Any process that folds now stores one product per KST day (kma-web/station-pages) holding the parsed inputs of the hourly fold: the newest page per kind and hour with its fetch instant, digest and raw identity, reduced AWS rows as a facts table, the city set they are whole for and the S06 exclusions of the read. It is written after a fold that recomputed the hourly part, never awaited by a request, at most once per ten minutes per day and only when the day's pages changed. Values keep undefined, dates, negative zero and NaN. A restart reads each day's newest product and compares it with the day catalogs: unlisted product pages are dropped and only newer listed pages are restored and parsed. An unusable product (unreadable, older schema or parse revision, a grown city set) or a newer page that is not complete gives the day's raw read. The load ends after the products, today's and yesterday's comparisons, the seed and the four newest minute pages; LoadRest finishes the other days and minutes. settled() counts LoadRest and the product comparison as busy, and an excluded group keeps its day partial. Products are kept out of the disk cache. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review of #2872 (CHANGES_REQUESTED) found three answers that differed from a cold raw read. RO-city-growth: a city page restored after the products could name a station whose AWS rows the products had reduced to registry facts. A fold whose city set grew past the set an AWS page was reduced for now starts Flight::Expand, which reads those pages in full again by their raw identity (RecordId::from_object_key); the next fold reduces them for the new set. RO-minute-fallback: the minute read kept only the newest acquisition of each minute. Up to three are kept, newest first, and a minute whose acquisition is not complete or does not parse is read from the next one. RO-sticky-excluded: a product carried only the count of excluded groups, so a repaired group kept its day partial. Products (schema 2) now record knownIds and excludedIds, and each read of a day restores the groups that are neither held nor known complete: the excluded ones again and those published since. A repaired group completes the day and the product is stored again; a group published later and older than the pages is checked too. S06 excluded members come from CompleteSet. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Second review of #2872 (RO-city-growth, RO-expand-progress, RO-record-key-overflow). A held AWS page reduced for a smaller city set than the last fold's now keeps settled() false, so no summary is recorded from its missing rows. The full read of such pages is started after every fold and request (not only after an hourly recompute), reads four pages per catalog read and keeps each read as it completes; only started pages wait for the retry, now one minute as for a failed ASOS S3 read, and pages not reached before the 30 s limit stay due for the next run. RecordId::from_object_key requires the key's four, two and two date digits before computing the date, so out-of-range numbers are refused instead of overflowing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Cross-provider review (Codex gpt-6.1-sol, high): PASS at 9c16049 (third round). Required (all fixed, each with a regression test seen failing first)
Recommended
EC2 restart comparison: see the PR description. The first answer after a restart is still bound by the general restart restore (1,100-1,300 S3 calls across all sources). |
AK chose on 2026-11-07 that the first answer after a restart is judged by the client validation (kma_client_eval.py), with parts that are not ready within the bound coming on later requests. The EC2 comparisons of master, this change and this change with #2872 showed 36 of 36 bodies passing it and first requests of 3.0 to 3.7 s on every binary. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…is_read observe_product::the_newest_of_two_processes_products_is_read failed 9 times in 20 at 0fa360e with one product for a day. Both producing processes stored every day's product (8 PUTs each), but two processes started together run in step and make each day's product within 0 to 3 ms of each other. The product's instant (made, product_candidates) is its record's fetched time and is in its bytes, so two made in the same millisecond are one record identity and S06 keeps one catalog entry. That is right for S3; the test assumed two instants. The second process now runs on a test transport that holds its AWS hourly LISTs: it lists the (absent) products, waits there while the first process stores its products, then makes its own, so each day holds two products of different instants and the third process reads the newer. 20 consecutive passes, and 10 with the CPU saturated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The test double answered the forecast-zone page with Seoul only. When that list was stored before the request's zone refresh, towns outside Seoul had no city zone and their mid temperature was not collected, so a_province_under_its_new_name_finds_its_town_and_mid_forecast failed in about 60% of runs on this branch (the day product reads at the start of a request make the race more likely) and occasionally on master. The double now answers every packaged land zone, as the provider's list does, and the test runs each town with and without the list stored first. Same test change as 0c76e77 on fix/2614-kma-test-clock, declared here so this PR's CI keeps one task declaration. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…TC-force-fallback-order) full_response::a_province_under_its_new_name_finds_its_town_and_mid_forecast asked each town with the forecast-zone list stored first and "without it", but nothing kept the request's own zone acquisition from storing the list before the answer: counting the lists S3 held when the answer was made, the "without" case had one in 10 of 10 runs, so it did not pin the release's land-zone fallback. In that case the double's zone request now waits at a barrier (HeldZones) until the answer has been checked, and the test asserts the lists S3 held at the answer (none, or the one stored first) and that the held request stores its list once released. 10/10 passes. The same test change as 64541ef on fix/2614-kma-test-clock. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CI run 42379684133 (52a270c) failed full_response::empty_weather_store_serves_seven_days_before_raw_publication_and_replays_identically: one raw publication before the body terminal. Its trace shows a group published 2 ms into the owned flight that acquired it, while the request was still running. Writer::acquire counted a waiter only by its watch receiver, and run() asked receiver_count() after sending Ready: a waiter that took the batch and returned before that check, or a creating caller not yet polled again when an instant provider was done (it subscribed after spawning the job), left the batch looking unwaited, and NoWaiters released it, so the response's own raw was stored before the response finished (against provider -> assemble -> respond -> store). The writer is unchanged from master; the day products do not use it. The caller now subscribes before the job is spawned (or under the table lock when it joins), and the waiters are counted before Ready is sent. The new tests/response_first.rs a_creating_waiter_holds_its_instant_provider_until_the_body_ends (2,000 scoped instant acquisitions) failed 17/20 runs with 30 busy loops on 10 cores (NoWaiters releases before the body ended) and passes 20/20 under the same load; both full-cycle tests pass 20/20 under it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Cross-provider re-review (Codex gpt-6.1-sol, high), round 4 on the commits after the round-3 PASS (b34c001, 52a270c, 06018c2, e708d12): CHANGES_REQUESTED. Required
Recommended
Both are being fixed once and applied identically here and on #2903. |
… (RFW-zero-count-join) run() counted the receivers, then announced Ready and decided NoWaiters, without the table lock under which callers join entries. A response that joined after its creator was cancelled (count 0) but before Ready got the batch while the stored 0 released it as unwaited, so its raw was published while its body was still being sent. Who waits is now settled under the table lock together with the Ready announcement and the NoWaiters decision. Publisher::checkpoint is a test seam (a default that is ready at once; no setting). The new a_response_joining_before_ready_holds_a_cancelled_creators_batch forces that order; with the count taken before the checkpoint it failed 3/3 ([NoWaiters]). Same writer and test code as 7d27588 on fix/2614-response-first-waiters (#2903). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…heduling) The regression test for the waiter count relied on the scheduler to preempt at the right step over 2,000 instant acquisitions, and its "nothing released" check could trip on a slow runner's 10 s dormancy cap. Two more Publisher::checkpoint points (Created, Announced) let a_creator_not_yet_waiting_when_its_batch_is_ready_holds_it and a_waiter_that_takes_the_batch_at_once_holds_it force the orders; each failed 3/3 with the old order ([NoWaiters]). The mass run is an ignored stress check that fails only on NoWaiters. The spec's RESPONSE-FIRST criterion names the forced-order tests. Same writer and test code as 03f7b57 on fix/2614-response-first-waiters (#2903). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
CI passes 14 of 14 at d840e9a, which merges master through #2866. Two earlier attempts were cancelled by the 45-minute job limit of the foundation job; passing runs take about 44 minutes. The cross-provider re-review passed (round 5). The restart race (#2900) builds on this PR; its EC2 results are on #2900. |
Summary
Stacked on #2866 (base
feat/2614-kma-restart-reads) so CI builds both restart fixes together for one EC2 comparison. AK chose this design on 2026-11-07 ("A로 진행").The EC2 restart comparison of #2866 showed that the first weather request after a restart misses the minute observations and the past days, and that S3 admission was not the remaining cause. The observation source's restart read (
observe.rsread_s3) restored every stored AWS hourly and city page of the 8-day window one day after another and parsed each page again; the minute pages came last, and every minute page call read the descriptor of every group in the day's minute catalog. Measured parse cost: about 10 ms per AWS page on an M4, so roughly 1.0-1.4 s per day and 12-17 s for 8 days on a t3.small, plus 4-5 s of sequential GETs, against a 2.5 s request wait.Changes
storage/catalog.rslookup_members,records.rs): a restart lists the day's minute catalog once and restores only the newest acquisition of each minute within the readers' 40 minutes; the newest four before the first answer, the rest after it.observe_product.rs, kindstation-pages, one per KST day): the parsed inputs of the hourly fold (the newest AWS hourly and city page of each hour, with their page identities), written by any process that folds, at most once per ten minutes per day and only when what the day is made of changed. No request waits for it. Excluded from the disk cache like the image forecast product.LoadRest). An unreadable product, an older schema or parse revision, or a grown city set falls back to the day's raw pages and the product is stored again.excludedcarried in the product) or a newer group is not complete.server2.md(S15b-2 restart read, S16 note), KMA data design (+ regenerated Archify sequence), infrastructure. Records:intent/,specs/,plans/issue-2614-kma-restart-observe.md; declarationserver2/config/tasks/KMA-RESTART-OBSERVE.json.Verification
targeted_members_read_one_catalog_and_only_their_groups,a_restart_restores_only_the_minute_pages_within_reach(200 stored minute groups: at most 20 descriptor and 20 raw GETs), and sixobserve_producttests (restart answers 168 past hours and minute rows with providers unreachable and no raw GET for covered pages; a later page restored alone and equal to a cold read; fallbacks; two writers; unsettled whileLoadRestruns; an excluded group stays partial; products not cached). Each failed before the change; mutations caught:LoadRestdropped fromsettled,excludednot carried, made-of check ignored.cargo test --locked --workspaceon the stack at 9c16049: 1,236 passed, 0 failed, 7 ignored; CI passes.cargo fmt --check,cargo clippy -D warnings, placement gate and artifact policy pass. Archify: validate, deliver, strict check and browser check pass.LoadRest.kma_client_eval.pyreported 0 violations for these bodies; it checks shape, not completeness.Decisions for AK
The first answer after a restart does not meet the client validation (AK's definition) with or without this change; this change shortens the observation restart read only. How to meet the criterion is open.
Products are stored under
kma-web/station-pagesand never deleted: estimated 300-400 KB gzipped per day product, about 25 MB a day per writing process at the ten-minute limit (not measured).The first answer after a start never records daily summaries (S16): the days past yesterday are compared after it, so a later request records them.
A group older than a day product's pages, published after the product and excluded by S06, is not noticed on a restart from the product (a raw read would mark the day partial).
Right after the first deployment, the oldest day of the window has no product and is read from its raw pages after the first answer.
Not in this change: a product for the minute pages, the ASOS station state, the collector's restart probing.
🤖 Generated with Claude Code