Skip to content

feat: send Glue VersionId on Hive table updates (GLUE_SEND_VERSION_ID) - #149

Open
jamespfaulkner wants to merge 4 commits into
mainfrom
feat/gluesync-send-versionid
Open

jamespfaulkner wants to merge 4 commits into
mainfrom
feat/gluesync-send-versionid

Conversation

@jamespfaulkner

Copy link
Copy Markdown
Contributor

What

Adds an opt-in GLUE_SEND_VERSION_ID flag (default off) to the GlueSync listener. When enabled, the listener reads the current Glue VersionId (via GetTable) and passes it on UpdateTable requests for non-Iceberg (Hive) tables, so AWS records requestParameters.versionId on the emitted CloudTrail UpdateTable event.

Why

The emitted versionId is the version the table was at immediately before the update. Consumers of the Glue/CloudTrail event stream can use it to deterministically identify and fetch the pre-update table version (via GetTableVersion) when handling ALTER_TABLE events, instead of guessing. Today no versionId is emitted, so that pre-update state can't be resolved reliably.

How

  • GlueTableService.update() — for Hive tables, reads the current VersionId and sets it on the UpdateTable. Iceberg tables are skipped (identified by metadata_location), avoiding the extra GetTable and optimistic-locking overhead.
  • Optimistic-locking handling — Glue enforces VersionId as a compare-and-swap, so a concurrent writer fails the write with ConcurrentModificationException. The retry/fallback control flow is extracted into a small, independently-tested OptimisticUpdateRetry: refresh the version and retry up to a bound, then fall back to an unconditional update so the sync never regresses below today's last-write-wins behaviour. The fallback is observable via a new version_conflict_fallback value on the glue_listener_event outcome tag.
  • Archive retention — for the carried versionId to resolve to fetchable content, the prior version must be archived, so when the flag is on, Hive tables are forced to skipArchive=false. An explicit per-table apiary.gluesync.skipArchive property still wins; Iceberg tables keep skipArchive=true (avoids table-version explosion).

Behaviour is unchanged when the flag is unset (default), and the flag is honoured identically by the listener and the resync CLI.

Testing

  • New OptimisticUpdateRetryTest — unit-tests the retry logic in isolation (success first try, retry-then-succeed, all-conflict → single fallback, non-conflict exception propagation, single-attempt, invalid maxAttempts).
  • Extended GlueTableServiceTest / GluePartitionServiceTest — versionId set/skipped (Hive vs Iceberg vs disabled), conflict retry + fallback, skipArchive precedence, env-var parsing.
  • Full reactor mvn clean install green (146 tests in this module, 0 failures).

Docs

README (GLUE_SEND_VERSION_ID env var + "Table update VersionId" section + updated SkipArchive precedence) and CHANGELOG updated.

🤖 Generated with Claude Code

jamespfaulkner and others added 2 commits October 7, 2026 12:50
Add an opt-in GLUE_SEND_VERSION_ID flag (default off) that makes the
GlueSync listener read the current Glue VersionId and pass it on
UpdateTable requests for non-Iceberg (Hive) tables, so AWS records
requestParameters.versionId on the emitted CloudTrail event. Consumers of
that event stream can then identify and fetch the pre-update table version
for ALTER_TABLE handling.

Glue enforces VersionId as a compare-and-swap, so a concurrent writer fails
with ConcurrentModificationException. The retry/fallback control flow is
extracted into OptimisticUpdateRetry (pure, independently tested): refresh
the version and retry up to a bound, then fall back to an unconditional
update so the sync never regresses below today's last-write-wins behaviour
(surfaced via the version_conflict_fallback outcome tag).

When the flag is on, Hive tables are forced to skipArchive=false so the
prior version is retained and the carried VersionId resolves via
GetTableVersion; an explicit per-table apiary.gluesync.skipArchive property
still wins. Iceberg tables are unaffected.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@jamespfaulkner
jamespfaulkner requested a review from a team as a code owner October 7, 2026 11:58
jamespfaulkner and others added 2 commits October 7, 2026 13:04
Previously a non-EntityNotFound failure of the getTable read (throttle,
timeout, access denied, etc.) added by the versionId path propagated as a
sync failure for that event — a new failure surface vs the single-call
behaviour before the flag. The read is best-effort, so readVersionId now
returns null on any AmazonClientException (other than EntityNotFound, which
still propagates so the caller creates the missing table), and the update
proceeds unconditionally. Surfaced via a new version_unavailable_fallback
outcome tag.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…come

retryOnConflict now returns the result of whichever branch ran (Supplier<T>),
so each branch returns its own UpdateOutcome directly instead of signalling
"version unavailable" through a mutable boolean[] captured in the lambda.

Also add an end-to-end test proving that, with GLUE_SEND_VERSION_ID on, an
ALTER_TABLE for a table missing in Glue (EntityNotFoundException from the
getTable version read) still drives a create via updateOrCreateTable.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant