A political conference and discussion platform, in Rust and Dioxus
wiki docs query-audit.md
4.5 kB
Markdown
at main

Every list query, and what it costs #

2026-07-31, after the author-picker work. The question asked was whether other queries share the two faults found there: no limit, and fetching a whole document to read one key. Every row count below is from production, not estimated.

The headline #

Missing limit turned out to be mostly a non-problem, and payload shape the real one. A list the user must see in full cannot be capped without pagination — a cap would silently hide children, comments or votes, and on a vote tally it would silently produce a WRONG NUMBER. So the fixes worth making were about what each row carries, not how many rows come back.

Three changes came out of it, all measured:

what before after
poll turnout (count_active_members) 46.0 KB 0.1 KB
drawer expansion, widest folder 54.0 KB 11.4 KB
home context list 3.2 KB 1.9 KB

The queries that return lists #

limit column: whether the operation sends one. worst is the largest result this deployment can actually produce today.

query limit worst verdict
NodesWhereQuery ✓ —
NodePickerQuery ✓ 10 — author picker
NodesSearchQuery ✓ 30 — search bar
MembersExistQuery ✓ 1 — existence probe
UsersSearchQuery ✓ 10 —
RecentNodesQuery ✓ —
ChildrenQuery — 48 children must not cap — a folder shows all its children
DrawerChildrenQuery — 48 children must not cap — same list, in the tree
ChildIdsQuery — 239 per context correct: a subtree walk that is wrong if truncated; ids only
VotesWhereQuery — 10 votes must not cap — this is the tally
CommentsQuery — 7 comments must not cap — a truncated discussion is a lie
ContextsWhereQuery — 8 groups bounded by the number of contexts
PollsWhereQuery — 5 per context bounded
PermissionsQuery — 45 per context bounded by mime × role
RelationsQuery — 2 per node bounded
InvitationsQuery — 23 per person bounded, and per signed-in user
DeletedNodesQuery — 2 in the bin bounded today; see below
MembersCountQuery n/a 1001 → 1 integer fixed, below

The one that was genuinely wrong: counting by fetching #

count_active_members selected every member id of a context and took .len(). On the largest context here — 1001 members — that is 46 KB over the wire to learn one integer, and it runs for every poll, because turnout is drawn on each one. The comment above it said the schema exposes no members_aggregate. That was simply false: membersAggregate is there and the user role may call it. Verified as an ordinary member against production: {"count": 1001}, 0.1 KB.

The two that carried documents to draw an icon #

DrawerChildFields and ContextNodeFields both select data, and both read exactly one thing out of it: a file's content type, for the glyph. Everything else in that jsonb — the whole Slate document of every sibling — was travelling for nothing. Hasura selects inside a jsonb column, so they now ask for data(path: "type"), as the search bar already does.

Measured on the widest folder in production, 48 children: 54.0 KB → 11.4 KB. The drawer expands a folder every time you open one, so this is not a one-off.

The ones deliberately left alone #

  • ChildNodeFields.data stays whole. A folder row renders rich content inline (SlateRenderer) and the feed reads a comment's text out of it. It is used, not carried.
  • PollSummaryFields.data stays whole — the options are the point.
  • NodeFields.data is the document itself.

Worth revisiting when the data grows #

Nothing here is urgent at today's sizes, and each would be a real fix later:

  • The bin has 2 rows today. At a few thousand it needs order_by deletedAt desc plus a limit and a "show older" step — a cap without an order would hide arbitrary items, which is why it has none now.
  • A folder with hundreds of children needs pagination, and then ChildrenQuery/DrawerChildrenQuery get their limit for free. The congress-scale folder to watch is the one at 48.
  • A poll with thousands of votes should be tallied server-side by an aggregate rather than by shipping ballots to the browser to count. Same shape of fix as the member count above, and the same reason: ask for the number.