draft(catalog-backend): search table dedup, covering indices, UNIQUE constraint
Single knex migration that cleans up duplicate search rows and creates covering indices including a UNIQUE constraint on (entity_id, key, value). Each step is idempotent and handles INVALID indices from interrupted runs. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Signed-off-by: Fredrik Adelöw <freben@spotify.com>
This commit is contained in:
@@ -0,0 +1,33 @@
|
||||
---
|
||||
'@backstage/plugin-catalog-backend': patch
|
||||
---
|
||||
|
||||
Added a migration that removes duplicate rows from the `search` table, creates covering indices for improved query performance, and adds a `UNIQUE` constraint on `(entity_id, key, value)`.
|
||||
|
||||
This is a long-running migration on large catalogs. On PostgreSQL with millions of search rows, the index creation may take 5-15 minutes. During this time, other pods running the previous version will continue to serve traffic normally — the index creation does not block reads or writes. If the pod is restarted during the migration, the next startup will clean up and retry automatically.
|
||||
|
||||
**For large installations**, you can run the following SQL commands against your PostgreSQL database before deploying to avoid the startup delay. If these have already completed, the migration will detect the existing indices and skip all work.
|
||||
|
||||
```sql
|
||||
-- Step 1: Remove duplicate search rows
|
||||
WITH cte AS (
|
||||
SELECT ctid, row_number() OVER (PARTITION BY entity_id, key, value) AS rn
|
||||
FROM search
|
||||
)
|
||||
DELETE FROM search USING cte WHERE search.ctid = cte.ctid AND cte.rn > 1;
|
||||
|
||||
-- Step 2: Create new indices (run each separately)
|
||||
CREATE UNIQUE INDEX CONCURRENTLY IF NOT EXISTS
|
||||
search_entity_key_value_idx ON search (entity_id, key, value);
|
||||
CREATE INDEX CONCURRENTLY IF NOT EXISTS
|
||||
search_key_value_entity_idx ON search (key, value, entity_id);
|
||||
CREATE INDEX CONCURRENTLY IF NOT EXISTS
|
||||
search_facets_covering_idx ON search (key, original_value, entity_id)
|
||||
WHERE original_value IS NOT NULL;
|
||||
|
||||
-- Step 3: Drop old indices that are no longer needed
|
||||
DROP INDEX CONCURRENTLY IF EXISTS search_key_value_idx;
|
||||
DROP INDEX CONCURRENTLY IF EXISTS search_key_original_value_idx;
|
||||
```
|
||||
|
||||
Also fixed `buildEntitySearch` to remove duplicate output for entities with duplicate array values, and added `ON CONFLICT DO UPDATE` to `syncSearchRows` so that concurrent stitching races are handled gracefully.
|
||||
Reference in New Issue
Block a user