Skip to content

Add partial indexes on alert-dedup columns for scale #44

@TaprootFreak

Description

@TaprootFreak

Context

Follow-up to #42 (position-lifecycle watchers). The four `findUnalerted*` queries filter on the `*_alerted_at IS NULL` columns added in migration `0003_position_alert_dedup`, but no index was added on those columns. At current scale (~30 open positions) this is fine, but as the table grows historical-row count, the planner falls back to a sequential scan every 5 minutes.

Same applies to `findUnalertedExpired`: it has no upper bound on `expiration`, so the scan grows unboundedly with history.

Proposed fix

Add four partial indexes:

```sql
CREATE INDEX idx_pos_unalerted_mini ON position_states (expiration, created)
WHERE mini_lifetime_alerted_at IS NULL AND is_closed = false AND is_denied = false;

CREATE INDEX idx_pos_unalerted_expiring_soon ON position_states (expiration)
WHERE expiring_soon_alerted_at IS NULL AND is_closed = false AND is_denied = false;

CREATE INDEX idx_pos_unalerted_expired ON position_states (expiration)
WHERE expired_alerted_at IS NULL AND is_closed = false AND is_denied = false;

CREATE INDEX idx_pos_unalerted_phase2 ON position_states (expiration, challenge_period)
WHERE phase2_alerted_at IS NULL AND is_closed = false AND is_denied = false;
```

Plus an upper bound on `findUnalertedExpired` (e.g. `expiration > now - 30d`) so historical decayed positions drop out of the scan.

Acceptance

  • New migration `0004_alert_dedup_indexes` with the four partial indexes.
  • `findUnalertedExpired` adds a 30-day lower bound on `expiration`.
  • `EXPLAIN` on each query uses index scan rather than seq scan after migration.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions