Skip to content

feat(spark-3.5): [stacked] Add VACUUM SQL extension for Iceberg table maintenance - #661

Draft
mkuchenbecker wants to merge 5 commits into
mkuchenb/spark35-sql-standalonefrom
mkuchenb/spark35-sql-vacuum
Draft

feat(spark-3.5): [stacked] Add VACUUM SQL extension for Iceberg table maintenance#661
mkuchenbecker wants to merge 5 commits into
mkuchenb/spark35-sql-standalonefrom
mkuchenb/spark35-sql-vacuum

Conversation

@mkuchenbecker

@mkuchenbecker mkuchenbecker commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

OpenHouse spark-3.5 SQL Extensions Stack

PR Content
#660 Standalone refactor (decouple spark-3.5 from spark-3.1)
(this) VACUUM

Summary

Adds a VACUUM command to Spark 3.5 Openhouse SQL Extensions.

VACUUM <table> [REMOVE ORPHAN FILES] [RETAIN n HOURS]

Behaviour:
All files beyond the specified retention that are no longer referenced in the current version are deleted.

  1. Snapshot expiration always runs. Snapshot expiration will delete files with the command.
  2. REMOVE ORPHAN FILES opts into orphan-file deletion,
    run before expiration. This is because snapshot expiration can't run on a table out of quota (writes a snapshot), but orphaned file deletion can run. Orphaned file deletion uses a recursive list operation and is expensive. The command may OOM if the Driver is not large enough. We should consider Inventory Lists as an optional input in the future so the operation can be run as a SQL query distributed on the executors.
  3. RETAIN n HOURS bounds both operations via the procedures' older_than argument (resolved to a literal
    timestamp in the session time zone, since procedure arguments must be foldable); when omitted, each procedure falls back to its own default retention.

Thin sugar over the Iceberg stored-procedure CALL path: VacuumTableExec validates the
target is an OpenHouse table (openhouse.tableId) and issues
CALL <catalog>.system.expire_snapshots / remove_orphan_files, so procedure resolution
and argument binding reuse the existing CALL path.

Changes

  • Client-facing API Changes
  • Internal API Changes
  • Bug Fixes
  • New Features
  • Performance Improvements
  • Code Style
  • Refactoring
  • Documentation
  • Tests

New non-reserved keywords VACUUM / REMOVE / ORPHAN / FILES / RETAIN / HOURS;
existing identifiers with those names still parse.

Testing Done

  • Added new tests for the changes made.

VacuumStatementTest (real Iceberg, Hadoop catalog), 6 tests: RETAIN 0 HOURS collapses
three snapshots to one with rows intact; REMOVE ORPHAN FILES RETAIN 24 HOURS preserves
live data; default retention; lower-case; non-OpenHouse-table rejection; invalid-syntax
parse error.

./gradlew :integrations:spark:spark-3.5:openhouse-spark-3.5-itest:statementTest --tests '*VacuumStatementTest'
# BUILD SUCCESSFUL — 6/6 pass

Additional Information

  • Large PR broken into smaller PRs, and PR plan linked in the description.

Comment thread integrations/spark/spark-3.5/openhouse-spark-runtime/docs/VACUUM.md Outdated
Comment thread integrations/spark/spark-3.5/openhouse-spark-runtime/docs/VACUUM.md Outdated
Comment thread integrations/spark/spark-3.5/openhouse-spark-runtime/docs/VACUUM.md Outdated
Comment thread integrations/spark/spark-3.5/openhouse-spark-runtime/docs/VACUUM.md Outdated
Comment thread integrations/spark/spark-3.5/openhouse-spark-runtime/docs/VACUUM.md
Mike Kuchenbecker and others added 5 commits July 31, 2026 16:25
Adds a VACUUM command to the OpenHouse spark-3.5 SQL extensions:

  VACUUM <table> [REMOVE ORPHAN FILES] [RETAIN n HOURS]

Snapshot expiration always runs; REMOVE ORPHAN FILES opts into orphan-file
deletion, run after expiration so it cleans against the settled live-file set.
RETAIN n HOURS bounds both operations via the procedures' older_than argument
(resolved to a literal timestamp in the session time zone, since procedure
arguments must be foldable); when omitted, each procedure applies its own
default retention.

Implemented as thin sugar over the Iceberg stored-procedure CALL path:
VacuumTableExec validates the target is an OpenHouse table (openhouse.tableId
property) and issues CALL <catalog>.system.expire_snapshots / remove_orphan_files
via sparkSession.sql(...), so procedure resolution and argument binding reuse the
existing CALL path. New non-reserved keywords VACUUM/REMOVE/ORPHAN/FILES/RETAIN/
HOURS; existing identifiers with those names still parse.

Tested by VacuumStatementTest (real Iceberg, Hadoop catalog): RETAIN 0 HOURS
collapses three snapshots to one with rows intact, REMOVE ORPHAN FILES RETAIN 24
HOURS preserves live data, default retention, lower-case, non-OpenHouse-table
rejection, and invalid-syntax parse errors.

Co-authored-by: Copilot <[email protected]>
Snapshot expiration commits table metadata and therefore cannot run on a table
that is out of quota, whereas orphan-file deletion only removes unreferenced
files from storage and always can. Running orphan removal first ensures it still
executes in that case, and scanning against the pre-expiration referenced-file
set means it can never delete a file a still-live snapshot references.

Co-authored-by: Copilot <[email protected]>
VACUUM is an Alpha feature and is now opt-in per table. A table must set
'openhouse.vacuum.enabled' = 'true'; otherwise VACUUM throws
UnsupportedOperationException explaining how to enable it.

- VacuumTableExec: add the openhouse.vacuum.enabled gate + ENABLED_PROP constant.
- VacuumStatementTest: enable the property on the vacuumed table and add
  testVacuumNotEnabledThrows for an OpenHouse table that has not opted in.
- Add public documentation (docs/VACUUM.md): behavior, syntax, the Alpha opt-in
  knob, merge-on-read handling, examples, and caveats.

Co-authored-by: Copilot <[email protected]>
Co-authored-by: Mike Kuchenbecker <[email protected]>
When RETAIN is omitted, VACUUM previously fell back to the Iceberg procedure
defaults (5-day snapshot expiration, 3-day orphan-file deletion). Wire the
cutoffs to the correct OpenHouse table properties instead, with no change to
what the command does (it still cleans reclaimed files via the CALL procedures'
default behavior):

- Snapshot expiration honors the history policy in the 'policies' property:
  older_than = now - maxAge x granularity, plus retain_last => versions when set
  (defaults maxAge=3, granularity=DAY, versions=0, matching OpenHouse).
- Orphan-file deletion honors 'ofd.one_day_ttl.enabled' (1 day when set, else the
  3-day default).

An explicit RETAIN n HOURS still overrides both. Pure helpers (ofdRetainDays,
parseHistoryRetention, granularityToChrono) are unit-tested in VacuumTableExecTest;
VacuumStatementTest.testVacuumHonorsHistoryPolicyVersions proves end-to-end that a
history policy of versions=2 keeps exactly 2 snapshots.

Co-authored-by: Copilot <[email protected]>
@mkuchenbecker
mkuchenbecker force-pushed the mkuchenb/spark35-sql-vacuum branch from efc6820 to 153fc94 Compare July 31, 2026 23:25
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