ofer-molad-placer opened a new issue, #55452:
URL: https://github.com/apache/arrow/issues/55452

   
   ### Describe the enhancement requested
   
   The Flight client has an async API for one call only: `GetFlightInfoAsync` 
in C++
   (`cpp/src/arrow/flight/client.h`), which 
`pyarrow.flight.AsyncioFlightClient` wraps through
   `FlightClient.as_async()` (added in #37093).
   
   `DoGet`, which streams the actual data, has no async version in C++ or 
Python. Neither does
   `DoAction`.
   
   We request:
   
   **Priority 1: async `DoGet`.** An async `DoGet` in the C++ client that 
delivers record batches as
   they arrive, with a way to slow down the stream when the reader cannot keep 
up. And a matching
   method on `AsyncioFlightClient`, for example `async def do_get(ticket, 
options)` that returns an
   async iterator of `RecordBatch` (or a reader with an awaitable 
`read_next_batch()` / `read_all()`).
   `DoGet` holds a thread for the whole stream. In a load test it used about 
twice the total thread
   time of all our `DoAction` calls.
   
   **Priority 2: async `DoAction`.** The same in C++ and on 
`AsyncioFlightClient`. Each action is
   short, but in the same load test we made about 8 times more `DoAction` calls 
than `DoGet` calls,
   mostly to poll job status. Together they still used a large part of the 
thread pool. With both calls
   async, we could drop the thread pool entirely.
   
   Priority 1 does not need to wait for Priority 2.
   
   ### Use case
   
   We call a Flight server from an asyncio Python service. Under peak load it 
runs about 150
   concurrent requests against the server. Today each `do_get` has to run in a 
thread pool
   (`loop.run_in_executor`) to avoid blocking the event loop, so the pool must 
be sized for that peak:
   one thread for each concurrent request. This means:
   
   - the thread pool size limits how many requests can be in flight at once;
   - cancelling the asyncio task does not cancel the RPC — the thread keeps 
reading until the stream
     ends or the timeout expires;
   - one deadline for the whole read has to be split by hand between 
`get_flight_info` and `do_get`.
   
   With `as_async()` we can already make `get_flight_info` async, but `do_get` 
usually takes most of
   the time, so the thread pool is still needed.
   
   ### Alternatives considered
   
   - Running the blocking client in a thread pool (what we do now).
   - Dropping the `pyarrow.flight` client and talking to the server directly 
with `grpc.aio`. This
     means building our own Flight client from scratch: generating stubs from 
`Flight.proto`, splitting
     each `FlightData` message into its IPC header and body and decoding them 
with `pyarrow.ipc`,
     tracking the schema across the stream, and re-implementing call headers, 
auth, TLS, timeouts and
     error mapping. That is a lot of low-level code to write and maintain, and 
it duplicates what
     the Flight client already does.
   
   Related: #37093 (async `GetFlightInfo`). A 2024 comment on that issue also 
asked about async
   `DoExchange`.
   
   ### Environment
   
   pyarrow 23.0.1, Python 3.14, Linux, gRPC transport.
   
   ### Component(s)
   
   C++, FlightRPC, Python
   


-- 
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]

Reply via email to