Fix #226: defer REST catalog metadata deletion until commit succeeds#397
Conversation
|
@sfc-gh-npuka @sfc-gh-okalaci — would appreciate a review when you have a moment. This is a targeted fix for the deletion_queue duplicate key error you filed in #226. |
762c469 to
41506bb
Compare
|
I'm not entirely against it, but it masks the underlying issue that we inserted something into the deletion queue that is apparently still in use. It would be preferable to solve this by avoiding the deletion of files that might still be in use by an external REST catalog, after a failure. |
41506bb to
792a759
Compare
…mmit succeeds ON CONFLICT DO NOTHING was a band-aid. The root cause is that InsertDeletionQueueRecord was called at metadata-write time (PRE_COMMIT), before the REST catalog HTTP batch commit was sent. On a 429 retry the same old-metadata path was enqueued a second time, crashing with: ERROR: duplicate key value violates unique constraint "deletion_queue_pkey" Fix the root cause: 1. Revert the ON CONFLICT DO NOTHING from InsertDeletionQueueRecordExtended so the primary key is strict again. 2. In ApplyIcebergMetadataChanges, replace the immediate InsertDeletionQueueRecord call for writable REST tables with AddRestCatalogMetadataForDeferredDeletion, which accumulates the old path in PgLakeXactRestCatalog->metadataToEnqueueOnSuccess (TopTransactionContext) without touching the DB. 3. In PostAllRestCatalogRequests (XACT_EVENT_COMMIT), on HTTP 204 only, copy pending entries to the process-global confirmedRestCatalogDeletions list (TopMemoryContext). On 429 or any other failure the paths are silently discarded with the transaction context. 4. In IcebergXactCallback (XACT_EVENT_PRE_COMMIT), call DrainConfirmedRestCatalogDeletions before ConsumeTrackedIcebergMetadataChanges. The drain runs inside an active snapshot (required by SPI) and inserts confirmed paths from the previous successful transaction into deletion_queue. The net effect: orphaned metadata paths reach deletion_queue if and only if the REST commit that replaced them returned 204. Retries on 429 leave the queue untouched, so no duplicate key is possible. Fixes Snowflake-Labs#226 Signed-off-by: Richie Bachala <richie.bachala@snowflake.com>
792a759 to
877546c
Compare
|
Hi @sfc-gh-mslot — thanks for the feedback. I've rewritten the approach to address exactly that concern. The original Here's how it works now:
The |
| return; | ||
|
|
||
| /* Take ownership before any SQL work. */ | ||
| List *toProcess = confirmedRestCatalogDeletions; |
There was a problem hiding this comment.
If the process ends we lose this list, so this doesn't seem like a good solution.
Problem
InsertDeletionQueueRecordfor writable REST catalog tables was called duringXACT_EVENT_PRE_COMMIT(insideApplyIcebergMetadataChanges) — before theREST catalog HTTP batch commit was sent (which happens in
XACT_EVENT_COMMIT).On a 429 rate-limit retry,
PRE_COMMITfires again with the same metadatafile path already in the queue:
The previous approach (
ON CONFLICT (path) DO NOTHING) masked the crash.This PR fixes the root cause.
Approach
Defer the enqueue until after the REST commit returns HTTP 204:
ApplyIcebergMetadataChanges(metadata_operations.c) — replace thedirect
InsertDeletionQueueRecordcall withAddRestCatalogMetadataForDeferredDeletion, which appends the old metadatapath to a per-transaction pending list
(
PgLakeXactRestCatalog->metadataToEnqueueOnSuccess, inTopTransactionContext).PostAllRestCatalogRequests(track_iceberg_metadata_changes.c) — onHTTP 204 only, copy pending entries to the process-global
confirmedRestCatalogDeletionslist (TopMemoryContext). On 429 or anyother failure the paths are discarded with the transaction context — no
duplicate key is possible.
IcebergXactCallback/XACT_EVENT_PRE_COMMIT(transaction_hooks.c)— call
DrainConfirmedRestCatalogDeletionsbeforeConsumeTrackedIcebergMetadataChanges. The drain runs inside the activesnapshot brackets (required for SPI) and flushes confirmed paths from the
previous successful transaction into
deletion_queue.Files changed
pg_lake_iceberg/src/iceberg/metadata_operations.cInsertDeletionQueueRecordwithAddRestCatalogMetadataForDeferredDeletionvia forwardexterndeclaration (avoids circular build dependency —pg_lake_icebergcannot includepg_lake_tableheaders)pg_lake_table/include/pg_lake/transaction/track_iceberg_metadata_changes.hdatatype/timestamp.h; exported two new functionspg_lake_table/src/transaction/track_iceberg_metadata_changes.cRestCatalogPendingMetadataDeletionstruct,metadataToEnqueueOnSuccessfield,confirmedRestCatalogDeletionsglobal,AddRestCatalogMetadataForDeferredDeletion,DrainConfirmedRestCatalogDeletions, and 204-success promotion inPostAllRestCatalogRequestspg_lake_table/src/transaction/transaction_hooks.cDrainConfirmedRestCatalogDeletions()beforeConsumeTrackedIcebergMetadataChangesin PRE_COMMITpg_lake_table/tests/pytests/test_deletion_queue_idempotency.pyMemory model
TopTransactionContext— freed automatically at transaction end if REST commit fails.TopMemoryContexton 204 — survive across transaction boundaries until drained.Edge cases verified
AddRestCatalogMetadataForDeferredDeletioncallsInitRestCatalogRequestsHashIfNeeded(), ensuringPgLakeXactRestCatalogis non-NULL even whenBindRelationToXactRestCatalogwas never called.metadataPathisNULLfor new REST tables; theif (metadataPath)guard prevents any enqueue.!createNewSnapshot && !createNewTablefires before thewritableRestCatalogTableblock, soAddRestCatalogMetadataForDeferredDeletionis never reached.Fixes #226
Signed-off-by: Richie Bachala richie.bachala@snowflake.com