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.
| RPC | HTTP | When to call |
|---|---|---|
Optimize | POST /:bucket/tables/:table_name/optimize | After heavy writes — bin-pack small files into larger ones (faster reads) |
Vacuum | POST /:bucket/tables/:table_name/vacuum | Reclaim space — remove old file versions past retention |
Compact | POST /:bucket/tables/:table_name/_compact | Force the write log to drain into Delta now (instead of waiting for the automatic tick) |
Restore | POST /:bucket/tables/:table_name/restore | Time travel — roll the table back to a previous Delta version |
History | GET /:bucket/tables/:table_name/history | Read 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_mbper partition. - Z-order — rewrite all files clustered by the given columns. Improves read locality for queries that filter on those columns. Set
z_order_columnsto opt in.
Request
HTTP
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
HTTP
{
"version": "44",
"optimizeType": "compact",
"filesAdded": "1",
"filesRemoved": "12",
"partitionsOptimized": "2",
"totalConsideredFiles": "16",
"totalFilesSkipped": "4",
"numBatches": "1",
"bytesAdded": "184729302",
"bytesRemoved": "212983040"
}Reading the response:
| Field | Meaning |
|---|---|
bytes_removed - bytes_added | Compaction savings — typically positive on small-files-heavy tables |
total_files_skipped / total_considered_files | Skip ratio — high values mean the table was already well-packed |
partitions_optimized | 0 = 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
HTTP
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
HTTP
{
"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_checkbypasses 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. Settrueonly 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 maintenance —
CompactthenOptimizeis the canonical sequence for shrinking files post-batch - Tests / e2e checks — drain the log to assert against
DescribeTable.last_drain_*counters
Request
HTTP
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
HTTP
{
"tableName": "events",
"walEntriesProcessed": "247",
"walUniqueKeys": "189",
"rowsMerged": "189",
"rowsRejected": "0",
"tombstonesSeen": "12",
"truncated": false,
"lastDrainTargetVersion": "43"
}
truncated = true: the call hitbatch_sizeand the log has more entries. Re-run until it returnsfalse— 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
HTTP
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
HTTP
{
"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
HTTP
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
HTTP
{
"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 operation — numTargetRowsInserted/Updated/Deleted for MERGE/UPDATE; numFilesAdded/Removed for OPTIMIZE; numCopiedRows for RESTORE; etc. See Delta’s commitInfo schema for the full vocabulary.
See also
- Execute —
Executereturns theversionafter every write — useful as a cursor for the nextHistorypage - Tables → DescribeTable — sidecar stats incl.
last_drain_*row classification + write-log backlog - Recipes → Time travel + Restore —
History+Restore+Vacuuminteraction grpcurlreference — full flag set + reflection-disabled fallbacks