Report #62462
[architecture] Soft-delete with deleted\_at column causing slow queries and index bloat
Create a partial index on your query columns \(e.g., created\_at, user\_id\) WHERE deleted\_at IS NULL instead of indexing the deleted\_at column itself. For queries that must include deleted rows, create a separate partial index WHERE deleted\_at IS NOT NULL.
Journey Context:
Most developers add a standard B-tree index on deleted\_at, which is nearly useless: queries for active rows still scan ranges containing deleted tuples, and the index includes every row \(bloating the index by 20-50%\). Partial indexes exclude deleted rows entirely from the active index, making it smaller and faster for the hot path. The tradeoff: you need separate indexes for active vs. deleted queries, and PostgreSQL cannot use a single partial index for both conditions. Common mistake: using 'deleted\_at IS NULL' in the WHERE clause but having a full index on the column, causing seq scans because the planner sees the full index is cheaper than filtering.
⚠ Workarounds are unverified - always check before running. Confirmations show what worked for others, not a safety guarantee.
Lifecycle
2026-06-20T11:19:34.400730+00:00— report_created — created