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 <noreply@anthropic.com>
This commit is contained in:
2026-10-19 14:05:50 +00:00
parent 187729a278
commit 6daab6989e
3 changed files with 16 additions and 9 deletions

View File

@@ -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}'`), `feed` or `fetch` span holding most of it (`$Q spans '{... && name="feed" && duration > 10s}'`),
or a `download` far slower than its size explains. 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 `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 `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` since #193, so a reap is about a second), `trim_artwork`, with what it removed on the `reap`

View File

@@ -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). - 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). - `/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). - 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). - `/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). - An admin setting, Signals for everyone on the server's TypeSafe key, so the server pays for Jev instead of each person (#271).

View File

@@ -1502,17 +1502,23 @@ async fn entry_page(
let order = let order =
crate::db::order_sql(page.sort.as_deref().unwrap_or("published"), page.dir.as_deref().unwrap_or("desc")); 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); 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 { let mut rows = match signal {
Some(id) => { Some(id) => {
db.signal_entries(user_id, id, filter, search, page.offset, limit, &order) db.signal_entries(user_id, id, filter, search, page.offset, limit, &order).instrument(in_list).await?
.instrument(step("entries_in"))
.await?
}
None => {
db.entries_of(user_id, feeds, 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(in_list).await?,
}; };
step("sanitize").in_scope(|| { step("sanitize").in_scope(|| {
let mut sanitizer = feed_sanitizer(); let mut sanitizer = feed_sanitizer();