From f1cab41be1fa14ab47681a67aaaceb0a4c722e80 Mon Sep 17 00:00:00 2001 From: Heikki Hellgren Date: Mon, 7 Oct 2024 14:18:06 +0300 Subject: [PATCH] fix(catalog): update search table in transaction - run the delete and insert in transaction for entity search - mark the deferred stitching done only after search is also updated Signed-off-by: Heikki Hellgren --- .changeset/stale-ravens-clap.md | 5 +++++ .../operations/stitcher/performStitching.ts | 22 +++++++++---------- 2 files changed, 16 insertions(+), 11 deletions(-) create mode 100644 .changeset/stale-ravens-clap.md diff --git a/.changeset/stale-ravens-clap.md b/.changeset/stale-ravens-clap.md new file mode 100644 index 0000000000..3abb16a04d --- /dev/null +++ b/.changeset/stale-ravens-clap.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend': patch +--- + +Update catalog search table in transaction diff --git a/plugins/catalog-backend/src/database/operations/stitcher/performStitching.ts b/plugins/catalog-backend/src/database/operations/stitcher/performStitching.ts index bf9a1307bd..0832ba5db8 100644 --- a/plugins/catalog-backend/src/database/operations/stitcher/performStitching.ts +++ b/plugins/catalog-backend/src/database/operations/stitcher/performStitching.ts @@ -218,28 +218,28 @@ export async function performStitching(options: { .onConflict('entity_id') .merge(['final_entity', 'hash', 'last_updated_at']); - if (options.strategy.mode === 'deferred') { + const markDeferred = async () => { + if (options.strategy.mode !== 'deferred') { + return; + } await markDeferredStitchCompleted({ knex: knex, entityRef, stitchTicket, }); - } + }; if (amountOfRowsChanged === 0) { logger.debug(`Entity ${entityRef} is already stitched, skipping write.`); + await markDeferred(); return 'abandoned'; } - // TODO(freben): Search will probably need a similar safeguard against - // race conditions like the final_entities ticket handling above. - // Otherwise, it can be the case that: - // A writes the entity -> - // B writes the entity -> - // B writes search -> - // A writes search - await knex('search').where({ entity_id: entityId }).delete(); - await knex.batchInsert('search', searchEntries, BATCH_SIZE); + await knex.transaction(async trx => { + await trx('search').where({ entity_id: entityId }).delete(); + await trx.batchInsert('search', searchEntries, BATCH_SIZE); + }); + await markDeferred(); return 'changed'; }