Skip to content

perf: back delivery list reads with a composite (status, created_at_chain) index- - #169

Merged
dev-fani merged 2 commits into
fanilabs:mainfrom
micmusjnr20:main
Aug 31, 2026
Merged

perf: back delivery list reads with a composite (status, created_at_chain) index-#169
dev-fani merged 2 commits into
fanilabs:mainfrom
micmusjnr20:main

Conversation

@micmusjnr20

Copy link
Copy Markdown
Contributor

Closes #104
Closes #105
Closes #106

Problem

DeliveryRepository.list (src/modules/deliveries/infrastructure/prisma-delivery-repository.ts) filters on one column (status, optionally sender_address/recipient_address/driver_address) but sorts on another (created_at_chain DESC). The deliveries table had only four single-column indexes and no composite covering filter-plus-sort, so Postgres matched rows then ran a second-pass Sort on every read. Because the blockchain indexer is the table's only writer and growth is unbounded, this degrades monotonically as deliveries accumulate.

This closes the last open case from the filter-and-sort indexing audit — the notifications, audit_logs, escrows, and driver_profiles composites (older tickets #46/#101) are already present in the current tree.

Change

  • Add composite index deliveries(status, created_at_chain desc) so the planner resolves the predicate and the order from a single index.
  • Drop the single-column deliveries(status) index it fully supersedes (covered as the composite's leading column) — removes duplicate write/maintenance cost.
  • Keep sender_address/recipient_address/driver_address indexes: filter-only columns with no covering composite.
  • Document the complete read-path index rationale in docs/DATABASE.md (missing until now).

Verification

Migration 20260830183747_delivery_filter_sort_index applies cleanly and prisma migrate status reports no drift. Verified with EXPLAIN ANALYZE against a Dockerized Postgres seeded to ~60k deliveries:

  • Redundant deliveries_status_idx is gone; deliveries_status_created_at_chain_idx present.
  • The filter resolves through the composite's leading column (Index Cond: status = ...).
  • The paginated shape (ORDER BY created_at_chain DESC + LIMIT) becomes a sort-free ordered Index Scan:
 Limit  (cost=0.29..23.63 rows=50 ...)
   ->  Index Scan using deliveries_status_created_at_chain_idx
         Index Cond: (status = 'ACTIVE'::delivery_status)

Testing

  • pnpm typecheck: identical count of pre-existing errors to mainzero new.
  • pnpm test: unit suite shows no regression (unrelated pre-existing failures on main — indexer fake ReferenceError, notifications env typing; DB/Redis-dependent flakiness).
  • No schema drift.

Notes (out of scope)

  • prisma/seed.ts has a pre-existing prisma.escrow.upsert amount (BigInt vs Decimal) failure unrelated to this change — flagged so it isn't mistaken for a regression.
  • Note: opening against fanilabs/backend upstream returned a GitHub-side HTTP 500; opening against this fork's main instead (same base commit as upstream fanilabs/backend).

micmusjnr20 and others added 2 commits August 30, 2026 18:45
DeliveryRepository.list filters deliveries by status (and optionally
sender/recipient/driver address) but orders by createdAtChain desc, a shape
the four single-column indexes could not serve from one index — Postgres
filtered the matched rows then sorted them in a second pass on every read.
The indexer is the table's only writer and growth is unbounded, so this
degrades monotonically as deliveries accumulate.

Back the query with deliveries(status, created_at_chain desc) so the planner
resolves the predicate and the order from a single index, and drop the
single-column deliveries(status) index it fully supersedes (it is covered as
the composite's leading column) to avoid duplicate write/maintenance cost.
sender_address/recipient_address/driver_address indexes stay: they are
filter-only columns with no covering composite.

Verified against a Dockerized Postgres with ~60k seeded deliveries via
EXPLAIN ANALYZE: the filter resolves through
deliveries_status_created_at_chain_idx, and the paginated shape
(LIMIT/cursor) becomes a sort-free ordered index scan. The unbounded no-take
query still sorts all matches, which is inherent to missing pagination and
tracked separately (GitHub fanilabs#19); this change is not a regression there and
unlocks the sort-free path once a take lands.

Also documents the full read-path index rationale (notifications, audit_logs,
deliveries, escrows, driver_profiles) in docs/DATABASE.md.

Generated with Codebuff 🤖
Co-Authored-By: Codebuff <noreply@codebuff.com>
perf: back delivery list reads with a composite (status, created_at_chain) index
@drips-wave

drips-wave Bot commented Aug 30, 2026

Copy link
Copy Markdown

@micmusjnr20 Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@dev-fani
dev-fani merged commit faac974 into fanilabs:main Aug 31, 2026
1 of 2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants