Proposal: Deprecate List in favor of RangeScan #1759
mattisonchao
started this conversation in
Proposal
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Summary
Deprecate the
Listoperation in the clients and makeRangeScanthe only way to read a range of keys.Listreturns all the keys of a range at once: the client holds the whole result in memory before the application sees the first key, and the application can't slow the server down or stop early.RangeScanis already a stream with a callback (Java) or a channel (Go), and the Java client already applies backpressure to the server through gRPC flow control. WhatRangeScanlacks is a way to skip the values, which is the only reason to callListtoday.The proposal:
RangeScan, so a scan returns keys and versions without the values.RangeScanthat would make the migration a regression: timeouts on long scans and resuming after a leader change. Ordering becomes explicit: records are in key order within a shard by default, and ordering across shards is an option.Listin the Go and Java clients, and move the CLI and other callers toRangeScan.Listfrom the client APIs in the next major release. The server keeps serving theListRPC for older clients.Target release: 0.19.
Motivation
The client can't bound the memory of a list
Both clients collect the whole list before returning it:
AsyncOxiaClient.list()returns aCompletableFuture<List<String>>. Each shard collects its keys into anArrayList(internalShardlist), the multi-shard path waits for all the shards withallOf, concatenates them and sorts the result (internalListMultiShards). Nothing reaches the application until the last shard is done.SyncClient.List()returns a[]stringbuilt by appending every batch.AsyncClient.List()streams batches on a channel, but they come from all the shards at once in no order, so most callers collect them anyway.On the server,
Listbatches keys up to 2 MiB per response with no limit on the count (maxTotalListKeyCount = 0). A list over a large range is as large as the range: the size depends on the data, not on the caller. A prefix with millions of keys is a few hundred MiB on the heap of the client, and there is nothing the caller can do about it except not callingList.RangeScanalready streamsListRangeScanaggregateAndSortRangeScanAcrossShards); Java interleaves the shards[]string, fully bufferedCompletableFuture<List<String>>, fully buffered, sortedRangeScanConsumercallback,onNextreturnsfalseto stop and cancel the streamsList<String>, fully bufferedCloseableIterable<GetResult>with manual gRPC flow control (FlowControlledRangeScanConsumer): a stream isn't asked for its next response until the buffer is drainedBoth RPCs share the same request shape (
shard,start_inclusive,end_exclusive,secondary_index_name,include_internal_keys,end_inclusive) and the same iterator on the leader. Keeping two operations doubles every feature added to range reads:end_inclusivewas added to both, and #1612 proposes a session filter onList.Proposal
1. Keys-only range scans
Add a field to
RangeScanRequest:The leader leaves
GetResponse.valueempty.RangeScanResponsedoesn't change, and no feature flag is needed: an older server ignores the field and sends the values, and the client keeps consuming the responses and drops the values. The scan returns the same keys either way; against an older server it only costs the transfer of the values.With keys only, the server sizes the batches by the key length instead of the value length. Today the
RangeScanbatcher counts onlylen(response.Value), so the 2 MiB limit doesn't account for the keys; that should be fixed for both modes.Keys-only also returns the version of each key (
version_id,modifications_count,session_id...), whichListnever returned. That's useful by itself: for example #1612's "keys attached to a session" could be a filter on a keys-only scan.Client options:
oxia.KeysOnly()as aRangeScanOption.RangeScanOption.KeysOnly().2. Close the gaps of
RangeScanMigrating callers from
ListtoRangeScanmust not make them worse. Two gaps:rangeScanFromShardwraps the whole stream incontext.WithTimeout(ctx, requestTimeout)(30s by default). With backpressure, a slow consumer makes a scan last longer than that and it fails. The timeout should bound the time to the first response (or between responses), not the whole scan; the caller'sctxalready bounds the whole scan. The Java client doesn't apply the request timeout to range scans.verified); after that, a leader election ends the scan with an error. Since the client knows the last key it delivered, it can restart the shard's scan after that key on the new leader. This matters more for scans than for lists, because they're expected to be long.Ordering
Today the clients disagree: the Go client merges the shards with a heap, so a multi-shard scan comes out in key order, while the Java
CompositeRangeScanConsumerinterleaves the shards in arrival order. The proposal makes the ordering a documented feature, the same in both clients:RangeScanOption(Go:oxia.OrderedAcrossShards(), Java:RangeScanOption.OrderedAcrossShards()) merges the shards in key order, which costs one buffered response per shard and runs at the pace of the slowest shard. In Go it's the existing heap merge; in Java it's a merge on top of the flow-controlled streams.The option is not a blocker for the deprecation: callers that need a sorted multi-shard result, as Java's
list()gives today, keep using the deprecatedListuntil it's available. In Go, the default changes from the merged order to the per-shard order, and callers that rely on the merge pass the option.3. Deprecate
ListAsyncClient.ListandSyncClient.Listwith// Deprecated: use RangeScan with KeysOnly.@DeprecatedonAsyncOxiaClient.listandSyncOxiaClient.list, and onListOption.ListRPC, so their behavior (including the order across shards) doesn't change while they exist and they keep working against older servers.oxia client listCLI command, and the docs and examples.getChildrenuseslist).4. Remove
Listfrom the clientsIn the next major release of each client. The
ListRPC stays inclient.protoand on the server so that old clients keep working; it's markeddeprecated = truein the proto. Removing the RPC from the server is out of scope. The server-sideListBlockused by the session manager is internal and is not affected.Release plan
Target: 0.19, the server and the Go and Java client releases that go with it.
keys_onlyon the server, the keys-only option in both clients, per-shard ordering as the default, the timeout and resume fixes, the deprecation ofListin both clients, and the move of the CLI.Listfrom the client APIs.Compatibility
ListRPC is still served.All reactions