Skip to content

feat(storage): warehouse REST API, Lakekeeper client, and per-execution injection - #7473

Open
mengw15 wants to merge 4 commits into
apache:mainfrom
mengw15:feat/6932-warehouse-rest-backend
Open

feat(storage): warehouse REST API, Lakekeeper client, and per-execution injection#7473
mengw15 wants to merge 4 commits into
apache:mainfrom
mengw15:feat/6932-warehouse-rest-backend

Conversation

@mengw15

@mengw15 mengw15 commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

What changes were proposed in this PR?

The backend of the per-user warehouse feature (umbrella #6870), all gated by warehouseEnabled (default off — nothing changes for existing deployments):

  • Warehouse management APIWarehouseResource: GET /warehouse/status (always answers, so the frontend can hide the feature), POST /warehouse and DELETE /warehouse/{whid} (403 while the flag is off). Create validates the URI-safe name, mints the catalog name user-<uid>-<name>, creates in Lakekeeper first and records the row after — a failed creation leaves no orphaned state.
  • LakekeeperClient — management-API create (Local flavor: storage profile on the deployment's own object store, per-warehouse key prefix, STS off) and empty-first delete: drop every table with purgeRequested=true, then the namespaces, then the warehouse entity.
  • Per-execution injectionWorkflowExecuteRequest gains warehouseId: Option[Int]; WorkflowService.resolveWarehouseName checks ownership and refuses an explicit pick while the feature is off (never a silent fallback — [BYO-S3] Feature flag (storage.warehouse.enabled, default off) #6930); the resolved name rides WorkflowContext.warehouse into every storage URI (results, runtime statistics, console messages); the chosen whid is recorded on workflow_executions (as cuid is today) so the picker can preselect the workflow's last-used warehouse. whid is ON DELETE SET NULL: deleting a warehouse purges its data, never the execution history.
  • Explicit read failureWarehouseReadGuard: paginating a /wh/<name>/… result while the feature is off fails naming the warehouse, instead of resolving against the shared warehouse and surfacing "table not found" ([BYO-S3] Feature flag (storage.warehouse.enabled, default off) #6930).

Any related issues, documentation, discussions?

Closes #6932. Part of #6870 (design discussions #5293 and #6040). Builds on #6944, #7359 and #7386.

How was this PR tested?

Five specs, 30 cases green locally (sbt "WorkflowExecutionService/testOnly *LakekeeperClientSpec *WarehouseResourceSpec *WarehouseReadGuardSpec *WorkflowServiceWarehouseSpec *ExecutionsMetadataPersistServiceSpec"): the Lakekeeper client runs against an in-process HTTP stub (create-payload shape; the purge → namespace → warehouse delete order); the resource spec covers the disabled gate and the create/list/delete flow on MockTexeraDB with a stubbed client; resolution pins ownership and the no-silent-fallback rule; the read guard pins the explicit failure message; the executions spec gains whid recording and the SET-NULL-on-delete case. A delete-order assertion was deliberately broken once to confirm it fails red.

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

Generated-by: Claude Code (claude-fable-5)

@github-actions

github-actions Bot commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Automated Reviewer Suggestions

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

  • Contributors with relevant context: @Yicong-Huang, @Ma77Ball, @aglinxinyuan
    You can notify them by mentioning @Yicong-Huang, @Ma77Ball, @aglinxinyuan in a comment.

@mengw15
mengw15 requested a balanced review from Copilot August 9, 2026 15:43
@mengw15
mengw15 marked this pull request as draft August 9, 2026 15:43
@codecov-commenter

codecov-commenter commented Aug 9, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 88.12500% with 19 lines in your changes missing coverage. Please review.
✅ Project coverage is 86.78%. Comparing base (a7f4386) to head (538beb4).

Files with missing lines Patch % Lines
...e/dashboard/user/warehouse/WarehouseResource.scala 91.89% 3 Missing and 3 partials ⚠️
...rg/apache/texera/web/service/WorkflowService.scala 50.00% 4 Missing ⚠️
...he/texera/web/resource/SyncExecutionResource.scala 0.00% 3 Missing ⚠️
...che/texera/web/service/ExecutionStatsService.scala 0.00% 2 Missing ⚠️
...a/org/apache/texera/web/TexeraWebApplication.scala 0.00% 1 Missing ⚠️
...ard/user/workflow/WorkflowExecutionsResource.scala 75.00% 1 Missing ⚠️
...he/texera/web/service/ExecutionResultService.scala 0.00% 1 Missing ⚠️
...g/apache/texera/web/service/LakekeeperClient.scala 97.87% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##               main    #7473      +/-   ##
============================================
+ Coverage     86.75%   86.78%   +0.03%     
- Complexity     4226     4245      +19     
============================================
  Files          1170     1173       +3     
  Lines         46807    46910     +103     
  Branches       5211     5213       +2     
============================================
+ Hits          40606    40712     +106     
+ Misses         4496     4485      -11     
- Partials       1705     1713       +8     
Flag Coverage Δ *Carryforward flag
access-control-service 70.00% <ø> (ø)
agent-service 89.01% <ø> (ø) Carriedforward from 29d7cd6
amber 81.97% <88.12%> (+0.05%) ⬆️
computing-unit-managing-service 60.38% <ø> (ø)
config-service 65.97% <ø> (ø)
file-service 69.05% <ø> (ø)
frontend 89.58% <ø> (+0.06%) ⬆️ Carriedforward from 29d7cd6
notebook-migration-service 78.89% <ø> (ø)
pyamber 97.56% <ø> (+0.04%) ⬆️ Carriedforward from 29d7cd6
workflow-compiling-service 26.31% <ø> (ø)

*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.

@github-actions

github-actions Bot commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

⚠️ Benchmark changes need a look

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

Compared against main a7f4386 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 382 0.233 24,517/36,477/36,477 us 🔴 +32.1% / 🔴 +119.5%
bs=100 sw=10 sl=64 915 0.558 107,620/143,289/143,289 us ⚪ within ±5% / 🔴 +26.6%
bs=1000 sw=10 sl=64 1,092 0.666 923,934/950,496/950,496 us ⚪ within ±5% / 🟢 -14.8%
Baseline details

Latest main a7f4386 from same runner

config metric PR latest main 7d avg Δ latest Δ 7d
bs=10 sw=10 sl=64 throughput 382 tuples/sec 445 tuples/sec 740.3 tuples/sec -14.2% -48.4%
bs=10 sw=10 sl=64 MB/s 0.233 MB/s 0.271 MB/s 0.452 MB/s -14.0% -48.4%
bs=10 sw=10 sl=64 p50 24,517 us 22,210 us 13,173 us +10.4% +86.1%
bs=10 sw=10 sl=64 p95 36,477 us 27,603 us 16,615 us +32.1% +119.5%
bs=10 sw=10 sl=64 p99 36,477 us 27,603 us 19,272 us +32.1% +89.3%
bs=100 sw=10 sl=64 throughput 915 tuples/sec 937 tuples/sec 939.86 tuples/sec -2.3% -2.6%
bs=100 sw=10 sl=64 MB/s 0.558 MB/s 0.572 MB/s 0.574 MB/s -2.4% -2.7%
bs=100 sw=10 sl=64 p50 107,620 us 104,646 us 106,062 us +2.8% +1.5%
bs=100 sw=10 sl=64 p95 143,289 us 138,612 us 113,212 us +3.4% +26.6%
bs=100 sw=10 sl=64 p99 143,289 us 138,612 us 123,853 us +3.4% +15.7%
bs=1000 sw=10 sl=64 throughput 1,092 tuples/sec 1,104 tuples/sec 966.11 tuples/sec -1.1% +13.0%
bs=1000 sw=10 sl=64 MB/s 0.666 MB/s 0.674 MB/s 0.59 MB/s -1.2% +12.9%
bs=1000 sw=10 sl=64 p50 923,934 us 903,560 us 1,038,733 us +2.3% -11.1%
bs=1000 sw=10 sl=64 p95 950,496 us 939,928 us 1,085,289 us +1.1% -12.4%
bs=1000 sw=10 sl=64 p99 950,496 us 939,928 us 1,115,555 us +1.1% -14.8%
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,523.10,200,128000,382,0.233,24516.56,36476.72,36476.72
1,100,10,64,20,2186.89,2000,1280000,915,0.558,107620.48,143288.73,143288.73
2,1000,10,64,20,18318.72,20000,12800000,1092,0.666,923934.32,950495.74,950495.74

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 backend support for per-user Lakekeeper warehouses, including management APIs, execution routing, persistence, and feature gating.

Changes:

  • Adds warehouse CRUD APIs and Lakekeeper integration.
  • Propagates warehouse selection through execution storage and metadata.
  • Adds schema migration and warehouse-focused tests.

Reviewed changes

Copilot reviewed 23 out of 23 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
sql/updates/33.sql Adds execution warehouse reference.
sql/texera_ddl.sql Updates base execution schema.
sql/changelog.xml Registers migration 33.
common/config/.../StorageConfig.scala Exposes warehouse bucket configuration and testable gate.
amber/src/test/.../WorkflowServiceWarehouseSpec.scala Tests warehouse resolution and ownership.
amber/src/test/.../WorkflowExecutionServiceSpec.scala Updates execution request fixture.
amber/src/test/.../WarehouseReadGuardSpec.scala Tests disabled read behavior.
amber/src/test/.../LakekeeperClientSpec.scala Tests management payload and deletion order.
amber/src/test/.../ExecutionsMetadataPersistServiceSpec.scala Tests warehouse persistence and deletion behavior.
amber/src/test/.../WarehouseResourceSpec.scala Tests warehouse REST operations.
amber/src/main/.../TexeraWebApplication.scala Registers the warehouse resource.
amber/src/main/.../WorkflowService.scala Resolves and injects selected warehouses.
amber/src/main/.../WarehouseReadGuard.scala Implements warehouse read gating.
amber/src/main/.../LakekeeperClient.scala Implements Lakekeeper management operations.
amber/src/main/.../ExecutionStatsService.scala Scopes runtime statistics by warehouse.
amber/src/main/.../ExecutionsMetadataPersistService.scala Records execution warehouse IDs.
amber/src/main/.../ExecutionResultService.scala Guards warehouse result pagination.
amber/src/main/.../ExecutionConsoleService.scala Scopes console storage by warehouse.
amber/src/main/.../SyncExecutionResource.scala Supplies the default warehouse selection.
amber/src/main/.../WarehouseResource.scala Provides warehouse CRUD endpoints.
amber/src/main/.../WorkflowExecuteRequest.scala Adds warehouse selection to execution requests.
amber/src/main/.../ExpansionGreedyScheduleGenerator.scala Scopes generated result URIs.
amber/src/main/.../CostBasedScheduleGenerator.scala Scopes generated result URIs.

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

Comment thread amber/src/main/scala/org/apache/texera/web/service/ExecutionResultService.scala Outdated
@mengw15
mengw15 marked this pull request as ready for review August 10, 2026 18:53
@mengw15
mengw15 requested a review from kunwp1 August 10, 2026 18:53

@kunwp1 kunwp1 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.

Left some comments. Do you have to also work on python side as well?

storageUriOption match {
case Some(storageUri) =>
// Refuse to read a per-user-warehouse result while the feature is off (#6930).
WarehouseReadGuard.assertReadable(storageUri)

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.

This assert error won't propagate because the catch below turns all the Exception to None. Is it intentional?


uriOption.flatMap { uri =>
// Refuse to read per-user-warehouse console messages while the feature is off (#6930).
WarehouseReadGuard.assertReadable(uri)

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.

Same here.

context
.deleteFrom(USER_WAREHOUSE)
.where(USER_WAREHOUSE.WHID.eq(whid))
.execute()

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.

I think you need to handle the case where DB delete fails because LakeKeeper is deleted first before DB delete.

val warehouseEnabled: Boolean =
// warehouseEnabled is a var because tests need to exercise the enabled path
// (mirroring s3Endpoint above); production code never writes it.
var warehouseEnabled: Boolean =

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.

Use val to make it consistent with others?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I checked this, and I found some other are also using var, for example

var s3Endpoint: String = conf.getString("storage.s3.endpoint")
var lakefsEndpoint: String = conf.getString("storage.lakefs.endpoint")

Comment on lines +45 to +48
private val warehouseNamePattern = "[A-Za-z0-9][A-Za-z0-9_-]*".r

private[warehouse] def isValidWarehouseName(name: String): Boolean =
name.length <= 64 && warehouseNamePattern.pattern.matcher(name).matches()

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.

If this pattern is from VFSURIFactory codebase, can you deduplicate this pattern?

Comment on lines +90 to +96
DashboardWarehouse(
row.getWhid,
row.getName,
row.getWarehouseName,
row.getFlavor.getLiteral,
row.getCreatedAt.toInstant.toEpochMilli
)

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.

Maybe deduplicate this code with line 167

Comment on lines +126 to +136
private def listNamespaces(warehouseId: UUID): List[String] = {
val response = Unirest.get(s"$catalogBase/$warehouseId/namespaces").asString()
failOn(response.getStatus, response.getBody, "list namespaces")
mapper
.readTree(response.getBody)
.get("namespaces")
.iterator()
.asScala
.map(parts => parts.get(0).asText())
.toList
}

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.

Can you check if this API is paginated and this only returns the first few tables? The API may not return the entire pages and you might need to consider that.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

common ddl-change Changes to the TexeraDB DDL engine feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BYO-S3] Warehouse REST endpoints + Lakekeeper client + per-execution injection

4 participants