mirror of
https://github.com/facebook/rocksdb.git
synced 2026-07-07 14:47:40 +08:00
4707775ae9
Summary: - propagate lower-level read and merge failures through `GetContext` via `read_status`, so `Get` and `GetEntity` preserve the original error instead of synthesizing `Corruption` when blob-backed reads or merge resolution fail - teach `GetMergeOperands` to resolve blob-backed default columns from wide-column entities, covering both the direct base-value path and the merge-plus-base path - add regression coverage for blob-read IO errors during `Get`/`GetEntity` merge resolution and for `GetMergeOperands` on blob-backed wide-column entities - fix the `DBFlushTest.MemPurgeCorrectLogNumberAndSSTFileCreation` test race by waiting for flush callbacks and cleaning up sync points ## Testing - `make db_blob_basic_test -j14` - `/usr/bin/perl -e 'alarm shift; exec ARGV' 60 ./db_blob_basic_test --gtest_filter='DBBlobBasicTest/DBBlobBasicIOErrorTest.GetBlob_IOError/*:DBBlobBasicTest/DBBlobBasicIOErrorTest.GetEntityMergeWithBlobBaseIOError/*'` ## Task T265824017, T265415808 Pull Request resolved: https://github.com/facebook/rocksdb/pull/14640 Reviewed By: anand1976 Differential Revision: D101690700 Pulled By: xingbowang fbshipit-source-id: 2b6fc357b37a01efa72a2d54dcff55be8992f42a
301 lines
13 KiB
C++
301 lines
13 KiB
C++
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
// This source code is licensed under both the GPLv2 (found in the
|
|
// COPYING file in the root directory) and Apache 2.0 License
|
|
// (found in the LICENSE.Apache file in the root directory).
|
|
|
|
#pragma once
|
|
|
|
#include <cstdint>
|
|
#include <functional>
|
|
#include <limits>
|
|
#include <string>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
#include "db/dbformat.h"
|
|
#include "rocksdb/rocksdb_namespace.h"
|
|
#include "rocksdb/status.h"
|
|
#include "rocksdb/wide_columns.h"
|
|
|
|
namespace ROCKSDB_NAMESPACE {
|
|
|
|
class BlobFetcher;
|
|
class BlobIndex;
|
|
class FilePrefetchBuffer;
|
|
class PinnableSlice;
|
|
class PrefetchBufferCollection;
|
|
class Slice;
|
|
|
|
// Wide-column serialization/deserialization primitives.
|
|
//
|
|
// Version 1 Layout:
|
|
// The two main parts of the layout are 1) a sorted index containing the column
|
|
// names and column value sizes and 2) the column values themselves. Keeping the
|
|
// index and the values separate will enable selectively reading column values
|
|
// down the line. Note that currently the index has to be fully parsed in order
|
|
// to find out the offset of each column value.
|
|
//
|
|
// Legend: cn = column name, cv = column value, cns = column name size, cvs =
|
|
// column value size.
|
|
//
|
|
// +----------+--------------+----------+-------+----------+---...
|
|
// | version | # of columns | cns 1 | cn 1 | cvs 1 |
|
|
// +----------+--------------+------------------+--------- +---...
|
|
// | varint32 | varint32 | varint32 | bytes | varint32 |
|
|
// +----------+--------------+----------+-------+----------+---...
|
|
//
|
|
// ... continued ...
|
|
//
|
|
// ...---+----------+-------+----------+-------+---...---+-------+
|
|
// | cns N | cn N | cvs N | cv 1 | | cv N |
|
|
// ...---+----------+-------+----------+-------+---...---+-------+
|
|
// | varint32 | bytes | varint32 | bytes | | bytes |
|
|
// ...---+----------+-------+----------+-------+---...---+-------+
|
|
//
|
|
// Version 2 Layout (with blob index support):
|
|
// Groups all metadata upfront before variable-length data. This enables
|
|
// efficient access patterns: index-based value access skips name data
|
|
// entirely, default column access is O(1), and type checks are O(1).
|
|
//
|
|
// Legend: cn = column name, cv = column value, cns = column name size,
|
|
// cvs = column value size, ct = column type.
|
|
//
|
|
// Section 1: HEADER (2 varints)
|
|
// +----------+--------------+
|
|
// | version | # of columns |
|
|
// | varint32 | varint32 |
|
|
// +----------+--------------+
|
|
//
|
|
// Section 2: SKIP INFO (3 varints)
|
|
// +-------------------+---------------------+------------------+
|
|
// | name_sizes_bytes | value_sizes_bytes | names_bytes |
|
|
// | varint32 | varint32 | varint32 |
|
|
// +-------------------+---------------------+------------------+
|
|
// name_sizes_bytes = byte size of NAME SIZES section (section 4)
|
|
// value_sizes_bytes = byte size of VALUE SIZES section (section 5)
|
|
// names_bytes = byte size of NAMES section (section 6)
|
|
//
|
|
// Placed immediately after the header so that header + skip info form
|
|
// a contiguous varint sequence (5 varints), enabling future SIMD-based
|
|
// varint decoding.
|
|
//
|
|
// Section 3: COLUMN TYPES (N bytes, fixed-size)
|
|
// +------+------+---...---+--------+
|
|
// | ct_0 | ct_1 | | ct_N-1 |
|
|
// | byte | byte | | byte |
|
|
// +------+------+---...---+--------+
|
|
// ct values are ValueType entries from db/dbformat.h, e.g.:
|
|
// kTypeValue (0x01) = inline value
|
|
// kTypeBlobIndex (0x11) = blob index reference
|
|
// Future per-column types (kTypeMerge, kTypeDeletion, etc.) can be
|
|
// added without format changes.
|
|
//
|
|
// Section 4: NAME SIZES (N varints)
|
|
// +----------+----------+---...---+------------+
|
|
// | cns_0 | cns_1 | | cns_{N-1} |
|
|
// | varint32 | varint32 | | varint32 |
|
|
// +----------+----------+---...---+------------+
|
|
//
|
|
// Section 5: VALUE SIZES (N varints)
|
|
// +----------+----------+---...---+------------+
|
|
// | cvs_0 | cvs_1 | | cvs_{N-1} |
|
|
// | varint32 | varint32 | | varint32 |
|
|
// +----------+----------+---...---+------------+
|
|
//
|
|
// Section 6: COLUMN NAMES (concatenated, sorted)
|
|
// +------+------+---...---+--------+
|
|
// | cn_0 | cn_1 | | cn_N-1 |
|
|
// | bytes| bytes| | bytes |
|
|
// +------+------+---...---+--------+
|
|
//
|
|
// Section 7: COLUMN VALUES (concatenated)
|
|
// +------+------+---...---+--------+
|
|
// | cv_0 | cv_1 | | cv_N-1 |
|
|
// | bytes| bytes| | bytes |
|
|
// +------+------+---...---+--------+
|
|
//
|
|
// When ct = kTypeBlobIndex, the cv contains a serialized BlobIndex.
|
|
|
|
class WideColumnSerialization {
|
|
public:
|
|
// Version constants for wide column serialization format.
|
|
// - kVersion1: Original format with inline column values only.
|
|
// - kVersion2: Extended format that supports blob index references in
|
|
// columns. Used when large column values are stored in blob
|
|
// files.
|
|
static constexpr uint32_t kVersion1 = 1;
|
|
static constexpr uint32_t kVersion2 = 2;
|
|
|
|
// Serialize columns using version 1 format (no blob support)
|
|
static Status Serialize(const WideColumns& columns, std::string& output);
|
|
|
|
// Serialize columns with some columns replaced by blob indices (version 2)
|
|
// columns: vector of (column_name, column_value) pairs
|
|
// blob_columns: vector of (column_index, blob_index) pairs indicating which
|
|
// columns should be stored as blob references
|
|
static Status SerializeV2(
|
|
const std::vector<std::pair<std::string, std::string>>& columns,
|
|
const std::vector<std::pair<size_t, BlobIndex>>& blob_columns,
|
|
std::string& output);
|
|
|
|
// Overload that takes Slice-based WideColumns directly, avoiding the
|
|
// need to copy column names and values into string pairs.
|
|
static Status SerializeV2(
|
|
const WideColumns& columns,
|
|
const std::vector<std::pair<size_t, BlobIndex>>& blob_columns,
|
|
std::string& output);
|
|
|
|
// Deserialize columns (version 1 format only)
|
|
static Status Deserialize(Slice& input, WideColumns& columns);
|
|
|
|
// Deserialize a V2 entity.
|
|
// columns: receives the full sorted column set. For blob-backed columns,
|
|
// value() is initially the serialized BlobIndex bytes from the entity.
|
|
// blob_columns: receives (column_index, blob_index) pairs identifying which
|
|
// entries in `columns` are blob references and their decoded BlobIndex data.
|
|
// For valid input, this must agree with HasBlobColumns() about whether blob
|
|
// column references are present.
|
|
static Status DeserializeV2(
|
|
Slice& input, std::vector<WideColumn>& columns,
|
|
std::vector<std::pair<size_t, BlobIndex>>& blob_columns);
|
|
|
|
// Check if the serialized entity has any blob column references.
|
|
// Sets *has_blob_columns to true if version >= 2 and at least one column
|
|
// has blob type; false otherwise.
|
|
// For valid input, this must agree with DeserializeV2() about whether blob
|
|
// column references are present.
|
|
// Returns Status::Corruption on decode errors.
|
|
static Status HasBlobColumns(const Slice& input, bool& has_blob_columns);
|
|
|
|
// Lightweight extraction of blob file numbers from a V2 entity.
|
|
// Calls the callback for each blob column's BlobIndex. Avoids full
|
|
// deserialization of column names and inline values.
|
|
// Returns OK if no blob columns or not V2 (callback not invoked).
|
|
static Status ForEachBlobFileNumber(
|
|
const Slice& input,
|
|
const std::function<Status(const BlobIndex&)>& callback);
|
|
|
|
static Status GetValueOfDefaultColumn(Slice& input, Slice& value);
|
|
|
|
// Resolves all blob column references in a V2 wide-column entity,
|
|
// fetches the blob values, and re-serializes as a V1 entity (all inline).
|
|
// Handles inlined blobs (IsInlined) defensively.
|
|
//
|
|
// Used by the read path (GetContext, DBIter) when a V2 entity with blob
|
|
// column references needs to be converted to V1 format for consumption by
|
|
// APIs that only support V1 (e.g., TimedFullMerge,
|
|
// PinnableWideColumns::SetWideColumnValue).
|
|
//
|
|
// Sets *resolved to false and leaves resolved_entity unchanged when
|
|
// no blob columns are present.
|
|
//
|
|
// Optional parameters:
|
|
// prefetch_buffers - for prefetch optimization (nullptr = no prefetch)
|
|
// total_bytes_read - accumulates bytes read from blob files (nullptr =
|
|
// skip) num_blobs_resolved - count of blob columns resolved (nullptr =
|
|
// skip)
|
|
static Status ResolveEntityBlobColumns(
|
|
const Slice& entity_value, const Slice& user_key,
|
|
const BlobFetcher* blob_fetcher,
|
|
PrefetchBufferCollection* prefetch_buffers, std::string& resolved_entity,
|
|
bool& resolved, uint64_t* total_bytes_read, uint64_t* num_blobs_resolved);
|
|
|
|
// Extracts the default column value from a V2 entity, resolving its
|
|
// blob reference if needed. The default column (empty name) is always
|
|
// at index 0 when present (columns are sorted).
|
|
//
|
|
// Sets result to the resolved default column value (fetching from blob
|
|
// file if it's a blob reference). If there is no default column, result
|
|
// is set to empty. Sets *resolved to true if a blob was found for the
|
|
// default column, false otherwise.
|
|
static Status GetValueOfDefaultColumnResolvingBlobs(
|
|
const Slice& entity_value, const Slice& user_key,
|
|
const BlobFetcher* blob_fetcher, PinnableSlice& result, bool& resolved);
|
|
|
|
// Resolves V2 entity blob columns if present, returning an effective
|
|
// Slice for use with TimedFullMerge. If the entity has no blob columns
|
|
// (V1 or V2 without blobs), effective_entity is set to entity_value.
|
|
// Otherwise, blob columns are resolved and effective_entity points to
|
|
// the re-serialized V1 data in resolved_entity.
|
|
// The caller must keep resolved_entity alive while using effective_entity.
|
|
static Status ResolveEntityForMerge(
|
|
const Slice& entity_value, const Slice& user_key,
|
|
const BlobFetcher* blob_fetcher,
|
|
PrefetchBufferCollection* prefetch_buffers, std::string& resolved_entity,
|
|
Slice& effective_entity);
|
|
|
|
private:
|
|
friend class WideColumnSerializationTest;
|
|
// Get the serialization version from the input.
|
|
// Sets *version to the version number.
|
|
// Returns Status::Corruption on decode errors.
|
|
static Status GetVersion(const Slice& input, uint32_t& version);
|
|
|
|
// Merges deserialized columns with resolved blob values and serializes
|
|
// the result using version 1 format (all values inline).
|
|
static Status SerializeResolvedEntity(
|
|
const std::vector<WideColumn>& columns,
|
|
const std::vector<std::pair<size_t, BlobIndex>>& blob_columns,
|
|
const std::vector<std::string>& resolved_blob_values,
|
|
std::string& output);
|
|
|
|
// Returns InvalidArgument with the given message if size exceeds uint32_t.
|
|
static Status ValidateWideColumnLimit(size_t size, const char* msg) {
|
|
if (size > static_cast<size_t>(std::numeric_limits<uint32_t>::max())) {
|
|
return Status::InvalidArgument(msg);
|
|
}
|
|
return Status::OK();
|
|
}
|
|
|
|
// Returns Corruption if prev_name >= name (columns must be strictly ordered).
|
|
static Status ValidateColumnOrder(const Slice& prev_name, const Slice& name) {
|
|
if (prev_name.compare(name) >= 0) {
|
|
return Status::Corruption("Wide columns out of order");
|
|
}
|
|
return Status::OK();
|
|
}
|
|
|
|
// Shared implementation for both SerializeV2 overloads.
|
|
// get_name(i): returns Slice for column i's name
|
|
// get_value(i): returns Slice for column i's inline value
|
|
template <typename GetName, typename GetValue>
|
|
static Status SerializeV2Impl(
|
|
size_t num_columns,
|
|
const std::vector<std::pair<size_t, BlobIndex>>& blob_columns,
|
|
std::string& output, GetName get_name, GetValue get_value);
|
|
|
|
// Validates num_columns limit and builds a per-column lookup map from
|
|
// blob_columns. Returns InvalidArgument on validation failure.
|
|
static Status BuildBlobIndexMap(
|
|
size_t num_columns,
|
|
const std::vector<std::pair<size_t, BlobIndex>>& blob_columns,
|
|
std::vector<const BlobIndex*>& blob_index_map);
|
|
|
|
// Parses V1 layout (interleaved name/value_size pairs followed by values)
|
|
// into columns. Used by both Deserialize and DeserializeV2 to avoid
|
|
// code duplication.
|
|
static Status DeserializeV1(Slice& input, uint32_t num_columns,
|
|
std::vector<WideColumn>& columns);
|
|
|
|
// Parses V2 layout sections 2-7 (skip info through values) into columns and
|
|
// column types. Used by both Deserialize and DeserializeV2 to avoid
|
|
// code duplication.
|
|
static Status DeserializeV2Impl(Slice& input, uint32_t num_columns,
|
|
std::vector<WideColumn>& columns,
|
|
std::vector<ValueType>& column_types);
|
|
|
|
// Returns true if t is a supported per-column ValueType. Currently only
|
|
// kTypeValue (inline) and kTypeBlobIndex are supported. Notably,
|
|
// kTypeWideColumnEntity is rejected to prevent recursive nesting.
|
|
static bool IsValidColumnValueType(ValueType t) {
|
|
return t == kTypeValue || t == kTypeBlobIndex;
|
|
}
|
|
|
|
// Returns true if any of the first num_columns type bytes equals
|
|
// kTypeBlobIndex. Typical entities have <10 columns, so a linear
|
|
// scan is sufficient; SIMD could be considered if column counts grow.
|
|
static bool ContainsBlobType(const char* type_bytes, uint32_t num_columns);
|
|
};
|
|
|
|
} // namespace ROCKSDB_NAMESPACE
|