Run the item page's count and the feed list's queries together (#346)

entry_page awaited the page's query and then its count, about 40 and
24 ms on /api/entries, the busiest route; feed_list awaited five
independent queries in turn (entries, downloaded, unread, blocked,
feeds), 59 ms of a 67 ms /api/feeds, with the pool's connections idle.
Both now use tokio::try_join!. On SQLite they queue on its connection
as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-23 15:28:09 +00:00
parent aa430f6d50
commit 21fa0ae476
3 changed files with 34 additions and 22 deletions

View File

@@ -521,22 +521,22 @@ impl Db {
// Of this person's feeds only, as the unread count is: counting every item of every feed on
// the server, 1534 feeds for someone reading 146, was 184-316 ms, 70% of the feed list's
// time (#165). The Directory reads this for every feed's title and artwork, not a count.
// Five queries that need nothing of each other, run at once: one after another they were
// 59 ms of a 67 ms feed list, on a pool of idle connections (#346).
let entries = counts(
"entries",
"SELECT e.feed_id, count(*) AS n FROM entries e
JOIN subscriptions sub ON sub.user_id = $1 AND sub.feed_id = e.feed_id
WHERE (CAST($2 AS TEXT) IS NULL OR e.feed_id = $2) GROUP BY e.feed_id",
vec![user_id.into(), one()],
)
.await?;
);
let downloaded = counts(
"downloaded",
"SELECT x.feed_id, count(*) AS n FROM enclosures x
JOIN subscriptions sub ON sub.user_id = $1 AND sub.feed_id = x.feed_id
WHERE x.path IS NOT NULL AND (CAST($2 AS TEXT) IS NULL OR x.feed_id = $2) GROUP BY x.feed_id",
vec![user_id.into(), one()],
)
.await?;
);
let unread = counts(
"unread",
"SELECT e.feed_id, count(*) AS n FROM entries e
@@ -549,22 +549,26 @@ impl Db {
WHERE h.user_id = $1 AND h.feed_id = e.feed_id AND h.guid = e.guid)
GROUP BY e.feed_id",
vec![user_id.into(), one()],
)
.await?;
let mut blocked: HashMap<String, Vec<String>> = tracing::Instrument::instrument(
);
let blocked = tracing::Instrument::instrument(
blocklists::Entity::find().filter(blocklists::Column::UserId.eq(user_id)).all(&self.orm),
step("blocked"),
)
.await?
.into_iter()
.map(|b| (b.feed_id, keywords(Some(b.words)).unwrap_or_default()))
.collect();
);
let mut rows = feeds::Entity::find();
if let Some(id) = only {
rows = rows.filter(feeds::Column::Id.eq(id));
}
Ok(tracing::Instrument::instrument(rows.all(&self.orm), step("feeds"))
.await?
let rows = tracing::Instrument::instrument(rows.all(&self.orm), step("feeds"));
let (entries, downloaded, unread, blocked, rows) = tokio::try_join!(
entries,
downloaded,
unread,
async { blocked.await.map_err(anyhow::Error::from) },
async { rows.await.map_err(anyhow::Error::from) },
)?;
let mut blocked: HashMap<String, Vec<String>> =
blocked.into_iter().map(|b| (b.feed_id, keywords(Some(b.words)).unwrap_or_default())).collect();
Ok(rows
.into_iter()
.map(|f| {
let id = f.id.clone();