Skip to content

feat(amber): wait out Lakekeeper's asynchronous purge when deleting a warehouse - #7744

Open
mengw15 wants to merge 3 commits into
apache:mainfrom
mengw15:fix/7742-warehouse-delete-purge-retry
Open

feat(amber): wait out Lakekeeper's asynchronous purge when deleting a warehouse#7744
mengw15 wants to merge 3 commits into
apache:mainfrom
mengw15:fix/7742-warehouse-delete-purge-retry

Conversation

@mengw15

@mengw15 mengw15 commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

What changes were proposed in this PR?

LakekeeperClient.deleteWarehouseEmptyFirst empties the warehouse by dropping every table with purgeRequested=true, then immediately deletes the warehouse entity. Lakekeeper purges the dropped tables' data files asynchronously (task queue tabular_purge) and refuses to delete the warehouse while any purge is pending — the tasks need the warehouse's storage profile to reach S3, so deleting it first would orphan them and leak the files. It answers 409 WarehouseHasUnfinishedTasks until the queue drains, so in practice the first delete of any warehouse that has ever stored execution results always failed; a retry seconds later succeeded (details in #7742).

  • Retry the final warehouse delete on exactly this conflict: 409 with error.type == "WarehouseHasUnfinishedTasks" is treated as transient and retried with exponential backoff — 200ms doubling up to a 5s cap over 7 retries (0.2+0.4+0.8+1.6+3.2+5+5s ≈ 16s bound). A purge that drains almost immediately, the common case, costs the caller ~200ms rather than a full fixed interval; a genuinely slow one issues fewer requests than a fixed pause would. Every other error — including any other 409, and a 409 whose body isn't the expected JSON envelope — still fails immediately, and 404 stays the idempotent goal state.
  • The bound and delays are constructor parameters with defaults, so production call sites are unchanged and the spec injects a zero initial delay — doubling zero stays zero, so the tests contain no real sleeps and stay deterministic.

Found while testing the flag-gated per-user warehouse feature (#6870); no deployment is affected because the flag defaults to off.

Any related issues, documentation, discussions?

Closes #7742. Part of #6870; deleteWarehouseEmptyFirst introduced in #7473. Verified against the local Lakekeeper (0.11.0) that its management API exposes no task-query endpoint or force-delete option, so waiting out the documented conflict type is the only client-side path.

How was this PR tested?

  • LakekeeperClientSpec (in-process HTTP stub, no external infra) gains four cases mirroring the issue's repro: the stub answers the warehouse delete with 409 WarehouseHasUnfinishedTasks twice and then 204 — verified failing before the fix (the first 409 threw) and passing after, with the stub asserting exactly 3 delete attempts; a warehouse whose queue never drains fails after the bounded 1+3 attempts; a 409 of any other type fails on the first attempt with no retry; and a 409 whose body is not JSON (a gateway error page) also fails on the first attempt rather than being waited out.
  • Full spec run locally: 8/8 passed; WorkflowExecutionService/scalafmtCheck (main + Test) passes.
  • The initial retry delay is injected as 0 in all tests — no real sleeps, deterministic across runs.

Was this PR authored or co-authored using generative AI tooling?

Generated-by: Claude Code (claude-opus-4-8)

@github-actions

Copy link
Copy Markdown
Contributor

Backport auto-label report

This fix: PR was checked against each actively-supported release branch. release/* labels drive the post-merge backport, so add or remove one to change where this fix lands.

Release branch Analysis
⚠️ release/v1.2 Not labeled automatically — none of the files this PR modifies exist on this branch (amber/src/main/scala/org/apache/texera/web/service/LakekeeperClient.scala, amber/src/test/scala/org/apache/texera/web/service/LakekeeperClientSpec.scala). The fix may target code that isn't on this release, or the files were moved/renamed after the branch was cut. Please check and add release/v1.2 by hand if this fix should be backported here.

Auto-label run.

@github-actions

Copy link
Copy Markdown
Contributor

Automated Reviewer Suggestions

Based on the git blame history of the changed files, we recommend the following reviewers:

  • No candidates found from git blame history.

… warehouse

deleteWarehouseEmptyFirst drops every table with purgeRequested=true, then
immediately deletes the warehouse entity. Lakekeeper purges the dropped
tables' data files asynchronously (task queue `tabular_purge`) and refuses
to delete the warehouse while any purge is pending -- the tasks need the
warehouse's storage profile to reach S3, so deleting it first would orphan
them and leak the files. It answers 409 WarehouseHasUnfinishedTasks until
the queue drains, so the first delete of any warehouse that had ever stored
execution results always failed; a retry seconds later succeeded.

Retry the final warehouse delete on exactly that conflict -- 409 with
error.type WarehouseHasUnfinishedTasks -- with a bounded pause (default 10
retries x 2s; the queue normally drains within seconds). Every other error,
including any other 409, still fails immediately, and 404 stays the
idempotent goal state. The bound and delay are constructor parameters with
defaults, so production call sites are unchanged and the spec injects a
zero delay -- no real sleeps in the tests.

LakekeeperClientSpec covers the three outcomes against its in-process stub:
409-409-204 succeeds with exactly 3 attempts (failed before the fix on the
first 409), a never-draining queue fails after the bounded 1+3 attempts,
and a 409 of any other type fails on the first attempt with no retry.

Found while testing the flag-gated per-user warehouse feature; no
deployment is affected because the flag defaults to off.

Closes apache#7742.
@mengw15 mengw15 changed the title fix(amber): wait out Lakekeeper's asynchronous purge when deleting a warehouse feat(amber): wait out Lakekeeper's asynchronous purge when deleting a warehouse Aug 18, 2026
@mengw15
mengw15 force-pushed the fix/7742-warehouse-delete-purge-retry branch from e82ece1 to 6ce6cba Compare August 18, 2026 00:31
@mengw15 mengw15 removed the fix label Aug 18, 2026
@github-actions

github-actions Bot commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

⚠️ Benchmark changes need a look

🟢 4 better · 🔴 3 worse · ⚪ 8 noise (<±5%) · 0 without baseline

Compared against main 60300e3 benchmarked on this same runner, so the delta is largely free of cross-runner hardware noise. The "7d avg" column still reflects the gh-pages dashboard. Treat <±5% as noise unless repeated.

Dashboard · Run

config throughput MB/s latency max Δ latest / 7d
🔴 bs=10 sw=10 sl=64 367 0.224 26,328/38,549/38,549 us 🟢 -43.0% / 🔴 +141.1%
🔴 bs=100 sw=10 sl=64 824 0.503 119,393/147,794/147,794 us 🔴 +11.3% / 🔴 +37.7%
bs=1000 sw=10 sl=64 947 0.578 1,053,370/1,111,522/1,111,522 us ⚪ within ±5% / 🔴 -9.7%
Baseline details

Latest main 60300e3 from same runner

config metric PR latest main 7d avg Δ latest Δ 7d
bs=10 sw=10 sl=64 throughput 367 tuples/sec 345 tuples/sec 779.07 tuples/sec +6.4% -52.9%
bs=10 sw=10 sl=64 MB/s 0.224 MB/s 0.21 MB/s 0.476 MB/s +6.7% -52.9%
bs=10 sw=10 sl=64 p50 26,328 us 24,289 us 12,818 us +8.4% +105.4%
bs=10 sw=10 sl=64 p95 38,549 us 67,670 us 15,986 us -43.0% +141.1%
bs=10 sw=10 sl=64 p99 38,549 us 67,670 us 19,339 us -43.0% +99.3%
bs=100 sw=10 sl=64 throughput 824 tuples/sec 864 tuples/sec 1,011 tuples/sec -4.6% -18.5%
bs=100 sw=10 sl=64 MB/s 0.503 MB/s 0.527 MB/s 0.617 MB/s -4.6% -18.5%
bs=100 sw=10 sl=64 p50 119,393 us 114,934 us 100,965 us +3.9% +18.3%
bs=100 sw=10 sl=64 p95 147,794 us 132,833 us 107,295 us +11.3% +37.7%
bs=100 sw=10 sl=64 p99 147,794 us 132,833 us 115,531 us +11.3% +27.9%
bs=1000 sw=10 sl=64 throughput 947 tuples/sec 951 tuples/sec 1,049 tuples/sec -0.4% -9.7%
bs=1000 sw=10 sl=64 MB/s 0.578 MB/s 0.581 MB/s 0.64 MB/s -0.5% -9.7%
bs=1000 sw=10 sl=64 p50 1,053,370 us 1,053,787 us 978,248 us -0.0% +7.7%
bs=1000 sw=10 sl=64 p95 1,111,522 us 1,158,992 us 1,021,881 us -4.1% +8.8%
bs=1000 sw=10 sl=64 p99 1,111,522 us 1,158,992 us 1,050,075 us -4.1% +5.9%
Raw CSV
config_idx,batch_size,schema_width,string_len,num_batches,total_ms,total_tuples,total_bytes,tuples_per_sec,mb_per_sec,lat_p50_us,lat_p95_us,lat_p99_us
0,10,10,64,20,545.08,200,128000,367,0.224,26328.33,38548.94,38548.94
1,100,10,64,20,2427.06,2000,1280000,824,0.503,119392.83,147793.59,147793.59
2,1000,10,64,20,21123.08,20000,12800000,947,0.578,1053369.73,1111522.46,1111522.46

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds bounded retry handling for Lakekeeper’s asynchronous warehouse purge conflicts.

Changes:

  • Retries only 409 WarehouseHasUnfinishedTasks.
  • Adds deterministic success, exhaustion, and unrelated-conflict tests.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

File Description
LakekeeperClient.scala Implements bounded warehouse-delete retries.
LakekeeperClientSpec.scala Tests retry and failure behavior.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@codecov-commenter

codecov-commenter commented Aug 18, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 80.95238% with 4 lines in your changes missing coverage. Please review.
✅ Project coverage is 91.03%. Comparing base (60300e3) to head (9fc3dd4).
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
...g/apache/texera/web/service/LakekeeperClient.scala 80.95% 1 Missing and 3 partials ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##               main    #7744      +/-   ##
============================================
+ Coverage     91.02%   91.03%   +0.01%     
- Complexity     4454     4466      +12     
============================================
  Files          1174     1174              
  Lines         47146    47163      +17     
  Branches       5287     5292       +5     
============================================
+ Hits          42916    42937      +21     
+ Misses         2550     2549       -1     
+ Partials       1680     1677       -3     
Flag Coverage Δ *Carryforward flag
access-control-service 81.00% <ø> (ø) Carriedforward from 6ce6cba
agent-service 98.62% <ø> (ø) Carriedforward from 6ce6cba
amber 87.49% <80.95%> (+0.03%) ⬆️
computing-unit-managing-service 73.67% <ø> (ø) Carriedforward from 6ce6cba
config-service 86.73% <ø> (ø) Carriedforward from 6ce6cba
file-service 68.90% <ø> (ø) Carriedforward from 6ce6cba
frontend 92.59% <ø> (ø) Carriedforward from 6ce6cba
notebook-migration-service 83.74% <ø> (ø) Carriedforward from 6ce6cba
pyamber 97.57% <ø> (ø) Carriedforward from 6ce6cba
workflow-compiling-service 77.19% <ø> (ø) Carriedforward from 6ce6cba

*This pull request uses carry forward flags. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

The retry only fires for a 409 whose body carries the
WarehouseHasUnfinishedTasks type, and the type check parses that body --
so a 409 with a non-JSON body (a gateway error page, say) must read as
'not the purge conflict' and fail immediately rather than be waited out
as if it were transient. Assert it fails on the first attempt.
@github-actions github-actions Bot added the fix label Aug 18, 2026
…ueue

The wait was a fixed 2s x 10. That made the common case -- a purge that
drains almost immediately -- cost the caller a full 2s before the delete
was retried, on a request a user is waiting on, while still issuing 11
requests when the queue is genuinely slow.

Double the pause instead, from 200ms up to a 5s cap over 7 retries:
0.2+0.4+0.8+1.6+3.2+5+5s, so a fast purge returns in ~200ms, a slow one
issues fewer requests, and the overall bound drops from 20s to ~16s.
Tests still inject a zero initial delay -- doubling zero stays zero, so
they contain no real sleeps.
@mengw15
mengw15 requested a review from kunwp1 August 18, 2026 00:58
@mengw15 mengw15 removed the fix label Aug 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BYO-S3] Warehouse deletion should wait out Lakekeeper's asynchronous purge instead of failing with 409

3 participants