caldempsey opened a new pull request, #152:
URL: https://github.com/apache/spark-connect-go/pull/152
### What changes were proposed in this pull request?
This PR adds streaming support to the Spark Connect Go client by introducing
`ToLocalIterator(ctx)` on the DataFrame API, matching the functionality
available in Scala's implementation. The implementation includes a new
`types.RowIterator` interface that streams Arrow records batch-by-batch, and a
`ToRecordBatches()` method on `ExecutePlanClient` that returns channels for
streaming Arrow records and errors. Context cancellation is handled throughout
the streaming pipeline to ensure proper resource cleanup.
### Why are the changes needed?
The Go client currently only supports `Collect()`, which loads all rows into
memory at once. This approach doesn't work for large OLAP queries that return
gigabytes of data, and prevents lightweight services like API gateways or
serverless functions from using Spark Connect effectively.
This PR brings the same streaming capability that Scala and Java clients
have through `toLocalIterator()`. Rows are deserialised batch-by-batch and
released immediately after consumption. Since `ToTable` was pulling entire
tables of Arrow batches, we needed a new method to stream batches individually.
For now, I've reused `ReadArrowTableToRows` for the batch-to-row conversion,
though this could be optimised in a follow-up PR.
The changes are purely additive to maintain backward compatibility. External
consumers can pull data at their own pace, making it suitable for streaming
over HTTP or gRPC. I believe there are many opportunities to improve
performance by reducing allocs and GC pressure, which can also be addressed in
a follow up.
### Does this PR introduce any user-facing change?
Yes, it adds a new optional API:
```go
it, err := df.ToLocalIterator(ctx)
if err != nil {
return err
}
defer it.Close()
for {
row, err := it.Next()
if err == io.EOF {
break
}
if err != nil {
return err
}
// process row ...
}
```
Existing code using `Collect()` or `ToArrow()` remains unchanged.
### How was this patch tested?
Unit tests verify that streaming returns rows in the correct order, handles
EOF properly, cancels contexts gracefully without goroutine leaks, converts
Arrow batches correctly, and propagates errors through the streaming pipeline.
The implementation was also tested against a Unity Catalog/Databricks Spark
cluster using this streaming HTTP handler:
```go
// Example usage with proper context handling in a REST server
func StreamingHandler(w http.ResponseWriter, r *http.Request) {
// Create a context with timeout for the request
ctx, cancel := context.WithTimeout(r.Context(), 30*time.Minute)
defer cancel()
// Set up proper cleanup on client disconnect
notify := w.(http.CloseNotifier).CloseNotify()
go func() {
select {
case <-notify:
cancel() // Cancel context if client disconnects
case <-ctx.Done():
// Context already done
}
}()
var df sql.DataFrame // Your DataFrame
rowIter, err := df.ToLocalIterator(ctx)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
defer func() {
if closeErr := rowIter.Close(); closeErr != nil {
log.Printf("Error closing iterator: %v", closeErr)
}
}()
w.Header().Set("Content-Type", "application/x-ndjson")
w.WriteHeader(http.StatusOK)
encoder := json.NewEncoder(w)
for {
select {
case <-ctx.Done():
// Context cancelled - exit gracefully
return
default:
}
row, err := rowIter.Next()
if err != nil {
if errors.Is(err, io.EOF) {
return // Normal completion
}
if errors.Is(err, context.Canceled) || errors.Is(err,
context.DeadlineExceeded) {
return // Context cancellation - exit gracefully
}
// Log error but don't crash
log.Printf("Error reading row: %v", err)
return
}
// Stream the row as JSON
if err := encoder.Encode(map[string]interface{}{
"values": row.Values(),
}); err != nil {
// Client disconnected or other write error
return
}
// Flush the response to ensure streaming
if flusher, ok := w.(http.Flusher); ok {
flusher.Flush()
}
}
}
```
All tests pass locally with `go test ./...`.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]