From 6daab6989eb0ecb53067cc7a94125e76e015b34a Mon Sep 17 00:00:00 2001 From: rays Date: Mon, 19 Oct 2026 14:05:50 +0000 Subject: [PATCH] A list's trace says which list it was (#287) The prod check of 2026-10-19 found /api/entries requests of 0.5-0.8 s overnight beside others of 80-100 ms on an idle host, the time all in entries_in, and nothing in the trace said which filter, sort or page they were. The step now carries them, and whether a search, not its words. Co-Authored-By: Claude Opus 5.5 --- .claude/skills/ipx-prod-check/SKILL.md | 2 +- crates/ipx/CHANGELOG.md | 1 + crates/ipx/src/web.rs | 22 ++++++++++++++-------- 3 files changed, 16 insertions(+), 9 deletions(-) diff --git a/.claude/skills/ipx-prod-check/SKILL.md b/.claude/skills/ipx-prod-check/SKILL.md index cdef9f8..8998d46 100644 --- a/.claude/skills/ipx-prod-check/SKILL.md +++ b/.claude/skills/ipx-prod-check/SKILL.md @@ -185,7 +185,7 @@ a count says something happened, the lines and traces say why. `feed` or `fetch` span holding most of it (`$Q spans '{... && name="feed" && duration > 10s}'`), or a `download` far slower than its size explains. - Steps are `step` spans named by `of` (#187): an item list's `entries_in`, `sanitize`, + Steps are `step` spans named by `of` (#187): an item list's `entries_in` (with the list asked for: `filter`, `sort`, `dir`, `offset`, `limit`, `search`), `sanitize`, `deadair`, `count_in`; the feed list's `catalogue`, `feed_list` and its queries; a `reap`'s `missing_files`, `prune_abandoned_failures`, `reap_candidates`, `clean_directory` (once a day since #193, so a reap is about a second), `trim_artwork`, with what it removed on the `reap` diff --git a/crates/ipx/CHANGELOG.md b/crates/ipx/CHANGELOG.md index 785370d..c2f0f57 100644 --- a/crates/ipx/CHANGELOG.md +++ b/crates/ipx/CHANGELOG.md @@ -19,6 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - A show from an OPML subscription or a Patreon creator can be kept: `kept` on `PATCH /api/feeds/{id}` and in the feed list. When the OPML drops a kept show, or you leave the OPML, it stays on your list as a feed of its own (#280). - `/metrics` counts Signals: items judged and fitting, calls to Jev by how they ended, the time they took and the tokens TypeSafe counted, time since the judge's last pass, and keys refused; a failed call to Jev shows as a failed trace (#270, #275). - An admin setting for how many days back a new Signal reads (#276). +- A list's trace says which filter, sort and page it was asked for, and whether a search, so a slow list can be told from the others (#287). - `/metrics` breaks Signals' items, fits and TypeSafe tokens out by person and by whose key paid, so the server's bill can be split (#278). - An admin setting, Signals for everyone on the server's TypeSafe key, so the server pays for Jev instead of each person (#271). diff --git a/crates/ipx/src/web.rs b/crates/ipx/src/web.rs index d831501..f743d9a 100644 --- a/crates/ipx/src/web.rs +++ b/crates/ipx/src/web.rs @@ -1502,17 +1502,23 @@ async fn entry_page( let order = crate::db::order_sql(page.sort.as_deref().unwrap_or("published"), page.dir.as_deref().unwrap_or("desc")); let limit = page.limit.clamp(1, 200); + // Which list, so a slow one's trace says what was asked of it (#287). The search's words stay + // out: they are the reader's own. + let in_list = tracing::info_span!( + "step", + of = "entries_in", + filter = page.filter.as_deref().unwrap_or("all"), + sort = page.sort.as_deref().unwrap_or("published"), + dir = page.dir.as_deref().unwrap_or("desc"), + offset = page.offset, + limit, + search = search.is_some(), + ); let mut rows = match signal { Some(id) => { - db.signal_entries(user_id, id, filter, search, page.offset, limit, &order) - .instrument(step("entries_in")) - .await? - } - None => { - db.entries_of(user_id, feeds, filter, search, page.offset, limit, &order) - .instrument(step("entries_in")) - .await? + db.signal_entries(user_id, id, filter, search, page.offset, limit, &order).instrument(in_list).await? } + None => db.entries_of(user_id, feeds, filter, search, page.offset, limit, &order).instrument(in_list).await?, }; step("sanitize").in_scope(|| { let mut sanitizer = feed_sanitizer();