Skip to content

feat(catalog-rest): add REST server-side scan planning client - #3011

Open
daviddallakyan2005 wants to merge 3 commits into
apache:mainfrom
daviddallakyan2005:rest-scan-plan
Open

feat(catalog-rest): add REST server-side scan planning client#3011
daviddallakyan2005 wants to merge 3 commits into
apache:mainfrom
daviddallakyan2005:rest-scan-plan

Conversation

@daviddallakyan2005

@daviddallakyan2005 daviddallakyan2005 commented Aug 16, 2026

Copy link
Copy Markdown

Which issue does this PR close?

What changes are included in this PR?

Working REST client for server-side scan planning (Go's catalog/rest/scan_planning.go), without wiring TableScan. Design note: #1690 (comment)

  • Named endpoints for plan / fetch-result / cancel / fetch-tasks, not in DEFAULT_ENDPOINTS.
  • Inherent methods on RestCatalog (plan_table_scan, fetch_planning_result, cancel_planning, fetch_scan_tasks, wait_for_plan). Catalog is untouched.
  • 404 split by error.type (NoSuchTable / NoSuchNamespace / NoSuchPlanId / NoSuchPlanTask).
  • UUIDv7 Idempotency-Key on POSTs; opaque plan-id as a single path segment.
  • wait_for_plan poller with jittered backoff, retry on 408/429/5xx, cancel on timeout / max retries / drop.
  • Retry-After accepts RFC 9110 delta-seconds and IMF-fixdate (HTTP-date). Overflowing second counts are ignored instead of panicking.
  • supports_remote_scan_planning stays false until task decoding exists (same as Go today). File payloads keep data-file as JSON.

Follow-ups: content-file decoder, TableScan routing, plan-scoped FileIO (#2651/#2932), DataFusion (#2671).

wait_for_plan is a per-plan poll loop (default 10 retries after the first GET, jittered 100ms–5s backoff, one GET plus JSON parse per attempt) and completed/fetch-tasks payloads keep each data-file and delete-file as a serde_json::Value tree, so a large plan materializes the full REST JSON DOM in memory once rather than decoded FileScanTask structs.

Are these changes tested?

Mockito unit tests in iceberg-catalog-rest (no docker / iceberg-rest-fixture):

cargo test -p iceberg-catalog-rest --lib
cargo clippy -p iceberg-catalog-rest --all-targets --all-features -- -D warnings
cargo fmt -p iceberg-catalog-rest -- --check
cargo public-api -p iceberg-catalog-rest --all-features -ss

Local result: 109 lib tests passed, clippy -D warnings clean, public-api.txt updated. GitHub CI on the previous commits (including workspace Tests (default)) was green; this follow-up adds HTTP-date Retry-After plus cancelled/failed/expired/namespace-404 coverage.

AI Disclosure

https://iceberg.apache.org/contribute/#guidelines-for-ai-assisted-contributions

AI assistance was used to draft the client, tests, and this description. The port was checked against Go scan_planning.go and existing REST catalog patterns. Tests and clippy were run locally as above.

Reviewer focus: wait_for_plan drop-cancel clones an uninitialized RestCatalog (an extra GET /v1/config on abort) and spawns the DELETE rather than awaiting it, so a Tokio worker Drop cannot deadlock.

Port plan / fetch-result / cancel / fetch-tasks onto RestCatalog with a
WaitForPlan poller. Task decoding and TableScan routing stay follow-ups,
so supports_remote_scan_planning remains false.

Part of apache#1690.
A huge Retry-After used to panic Duration::from_secs. Ignore overflow,
await cancel when the poller times out, and DELETE the plan if the wait
future is dropped.
RFC 9110 allows both delta-seconds and IMF-fixdate. Ignoring the date
form made wait_for_plan retry immediately against a server that asked
us to wait. Tests also cover cancelled/failed/expired poll outcomes
and the namespace 404 split.
(min_delay, max_delay, grace, max_retries, clamp_retry_after)
}

fn next_scan_plan_backoff(prev: Duration, min_delay: Duration, max_delay: Duration) -> Duration {

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.

Consider reusing backon instead of the hand-rolled backoff. backon is already a workspace dependency (backon = "1.5.1" in the root Cargo.toml) and is already the repo pattern for retry loops — see crates/iceberg/src/transaction/mod.rs:206 (ExponentialBuilder::new().with_min_delay(..).with_max_delay(..).with_max_times(..)).

The realistic scope: this is a poll-until-terminal-state loop, not a plain retry, so backon won't model the Retry-After handling, the submitted → completed/failed/cancelled state machine, or the drop-guard cancel, keep those. But the generic exponential-jitter part (next_scan_plan_backoff + resolve_wait_options delay math) could be ExponentialBuilder, which would also let us drop the new rand dependency added to this crate. WDYT?

}

/// True when a fetch-result 404 was a forgotten plan-id.
pub fn is_plan_expired(err: &Error) -> bool {

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.

These public predicates work by string-matching err.message() against the MSG_* constants (and is_plan_failed even does starts_with(format!("{MSG}: "))). That's fragile as a public API — a message tweak silently breaks callers, and matching on error text is unusual for this codebase. Consider a typed error/kind (or a dedicated planning-error enum) so callers can match structurally. Not blocking, but it's the part of the public surface I'd most want to firm up before it's relied on.

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.

In fact do we need this at all in the public api? Seems like we only use this in tests?

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 question for the other helpers below

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

Nice work! Excited to see server-side planning work happen! Left a few comments.

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.

2 participants