Skip to Content
We are live but in Staging 🎉

Maintenance — API Reference

Package: dodil.k3.tables.v1 · Service: TableService

Five maintenance RPCs covering the operational lifecycle of a Delta table. K3 runs the write-log → Delta drain automatically; you’ll reach for these for explicit file packing, time travel, and reading the commit log.

RPCHTTPWhen to call
OptimizePOST /:bucket/tables/:table_name/optimizeAfter heavy writes — bin-pack small files into larger ones (faster reads)
VacuumPOST /:bucket/tables/:table_name/vacuumReclaim space — remove old file versions past retention
CompactPOST /:bucket/tables/:table_name/_compactForce the write log to drain into Delta now (instead of waiting for the automatic tick)
RestorePOST /:bucket/tables/:table_name/restoreTime travel — roll the table back to a previous Delta version
HistoryGET /:bucket/tables/:table_name/historyRead the Delta commit log

gRPC setup — grpcurl, endpoints, reflection, and field-name casing — is covered once in Conventions → Using gRPC.

Optimize

Compacts small Delta files into larger ones. Two modes:

  • Bin-pack (default) — coalesce small files up to target_file_size_mb per partition.
  • Z-order — rewrite all files clustered by the given columns. Improves read locality for queries that filter on those columns. Set z_order_columns to opt in.

Request

Bin-pack with default 128 MB target:

curl -sS -X POST "https://k3.dev.dodil.io/kb-prod/tables/events/optimize" \ -H "Authorization: Bearer $DODIL_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "bucket": "kb-prod", "tableName": "events", "targetFileSizeMb": "128" }'

Z-order variant:

curl -sS -X POST "https://k3.dev.dodil.io/kb-prod/tables/events/optimize" \ -H "Authorization: Bearer $DODIL_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "bucket": "kb-prod", "tableName": "events", "zOrderColumns": ["event_type", "user_id"] }'

Response

{ "version": "44", "optimizeType": "compact", "filesAdded": "1", "filesRemoved": "12", "partitionsOptimized": "2", "totalConsideredFiles": "16", "totalFilesSkipped": "4", "numBatches": "1", "bytesAdded": "184729302", "bytesRemoved": "212983040" }

Reading the response:

FieldMeaning
bytes_removed - bytes_addedCompaction savings — typically positive on small-files-heavy tables
total_files_skipped / total_considered_filesSkip ratio — high values mean the table was already well-packed
partitions_optimized0 = no-op

When to call: after large bulk writes / nightly batches / migrations. Routinely-compacted HTAP tables stay well-packed automatically.

Vacuum

Permanently delete old file versions past retention. This is destructive — vacuumed files are gone, and the corresponding Delta versions can no longer be restored. Default retention is 168 h (7 days).

Request

Dry-run first:

curl -sS -X POST "https://k3.dev.dodil.io/kb-prod/tables/events/vacuum" \ -H "Authorization: Bearer $DODIL_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "bucket": "kb-prod", "tableName": "events", "retentionHours": "168", "dryRun": true }'

Real run:

curl -sS -X POST "https://k3.dev.dodil.io/kb-prod/tables/events/vacuum" \ -H "Authorization: Bearer $DODIL_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "bucket": "kb-prod", "tableName": "events", "retentionHours": "168" }'

Response

{ "version": "44", "filesDeleted": "12", "filesDeletedPaths": [ "s3://kb-prod/.../events/part-00000-old.parquet", "s3://kb-prod/.../events/part-00001-old.parquet" ], "dryRun": false }

disable_retention_check bypasses Delta’s safety net — Delta refuses to vacuum files younger than 168 h because in-flight readers / time-travel queries break once the files are gone. Set true only for forced cleanups on quiesced tables where you accept that risk.

Vacuum interacts with Restore — once you vacuum past version N, restore to version < N is no longer possible. See Recipes → Time travel + restore for the safe pattern.

Compact

Force the write log to drain into Delta now. K3 runs this automatically in the background; you’d call it manually for:

  • After bulk writes — when you want the rows materialized in Delta immediately for analytical reads
  • Before maintenanceCompact then Optimize is the canonical sequence for shrinking files post-batch
  • Tests / e2e checks — drain the log to assert against DescribeTable.last_drain_* counters

Request

curl -sS -X POST "https://k3.dev.dodil.io/kb-prod/tables/events/_compact" \ -H "Authorization: Bearer $DODIL_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "bucket": "kb-prod", "tableName": "events", "batchSize": "10000" }'

Response

{ "tableName": "events", "walEntriesProcessed": "247", "walUniqueKeys": "189", "rowsMerged": "189", "rowsRejected": "0", "tombstonesSeen": "12", "truncated": false, "lastDrainTargetVersion": "43" }

truncated = true: the call hit batch_size and the log has more entries. Re-run until it returns false — common pattern in operator scripts.

wal_unique_keys < wal_entries_processed is normal — the compactor dedupes multiple writes to the same key (newest-wins) before MERGE.

Restore

Time travel — roll the table back to a previous Delta version. Identify the target version either by number (from History) or by timestamp (mutually exclusive).

Request

By version:

curl -sS -X POST "https://k3.dev.dodil.io/kb-prod/tables/events/restore" \ -H "Authorization: Bearer $DODIL_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "bucket": "kb-prod", "tableName": "events", "version": "40" }'

By timestamp:

curl -sS -X POST "https://k3.dev.dodil.io/kb-prod/tables/events/restore" \ -H "Authorization: Bearer $DODIL_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "bucket": "kb-prod", "tableName": "events", "restoreTimestamp": "2026-05-27T08:00:00Z" }'

Response

{ "versionBefore": "43", "versionAfter": "44" }

A restore is itself a commit — produces a new Delta version (version_after) that contains the state of version_before. You can restore the restore (forward through history) until you Vacuum the intervening versions.

History

Read the Delta commit log. Use version as the cursor; before_version pages backwards through history.

Request

Recent activity:

curl -sS "https://k3.dev.dodil.io/kb-prod/tables/events/history?limit=50" \ -H "Authorization: Bearer $DODIL_TOKEN"

Paginate backwards:

curl -sS "https://k3.dev.dodil.io/kb-prod/tables/events/history?limit=50&before_version=20" \ -H "Authorization: Bearer $DODIL_TOKEN"

Response

{ "currentVersion": "43", "entries": [ { "version": "43", "timestamp": "1716843600000", "operation": "MERGE", "parameters": { "predicate": "id = source.id AND user_id = source.user_id" }, "operationMetrics": { "numTargetRowsInserted": "5", "numTargetRowsUpdated": "3", "numTargetRowsDeleted": "0" }, "readVersion": "42" }, { "version": "42", "operation": "OPTIMIZE", "operationMetrics": { "numFilesAdded": "1", "numFilesRemoved": "12" } } ], "hasMore": true, "oldestReturnedVersion": "42" }

HistoryEntry shape is documented in Core Concepts → HistoryEntry — includes version, timestamp, operation, structured parameters, operation_metrics, user_metadata, engine_info, and read_version.

Use version for ordering — clock skew across writers can cause tiny non-monotonicity in timestamp. Use before_version as the pagination cursor.

operation_metrics keys vary by operationnumTargetRowsInserted/Updated/Deleted for MERGE/UPDATE; numFilesAdded/Removed for OPTIMIZE; numCopiedRows for RESTORE; etc. See Delta’s commitInfo schema for the full vocabulary.


See also