Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[server][dvc] Server request/response handling overhaul (#1152)
This refactors many parts of server request and response handling, including how metrics are recorded. The significant functional changes are: 1. Parallel batch gets no longer have a race condition in the way metrics are recorded. Previously, the metrics which aggregate a value for all keys in the query could be missing data points and thus under-report due to this race. 2. The configs to enable and tune parallel batch gets now apply to compute as well, which was not supported before. 3. K/V size profiling is now always enabled for single gets, since it is considered cheap enough for that workload. The config still controls whether it is enabled or not for batch get and compute. Non-functional changes include: 1. The elimination of locking in parallel query processing. 2. The serialization of responses to bytes now happens inside each subtask, rather than sequentially after all subtasks are completed. These are then presented as a CompositeByteBuf, which may result in smaller allocations since there is no need for one large byte[] holding the whole response anymore. 3. The objects used to record stats are now specialized for each query type (single get, batch get, compute) as well as for whether K/V size profiling is enabled or not, leading to a reduction of memory footprint for workloads that don’t need certain stats. See the class hierarchy details below for more info. 4. As much as possible, when some behavior is fixed for the whole duration of the server’s runtime, a property is allocated to hold the closure to be executed according to the relevant config, rather than evaluating the immutable config in the hot path. Similary, removed as much branching as possible from ChunkingUtils and related classes. Below are some of the implementation details for achieving the above changes as well as to clean up some tech debt. The ReadResponse is now split into two interfaces, each of which has its own hierarchy of implementations: - ReadResponse, which holds the state required to achieve the functional goals of the server, as seen by the querier (i.e. populating the payload and headers). This is now moved from the DVC module to the server module. The following subclass hierarchy exists: - AbstractReadResponse: holds common functional state and behavior for all read requests. - SingleGetResponseWrapper: renamed from StorageResponseObject. - MultiKeyResponseWrapper: used for single-threaded batch get requests as well as subtasks of parallel batch get requests. - ComputeResponseWrapper: used for single-threaded compute requests as well as subtasks of parallel compute requests. - ParallelMultiKeyResponseWrapper: used in parallel batch get & compute requests to hold an array of MultiKeyResponseWrapper, each of which are used exclusively by one subtask. This class is then responsible for aggregating the results of all these wrappers. - ReadResponseStats: holds the state required for recording metrics, after the response is flushed. This replaces the ReadResponse parameter passed into all chunking-related utilities, thus more clearly constraining the role of that parameter in those classes. The purpose of this interface is to accumulate stats, but it doesn’t offer any API for reading or using those stats (see below how that part is done). The following subclass hierarchy exists: - AbstractReadResponseStats: holds the common metrics state reported for all read responses. The class also implements ReadResponseStatsRecorder, a new interface which is responsible for recording the accumulated metric state. Each subclass is responsible for defining the recording logic pertaining to the extra state it carries. - MultiKeyResponseStats: holds the record count. - MultiGetResponseStatsWithSizeProfiling: holds K/V sizes for each record. - ComputeResponseStats: holds nine primitives used exclusively in read compute stats. - ComputeResponseStatsWithSizeProfiling: holds K/V sizes for each record. - NoOpReadResponseStats: a singleton intended to be passed into the various ChunkingUtils APIs by code paths which do not need any stats recorded via this interface. This allows the ChunkingUtils code to assume this parameter is not null and systematically call whatever APIs it needs, rather than null-checking on the hot path. Miscellaneous main code changes: - MultiKeyRouterRequestWrapper::getKeys now returns a List<K> rather than an Iterable<K>. - DaVinciBackend::getStoreOrThrow now makes use of CHM::computeIfAbsent, rather than syncrhonized. This helped stabilize an integration test. - Muted a common stacktrace in IsolatedIngestionUtils. - ThinClientMetaStoreBasedRepository::getStoreMetaValue now has a lower timeout and retries, rather than trying only one request with the default timeout of 10 seconds. Total time spent should be the same. - Refactored ReplicationMetadataRocksDBStoragePartition so that the getReplicationMetadata API takes a ByteBuffer param, rather than byte[], to bring it in line with the RocksDBStoragePartition::get API. - Discovered and fixed an unrelated bug in the ChunkingUtils' getValueAndSchemaIdFromStorage API, where it would carry the ChunkValueManifest's special schema ID rather than the real value's schema ID, when chunking kicks in.
- Loading branch information