Reason or Problem
_read_cog_http in xrspatial/geotiff/_reader.py walks tiles sequentially. Each tile is fetched with its own HTTP range request, and each request blocks on the previous one before the next is sent.
For a COG with N tiles read over a high-latency link, total wall time is bounded by N × RTT. A 100-tile COG over a 50 ms RTT link spends 5 seconds on round trips alone before any data flows.
Proposal
Fetch tile ranges concurrently with a small thread pool. Each tile still issues its own HTTP range request, but the requests overlap on the wire so RTT is hidden behind concurrency.
Design:
Add a helper on _HTTPSource (or alongside _read_cog_http) that takes a list of (offset, byte_count) ranges and returns the bytes for each, in input order:
def read_ranges(self, ranges: list[tuple[int, int]], max_workers: int = 8) -> list[bytes]:
...
Implementation uses concurrent.futures.ThreadPoolExecutor. urllib3.PoolManager is already used by _HTTPSource and is thread-safe, so the existing pool handles connection reuse across worker threads.
In _read_cog_http, replace the sequential loop:
for tr in range(tiles_down):
for tc in range(tiles_across):
...
tile_data = source.read_range(off, bc)
# decode + place tile
with two passes: collect all (offset, byte_count, placement) entries first, fetch them with read_ranges, then iterate the returned bytes and decode/place each tile.
_HTTPSource.read_range stays unchanged, so no other call sites are affected.
Usage:
No API change. Existing read_to_array(http_url) calls benefit automatically.
Value:
For 100 tiles × 50 ms RTT with 8 workers, total fetch drops from ~5 s to ~600 ms. Local file reads are unaffected; this only touches _read_cog_http.
Stakeholders and Impacts
Touches _HTTPSource and _read_cog_http. No public API change.
Drawbacks
- Extra worker threads (small constant).
- Servers without HTTP/1.1 keep-alive may see more connection setup. The urllib3 PoolManager already in use mitigates that.
Alternatives
- Multipart Range requests (
bytes=0-N1,N2-N3,...). Cuts request count further but server support is uneven; some CDNs return 200 with the full body, others reject with 400/416. Could be layered on top of the threadpool fallback later.
- Adjacent-range coalescing (merge ranges within ~8 KB). Useful when tile offsets are nearly contiguous. Also a future addition.
Unresolved Questions
- Worker pool size. 8 is a reasonable default; could be tunable via env var or kwarg if anyone needs it.
Additional Notes or Context
Found during the geotiff performance audit (P-1).
Reason or Problem
_read_cog_httpinxrspatial/geotiff/_reader.pywalks tiles sequentially. Each tile is fetched with its own HTTP range request, and each request blocks on the previous one before the next is sent.For a COG with N tiles read over a high-latency link, total wall time is bounded by N × RTT. A 100-tile COG over a 50 ms RTT link spends 5 seconds on round trips alone before any data flows.
Proposal
Fetch tile ranges concurrently with a small thread pool. Each tile still issues its own HTTP range request, but the requests overlap on the wire so RTT is hidden behind concurrency.
Design:
Add a helper on
_HTTPSource(or alongside_read_cog_http) that takes a list of(offset, byte_count)ranges and returns the bytes for each, in input order:Implementation uses
concurrent.futures.ThreadPoolExecutor.urllib3.PoolManageris already used by_HTTPSourceand is thread-safe, so the existing pool handles connection reuse across worker threads.In
_read_cog_http, replace the sequential loop:with two passes: collect all
(offset, byte_count, placement)entries first, fetch them withread_ranges, then iterate the returned bytes and decode/place each tile._HTTPSource.read_rangestays unchanged, so no other call sites are affected.Usage:
No API change. Existing
read_to_array(http_url)calls benefit automatically.Value:
For 100 tiles × 50 ms RTT with 8 workers, total fetch drops from ~5 s to ~600 ms. Local file reads are unaffected; this only touches
_read_cog_http.Stakeholders and Impacts
Touches
_HTTPSourceand_read_cog_http. No public API change.Drawbacks
Alternatives
bytes=0-N1,N2-N3,...). Cuts request count further but server support is uneven; some CDNs return 200 with the full body, others reject with 400/416. Could be layered on top of the threadpool fallback later.Unresolved Questions
Additional Notes or Context
Found during the geotiff performance audit (P-1).