AL+

Language: RO (română) EN (English)

Email us: paul@alplustech.com

Remote read-ahead needs a memory budget as well as concurrency

DuckDB's asynchronous I/O design shows how read-ahead hides remote latency by holding unfinished work in memory. Concurrency needs both resource budgets.

Citiți articolul în română

Remote storage can leave a query waiting even when the machine has spare processing capacity. Starting more reads ahead of consumption can hide that delay, but the unfinished work occupies memory. A useful concurrency decision therefore needs two budgets: capacity to transfer data and capacity to hold it until the query can use it. Maximising pending requests alone is an incomplete objective.

DuckDB's 31 July 2026 asynchronous I/O design article makes this tradeoff concrete. It describes REGULAR and ASYNC thread pools and read-ahead governed by memory availability. The account concerns 2.0 development builds and a then-future major release. It should not be read as evidence that the design had shipped in the stable version discussed that July.

Separate the work from the waiting

The 22 July announcement of DuckDB 1.5.5 supplies the stable-release context. In the later design account, regular workers process data while asynchronous workers handle fetches, allowing transfer and processing to overlap. This separation explains the opportunity. A worker waiting for remote data cannot spend that same interval decoding the next available job unless the execution design lets it do so.

That opportunity depends on the bottleneck. If a query already uses the available transfer capacity, issuing more reads cannot create extra bandwidth. If processing is slower than arrival, fetching further ahead primarily increases the amount waiting for processing. The relevant question is where time is lost in the particular path, rather than whether asynchronous work is generally a more advanced design.

Read-ahead is a prediction that some future input will be useful soon. The more work admitted in advance, the longer the interval between requesting those bytes and consuming them can become. A queue therefore represents both useful overlap and a resource commitment. Its length has to be interpreted alongside the amount of data represented by each queued job.

Count held data, not just jobs

A limit on the number of jobs is not necessarily a limit on memory. Jobs can have different sizes and can contain several fetch tasks. A nominally short queue can still hold substantial data. For an application owner, the meaningful resource is the capacity unavailable to other query work while those inputs wait. Counting jobs helps describe scheduling, but does not complete that accounting.

The July design explicitly discusses this distinction. Its default read-ahead mode negotiates a memory budget; a positive fixed depth limits jobs without the same memory budget. Those settings are not interchangeable safety claims. Copying a depth from a successful example would require understanding both the input shape and the other work competing for memory. This article does not propose a configuration change.

Memory pressure also changes what useful concurrency looks like during a query. A join can need capacity while a scan would like to hold more future input. Giving both an independent maximum can oversubscribe the machine. A shared budget makes the tradeoff visible: read-ahead may need to shrink while another operator works, then expand when that operator releases its allocation.

Treat benchmark conditions as part of the result

DuckDB reports a TPC-H Query 6 experiment at SF100 using S3 data and EC2 compute, comparing 1.5.5 with 2.0 development builds. That is a vendor experiment under stated conditions, not an AL+ measurement or a universal acceleration claim. Its relevance is strongest where waiting on remote reads prevents use of available bandwidth, which is the problem the design aims to address.

For a different workload, the first useful comparison would retain the properties that explain the result. A query with a different projection may request different ranges. A workload with warm local data may spend little time waiting for storage. A shared machine may face memory competition absent from a dedicated run. These are reasons to qualify transferability, rather than dismiss a carefully bounded experiment.

The result also should not become a promise about every user-visible operation. Database scan time is one part of an application's path. A faster scan can matter greatly when that scan dominates the response. It may matter little when another dependency dominates. A product decision should connect the relevant storage wait to the operation the user actually needs completed.

Synchronous access can be sufficient

The strongest counterargument is local, low-latency storage with an execution path that already keeps the machine busy. The design account itself explains why synchronous access suited DuckDB's earlier local use. In that situation, adding deeper read-ahead can consume resources without solving a significant waiting problem. Retaining a simpler access pattern can be a reasonable decision supported by the workload.

Even for remote data, the sensible target is sufficient overlap within the resource envelope. A permanently full queue is not proof of a healthy query. It can indicate that input arrives faster than processing can consume it. The owner should be able to explain what the queued work contributes and what resource pressure would justify admitting less of it.

Make the operating envelope explicit

For custom software development, storage architecture should include the memory held by unfinished reads alongside the processing budget. That makes a design review more concrete than a request for maximum concurrency. It also gives the maintainer a way to discuss behavior under competing queries without pretending that one successful benchmark settles every operating condition.

The durable principle is to buy overlap deliberately. Keep enough input in flight to address the observed wait, while leaving room for the computation that makes those bytes useful. Remote latency and memory pressure belong in the same decision, because read-ahead trades one against the other.