perf(db): GIN trigram-индексы строятся CONCURRENTLY вне транзакции
2 GIN trigram-индекса (pages.title, pages.text_content) + users.name строились plain CREATE INDEX внутри Kysely-транзакции миграции: SHARE-lock блокирует записи на pages/users на минуты при автодеплое. Kysely гоняет миграцию в транзакции, а CONCURRENTLY внутри транзакции нельзя. Поэтому ensureConcurrentIndexes (concurrent-indexes.ts) пре-строит эти индексы через CREATE INDEX CONCURRENTLY (raw, вне транзакции) ДО миграатора — на существующей БД миграционный CREATE INDEX IF NOT EXISTS становится no-op и лок не берётся. Best-effort: на свежей БД (нет pages/f_unaccent) пре-build молча пропускается, а миграция строит индекс на пустой таблице. Худший случай = прежнее поведение, лучший — без лока. CONCURRENT_INDEXES — канонические определения; drift-guard тест сверяет их с выражениями в миграциях. Тест раннера: CONCURRENTLY+IF NOT EXISTS, вне транзакции, best-effort (падение одного не рвёт остальные). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -33,16 +33,18 @@ import { type Kysely, sql } from 'kysely';
|
||||
* - comments: `findPageComments` does WHERE page_id ORDER BY id ASC, but only
|
||||
* `(page_id)` exists → extra sort.
|
||||
*
|
||||
* DEPLOY-TIME LOCK WARNING: these are plain (non-CONCURRENT) CREATE INDEX
|
||||
* statements — CONCURRENTLY is impossible because Kysely runs each migration in a
|
||||
* transaction. They take a SHARE lock that BLOCKS writes (INSERT/UPDATE/DELETE) on
|
||||
* pages/users/groups/comments/page_history for the duration of the build. The two
|
||||
* GIN trigram builds on pages.title / users.name are the slow ones and can take
|
||||
* minutes on a large tenant → a write-outage window during the deploy migration.
|
||||
* For large installations, run this migration in a maintenance window, or build
|
||||
* the trigram indexes out-of-band with CREATE INDEX CONCURRENTLY before deploying
|
||||
* (then this migration's `IF NOT EXISTS` is a no-op). Small/typical tenants are
|
||||
* unaffected.
|
||||
* DEPLOY-TIME LOCK: these are plain (non-CONCURRENT) CREATE INDEX statements —
|
||||
* CONCURRENTLY is impossible HERE because Kysely runs each migration in a
|
||||
* transaction. The two GIN trigram builds on pages.title / users.name are the
|
||||
* slow ones and would take a SHARE lock that BLOCKS writes on pages/users for
|
||||
* minutes on a large tenant. To avoid that, `ensureConcurrentIndexes`
|
||||
* (database/concurrent-indexes.ts) now pre-builds BOTH trigram indexes with
|
||||
* CREATE INDEX CONCURRENTLY (no transaction) BEFORE the migrator runs, so on an
|
||||
* existing DB the two `IF NOT EXISTS` trigram creates below no-op and no write
|
||||
* lock is taken. On a fresh DB the pre-build is skipped and they build on an
|
||||
* empty table. Keep the two trigram creates in lockstep with their CANONICAL
|
||||
* definitions in CONCURRENT_INDEXES. (The plain b-tree indexes further down are
|
||||
* fast metadata-only builds; they are not pre-built.)
|
||||
*/
|
||||
export async function up(db: Kysely<any>): Promise<void> {
|
||||
// Index-compatible, output-identical redefinition of f_unaccent (see header).
|
||||
|
||||
@@ -26,13 +26,16 @@ import { type Kysely, sql } from 'kysely';
|
||||
* to update than b-trees); on the small instances this fork targets that cost
|
||||
* is acceptable and the read win on agent lookups is the priority.
|
||||
*
|
||||
* DEPLOY-TIME LOCK WARNING: plain (non-CONCURRENT) CREATE INDEX — Kysely runs
|
||||
* each migration in a transaction, so CONCURRENTLY is impossible. The build takes
|
||||
* a SHARE lock that BLOCKS writes on `pages` for its duration. The text_content
|
||||
* GIN build is the slow one and can take minutes on a large tenant. For large
|
||||
* installations, run this in a maintenance window or build the index out-of-band
|
||||
* with CREATE INDEX CONCURRENTLY before deploying (then `IF NOT EXISTS` no-ops
|
||||
* here). Small/typical tenants are unaffected.
|
||||
* DEPLOY-TIME LOCK: this is a plain (non-CONCURRENT) CREATE INDEX — Kysely runs
|
||||
* each migration in a transaction, so CONCURRENTLY is impossible HERE, and the
|
||||
* build would take a SHARE lock that BLOCKS writes on `pages` for its duration
|
||||
* (the text_content GIN build can take minutes on a large tenant). To avoid that,
|
||||
* `ensureConcurrentIndexes` (database/concurrent-indexes.ts) now pre-builds this
|
||||
* index with CREATE INDEX CONCURRENTLY (no transaction) BEFORE the migrator runs,
|
||||
* so on an existing DB the `IF NOT EXISTS` below no-ops and no write lock is taken.
|
||||
* On a fresh DB the pre-build is skipped and this builds it on an empty table
|
||||
* where the lock is irrelevant. Keep this create in lockstep with the CANONICAL
|
||||
* definition in CONCURRENT_INDEXES (same expression + opclass).
|
||||
*/
|
||||
export async function up(db: Kysely<any>): Promise<void> {
|
||||
// The title predicate is served by #348's idx_pages_title_trgm — see header.
|
||||
|
||||
Reference in New Issue
Block a user