Andrey Yarovoy created HDDS-16943:
-------------------------------------
Summary: Avoid the extra ListStatus RPC at the end of every file
system listing
Key: HDDS-16943
URL: https://issues.apache.org/jira/browse/HDDS-16943
Project: Apache Ozone
Issue Type: Improvement
Components: Ozone Manager
Reporter: Andrey Yarovoy
h2. Problem
The file system clients keep listing until OM returns a page that adds no new
entry:
{code:java}
// BasicRootedOzoneFileSystem.listStatusAdapter (same loop in
BasicOzoneFileSystem)
do {
tmpStatusList = adapter.listStatus(pathToKey(f), false, startPath,
numEntries, ...);
// add entries, dropping a repeated first entry
...
} while (entriesAdded > 0);
{code}
So every \{{FileSystem.listStatus}} costs at least two OM listing RPCs, even
when the whole directory fits in the
first page. The second call returns only the repeated start key (OM includes
\{{startKey}} in the result) and adds
nothing. The \{{listStatusIterator}} path (\{{hasNextNoFilter}}) has the same
rule: it fetches another page whenever
the current one was not empty.
In a benchmark of 2000 \{{ofs}} \{{listStatus}} calls on directories of 10
entries (\{{TestOfsListStatusRpcBenchmark}},
HDDS-16760), every call made exactly two ListStatusLight RPCs. One of them is
not needed.
h2. Why the client cannot stop on a short page
Before HDDS-11696 the loop stopped when a page was shorter than
\{{numEntries}}. HDDS-11696 changed it to stop on an
empty page, because OM caps the page size on its own:
* \{{OzoneManagerRequestHandler.limitListSize}}: \{{min(numEntries,
ozone.om.server.list.max.size)}} (default *1000*);
* \{{OmMetadataReader.listStatus}} / \{{listStatusLight}}: \{{min(n,
ozone.fs.listing.page.size.max)}} (default 1024),
raised to at least 2 by \{{OzoneConfigUtil.limitValue}}.
The client asks for \{{ozone.fs.listing.page.size}} (default *1024*), so with
default settings a full page of 1000
entries is shorter than the request. The client cannot know OM's caps, which
are server configuration, so a
client-only "stop on a short page" rule would silently cut off any directory
with more than 1000 entries. This
must not be reintroduced.
h2. Proposal
Let OM say whether the listing may continue, as \{{ListKeysResponse}} /
\{{ListKeysLightResponse}} already do with
{\{isTruncated}}.
# *Proto.* Add \{{optional bool isTruncated}} to \{{ListStatusResponse}} and
\{{ListStatusLightResponse}}.
# *OM.* Set it to \{{returnedCount >= effectiveLimit}}, where
\{{effectiveLimit}} is the page size OM actually used
after both caps above. Compare with that value, not with the requested
\{{numEntries}}.
Both listing paths return \{{min(effectiveLimit, remaining live entries)}}:
** FSO, \{{OzoneListStatusHelper.listStatusFSO}}: a single loop {{while
(map.size() < numEntries &&
heapIterator.hasNext())}}; cache tombstones and DB keys shadowed by the
cache never reach the heap, so they do
not use up the limit.
** LEGACY/OBS, \{{KeyManagerImpl.listStatus}}: entries are counted only when a
live status is added, and the final
loop stops at \{{numEntries}}.
So \{{isTruncated = false}} always means the listing is complete. When
exactly \{{effectiveLimit}} entries remain,
the flag is \{{true}} and the client makes one more call, as it does today. A
look-ahead
(\{{heapIterator.hasNext()}} on FSO, remaining map/iterator entries on
LEGACY) could avoid that last call, but it is
not needed for correctness.
# *Client.* Stop after a page whose response has \{{isTruncated}} set to
\{{false}}. If the field is absent (an older
OM), keep the current loop. Field presence distinguishes the two, so no
\{{OzoneManagerVersion}} gate is needed.
# *Plumbing.* The listing calls return a plain \{{List}} today through
\{{OzoneManagerProtocol}},
\{{ClientProtocol}} / \{{RpcClient}}, \{{OzoneClientAdapter}} and the two
file systems. The flag needs a way up,
for example a small result type holding the entries and the flag, used by the
file system listing loops and by
\{{hasNextNoFilter}}. Existing \{{List}}-returning methods can stay for other
callers.
Both \{{ofs}} (\{{BasicRootedOzoneFileSystem}}) and \{{o3fs}}
(\{{BasicOzoneFileSystem}}) benefit.
h2. Expected effect
A listing that fits in one page goes from two OM listing RPCs to one. On top of
HDDS-16760, an \{{ofs}}
{\{listStatus}} of such a directory goes from 2 OM RPCs to 1; before HDDS-16760
it was 4.
h2. Compatibility
* One new optional field in two response messages; no new request field, result
code or configuration.
* New client, old OM: the field is absent, so the client keeps the current loop.
* Old client, new OM: the field is ignored; behavior unchanged.
* Listing results are unchanged; only the number of calls changes.
h2. Testing
* OM unit tests for both \{{ListStatus}} and \{{ListStatusLight}}, FSO and
LEGACY: \{{isTruncated}} is \{{false}} when
fewer than the effective limit entries are returned and \{{true}} on a full
page, with the effective limit set by
each cap (\{{ozone.om.server.list.max.size}} below and above
\{{ozone.fs.listing.page.size.max}}, and a request of
size 1, which OM raises to 2).
* A full page whose only remaining entries are cache tombstones still ends the
listing on the next call.
* Client unit tests: the loop stops after one call when \{{isTruncated}} is
\{{false}}, and keeps the current loop
when the field is absent.
* Integration: a directory larger than \{{ozone.om.server.list.max.size}} with
the client page size above it, listed
in full through \{{ofs}} and \{{o3fs}}, by \{{listStatus}} and
\{{listStatusIterator}}. This is the regression guard
for HDDS-11696.
* \{{TestOfsListStatusRpcBenchmark}}: ListStatus RPCs per call drop from 2 to 1.
--
This message was sent by Atlassian Jira
(v8.20.10#820010)
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]