Skip to content

Add bounded SEA API support for CloudFetch (UseBoundedSeaApi)#1468

Open
gopalldb wants to merge 7 commits into
databricks:mainfrom
gopalldb:feature/bounded-sea-api-cloudfetch
Open

Add bounded SEA API support for CloudFetch (UseBoundedSeaApi)#1468
gopalldb wants to merge 7 commits into
databricks:mainfrom
gopalldb:feature/bounded-sea-api-cloudfetch

Conversation

@gopalldb
Copy link
Copy Markdown
Collaborator

@gopalldb gopalldb commented May 25, 2026

Summary

  • Add UseBoundedSeaApi connection property (default 0) to gate bounded SEA API compliance
  • CloudFetch path: Force StreamingChunkProvider (uses next_chunk_index chaining instead of deprecated total_chunk_count), send row_offset on GetResultData, add batched link refresh reconciliation
  • Bring StreamingChunkDownloadTask to parity with ChunkDownloadTask (outer catch(Throwable), thread context propagation, exception chaining)

Key design decisions

  • UseBoundedSeaApi is off by default — no behavioral change without opt-in
  • StreamingChunkProvider is already bounded-SEA-compliant (uses next_chunk_index), only needed to add row_offset and force its selection

Test plan

  • Verify UseBoundedSeaApi=0 (default) — no behavioral change, existing CloudFetch and inline paths unchanged
  • Verify UseBoundedSeaApi=1 with CloudFetch enabled — uses StreamingChunkProvider with row_offset
  • Unit tests pass (core module: 0 failures)

NO_CHANGELOG=true

This pull request was AI-assisted by Isaac.

@gopalldb gopalldb force-pushed the feature/bounded-sea-api-cloudfetch branch from e8e6bde to e9dcd7c Compare May 25, 2026 09:53
@gopalldb gopalldb changed the title Add bounded SEA API support for CloudFetch (UseBoundedSeaApi) Add bounded SEA API support for CloudFetch and inline Arrow results May 27, 2026
@gopalldb gopalldb force-pushed the feature/bounded-sea-api-cloudfetch branch from d6b9994 to 1fa1b2b Compare May 27, 2026 08:11
@gopalldb gopalldb changed the title Add bounded SEA API support for CloudFetch and inline Arrow results Add bounded SEA API support for CloudFetch (UseBoundedSeaApi) May 27, 2026
gopalldb added 5 commits May 27, 2026 14:46
Part 1 of bounded SEA API compliance for CloudFetch:

1. New connection property UseBoundedSeaApi (default 0/off). When enabled:
   - Sends row_offset query parameter on GetResultData requests
   - Forces StreamingChunkProvider (which uses next_chunk_index, not
     total_chunk_count) even when streaming is explicitly disabled

2. StreamingChunkProvider already uses next_chunk_index for continuation
   and end-of-stream detection — no changes needed to its core logic.

3. Legacy RemoteChunkProvider (uses total_chunk_count) is bypassed when
   bounded SEA is enabled.

row_offset is derived from the previous link's row_offset + row_count
and sent as a query parameter on /sql/statements/{id}/result/chunks/{idx}.
This is required for future >100GB results and cluster-side fetch.

Co-authored-by: Isaac
Signed-off-by: Gopal Lal <gopal.lal@databricks.com>
During coalesced link refresh, the server may return links for chunks
not yet in the provider's map (newly-discovered chunks beyond
highestKnownChunkIndex). Previously these were silently skipped.

Now: create new chunks from refresh response links, update
highestKnownChunkIndex, and set endOfStreamReached from the response's
hasMore flag. Follows the per-chunk state-machine reconciliation from
the bounded SEA API spec.

Co-authored-by: Isaac
Signed-off-by: Gopal Lal <gopal.lal@databricks.com>
Fixes 3 gaps found by comparing with the legacy ChunkDownloadTask:

1. Outer catch(Throwable) + exception chaining in finally: uncaught
   exceptions were lost — the finally block created a generic exception
   without the original cause. Now captures uncaughtException and chains
   it, matching ChunkDownloadTask's pattern.

2. Thread context propagation: download threads had no connection context
   or statementId for telemetry/logging. Now captures caller's context
   via DatabricksThreadContextHolder and clears in finally.

3. Download timing: added task-level timing log (totalMs, retries)
   matching ChunkDownloadTask's diagnostics.

Also includes the RuntimeException catch (parity with PR databricks#1302).

Co-authored-by: Isaac
Signed-off-by: Gopal Lal <gopal.lal@databricks.com>
P0-1: Remove redundant chunk.setStatus(DOWNLOAD_FAILED) in inner catch
— defer entirely to finally block. Fixes StreamingChunkDownloadTaskTest.

P0-2: Add NEXT_CHANGELOG.md entry under ### Added for UseBoundedSeaApi.

P1-1: Call triggerDownloads() after reconciliation creates new chunks
from refresh response — prevents newly-discovered chunks sitting PENDING.

P1-2/P1-3: Un-gated changes (new chunk creation, EOS from refresh,
triggerDownloads) are intentional parity fixes for all
EnableStreamingChunkProvider=1 users. EnableStreamingChunkProvider
defaults to off, so default users are unaffected.

P1-4: Revert RuntimeException from inner catch — DatabricksError is
caught by outer catch(Throwable) and fails immediately (no retry),
matching ChunkDownloadTask behavior exactly. NPE/ISE won't be retried.

P2-1: Always send row_offset (even 0 for chunk 0) when bounded SEA
enabled — explicit is safer than relying on server default.

P2-3: Update nextLinkFetchIndex after reconciliation to avoid prefetch
thread re-fetching chunks already discovered via refresh.

P2-5: Add "Requires server support" to connection property help text.

Co-authored-by: Isaac
Signed-off-by: Gopal Lal <gopal.lal@databricks.com>
…on updates

P1-1: Use putIfAbsent in createChunkFromLink to prevent double row-count
and chunk replacement when called concurrently from prefetch and download
threads. Without this, a race could leave a CompletableFuture that is
never completed, causing consumer hangs.

P1-2: Bundle nextLinkFetchIndex + nextRowOffsetToFetch into an immutable
FetchPosition holder updated via volatile reference. This ensures the
prefetch thread always reads a consistent (index, rowOffset) pair, which
matters for bounded SEA API where row_offset is used by the server.

P2-1: Bump reconciliation failure log from DEBUG to WARN for production
visibility.

Signed-off-by: Gopal Lal <gopal.lal@databricks.com>

Co-authored-by: Isaac
Signed-off-by: Gopal Lal <gopal.lal@databricks.com>
@gopalldb gopalldb force-pushed the feature/bounded-sea-api-cloudfetch branch from 5801928 to 7e0b2c1 Compare May 27, 2026 09:17
gopalldb added 2 commits May 27, 2026 22:31
…h path

Per the bounded SEA API contract, drivers must not depend on
manifest.{chunks, total_chunk_count, total_row_count}. Pass null for
totalChunkCount when converting initial ExternalLinks for
StreamingChunkProvider — the provider derives end-of-stream from
next_chunk_index on ExternalLink instead.

Signed-off-by: Gopal Lal <gopal.lal@databricks.com>

Co-authored-by: Isaac
Signed-off-by: Gopal Lal <gopal.lal@databricks.com>
…ast/isAfterLast

When boundedSeaApiEnabled=true, isLast() and isAfterLast() now use
hasNext() instead of resultSetMetaData.getTotalRows(). The bounded SEA
API contract does not guarantee manifest.total_row_count is populated;
the chunk providers derive end-of-stream from next_chunk_index instead.

Gated behind isBoundedSeaApiEnabled() + ArrowStreamResult instanceof
check so existing Thrift and non-bounded SEA behavior is unchanged.

Signed-off-by: Gopal Lal <gopal.lal@databricks.com>

Co-authored-by: Isaac
Signed-off-by: Gopal Lal <gopal.lal@databricks.com>
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.

1 participant