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
852 lines
30 KiB
C++
852 lines
30 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).
|
|
|
|
#include "db/blob/blob_file_partition_manager.h"
|
|
|
|
#include <array>
|
|
#include <atomic>
|
|
#include <cinttypes>
|
|
#include <memory>
|
|
#include <utility>
|
|
|
|
#include "cache/cache_key.h"
|
|
#include "cache/typed_cache.h"
|
|
#include "db/blob/blob_contents.h"
|
|
#include "db/blob/blob_file_cache.h"
|
|
#include "db/blob/blob_file_completion_callback.h"
|
|
#include "db/blob/blob_file_reader.h"
|
|
#include "db/blob/blob_index.h"
|
|
#include "db/blob/blob_log_writer.h"
|
|
#include "db/version_set.h"
|
|
#include "file/filename.h"
|
|
#include "file/read_write_util.h"
|
|
#include "file/writable_file_writer.h"
|
|
#include "logging/logging.h"
|
|
#include "util/aligned_buffer.h"
|
|
#include "util/compression.h"
|
|
#include "util/mutexlock.h"
|
|
|
|
namespace ROCKSDB_NAMESPACE {
|
|
|
|
namespace {
|
|
|
|
class RoundRobinBlobFilePartitionStrategy : public BlobFilePartitionStrategy {
|
|
public:
|
|
using BlobFilePartitionStrategy::SelectPartition;
|
|
|
|
const char* Name() const override {
|
|
return "RoundRobinBlobFilePartitionStrategy";
|
|
}
|
|
|
|
uint32_t SelectPartition(uint32_t num_partitions,
|
|
uint32_t /*column_family_id*/, const Slice& /*key*/,
|
|
const Slice& /*value*/) override {
|
|
assert(num_partitions > 0);
|
|
return static_cast<uint32_t>(
|
|
next_partition_.fetch_add(1, std::memory_order_relaxed) %
|
|
static_cast<uint64_t>(num_partitions));
|
|
}
|
|
|
|
private:
|
|
std::atomic<uint64_t> next_partition_{0};
|
|
};
|
|
|
|
struct DirectWriteCompressionState {
|
|
CompressionOptions compression_opts;
|
|
// `working_area` must be released before its owning compressor.
|
|
std::unique_ptr<Compressor> compressor;
|
|
Compressor::ManagedWorkingArea working_area;
|
|
};
|
|
|
|
DirectWriteCompressionState& GetDirectWriteCompressionState(
|
|
CompressionType compression, const CompressionOptions& compression_opts) {
|
|
assert(compression <= kLastBuiltinCompression);
|
|
|
|
static thread_local std::array<DirectWriteCompressionState,
|
|
static_cast<size_t>(kLastBuiltinCompression) +
|
|
1>
|
|
compression_states;
|
|
|
|
auto& compression_state =
|
|
compression_states[static_cast<size_t>(compression)];
|
|
if (compression != kNoCompression &&
|
|
(compression_state.compressor == nullptr ||
|
|
compression_state.compression_opts != compression_opts)) {
|
|
// BDW compression settings are mutable, so rebuild the per-thread cached
|
|
// compressor when the latest published opts for this type change.
|
|
compression_state.working_area = Compressor::ManagedWorkingArea{};
|
|
compression_state.compressor.reset();
|
|
compression_state.compression_opts = compression_opts;
|
|
compression_state.compressor =
|
|
GetBuiltinV2CompressionManager()->GetCompressor(compression_opts,
|
|
compression);
|
|
if (compression_state.compressor != nullptr) {
|
|
compression_state.working_area =
|
|
compression_state.compressor->ObtainWorkingArea();
|
|
}
|
|
}
|
|
|
|
return compression_state;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
BlobFilePartitionManager::BlobFilePartitionManager(
|
|
uint32_t num_partitions,
|
|
std::shared_ptr<BlobFilePartitionStrategy> strategy,
|
|
FileNumberAllocator file_number_allocator, FileSystem* fs,
|
|
SystemClock* clock, Statistics* statistics, const FileOptions& file_options,
|
|
std::string db_path, std::string column_family_name,
|
|
uint64_t blob_file_size, bool use_fsync, BlobFileCache* blob_file_cache,
|
|
BlobFileCompletionCallback* blob_callback,
|
|
const std::vector<std::shared_ptr<EventListener>>& listeners,
|
|
FileChecksumGenFactory* file_checksum_gen_factory,
|
|
const FileTypeSet& checksum_handoff_file_types,
|
|
const std::shared_ptr<IOTracer>& io_tracer, std::string db_id,
|
|
std::string db_session_id, Logger* info_log)
|
|
: num_partitions_(num_partitions == 0 ? 1 : num_partitions),
|
|
strategy_(strategy != nullptr
|
|
? std::move(strategy)
|
|
: std::make_shared<RoundRobinBlobFilePartitionStrategy>()),
|
|
file_number_allocator_(std::move(file_number_allocator)),
|
|
fs_(fs),
|
|
clock_(clock),
|
|
statistics_(statistics),
|
|
file_options_(file_options),
|
|
db_path_(std::move(db_path)),
|
|
column_family_name_(std::move(column_family_name)),
|
|
blob_file_size_(blob_file_size),
|
|
use_fsync_(use_fsync),
|
|
blob_file_cache_(blob_file_cache),
|
|
blob_callback_(blob_callback),
|
|
listeners_(listeners),
|
|
file_checksum_gen_factory_(file_checksum_gen_factory),
|
|
checksum_handoff_file_types_(checksum_handoff_file_types),
|
|
io_tracer_(io_tracer),
|
|
db_id_(std::move(db_id)),
|
|
db_session_id_(std::move(db_session_id)),
|
|
info_log_(info_log) {
|
|
partitions_.reserve(num_partitions_);
|
|
for (uint32_t i = 0; i < num_partitions_; ++i) {
|
|
partitions_.emplace_back(new Partition());
|
|
}
|
|
}
|
|
|
|
BlobFilePartitionManager::~BlobFilePartitionManager() {
|
|
if (blob_file_cache_ != nullptr) {
|
|
std::vector<uint64_t> tracked_file_numbers;
|
|
{
|
|
ReadLock lock(&file_partition_mutex_);
|
|
tracked_file_numbers.reserve(file_to_partition_.size());
|
|
for (const auto& entry : file_to_partition_) {
|
|
tracked_file_numbers.push_back(entry.first);
|
|
}
|
|
}
|
|
|
|
for (uint64_t file_number : tracked_file_numbers) {
|
|
// Dropping the CF or closing the DB can destroy the manager while active
|
|
// direct-write readers are still cached. Evict them before the CF-owned
|
|
// blob cache goes away so Close() does not retain obsolete footer-less
|
|
// readers for files that are no longer live.
|
|
blob_file_cache_->Evict(file_number);
|
|
}
|
|
}
|
|
}
|
|
|
|
void BlobFilePartitionManager::ResetPartitionState(Partition* partition) {
|
|
partition->writer.reset();
|
|
partition->file_number = 0;
|
|
partition->file_size = 0;
|
|
partition->blob_count = 0;
|
|
partition->total_blob_bytes = 0;
|
|
partition->garbage_blob_count = 0;
|
|
partition->garbage_blob_bytes = 0;
|
|
partition->column_family_id = 0;
|
|
partition->compression = kNoCompression;
|
|
partition->sync_required = false;
|
|
}
|
|
|
|
void BlobFilePartitionManager::AddFilePartitionMapping(uint64_t file_number,
|
|
uint32_t partition_idx) {
|
|
WriteLock lock(&file_partition_mutex_);
|
|
file_to_partition_[file_number] = partition_idx;
|
|
}
|
|
|
|
void BlobFilePartitionManager::RemoveFilePartitionMapping(
|
|
uint64_t file_number) {
|
|
WriteLock lock(&file_partition_mutex_);
|
|
file_to_partition_.erase(file_number);
|
|
}
|
|
|
|
Status BlobFilePartitionManager::OpenNewBlobFile(Partition* partition,
|
|
uint32_t column_family_id,
|
|
CompressionType compression,
|
|
uint32_t partition_idx) {
|
|
assert(partition != nullptr);
|
|
assert(!partition->writer);
|
|
|
|
const uint64_t blob_file_number = file_number_allocator_();
|
|
const std::string blob_file_path = BlobFileName(db_path_, blob_file_number);
|
|
|
|
AddFilePartitionMapping(blob_file_number, partition_idx);
|
|
|
|
if (blob_callback_ != nullptr) {
|
|
blob_callback_->OnBlobFileCreationStarted(
|
|
blob_file_path, column_family_name_,
|
|
/*job_id=*/0, BlobFileCreationReason::kFlush);
|
|
}
|
|
|
|
std::unique_ptr<FSWritableFile> file;
|
|
Status s = NewWritableFile(fs_, blob_file_path, &file, file_options_);
|
|
if (!s.ok()) {
|
|
RemoveFilePartitionMapping(blob_file_number);
|
|
return s;
|
|
}
|
|
|
|
const bool perform_data_verification =
|
|
checksum_handoff_file_types_.Contains(FileType::kBlobFile);
|
|
auto file_writer = std::make_unique<WritableFileWriter>(
|
|
std::move(file), blob_file_path, file_options_, clock_, io_tracer_,
|
|
statistics_, Histograms::BLOB_DB_BLOB_FILE_WRITE_MICROS, listeners_,
|
|
file_checksum_gen_factory_, perform_data_verification);
|
|
|
|
// This only drains WritableFileWriter's buffered bytes so readers can see
|
|
// each appended record promptly. Durability still comes from SyncAllOpenFiles
|
|
// or AppendFooter(), both of which call Sync().
|
|
constexpr bool kDoFlushEachRecord = true;
|
|
auto blob_log_writer = std::make_unique<BlobLogWriter>(
|
|
std::move(file_writer), clock_, statistics_, blob_file_number, use_fsync_,
|
|
kDoFlushEachRecord);
|
|
|
|
constexpr bool has_ttl = false;
|
|
constexpr ExpirationRange expiration_range{};
|
|
BlobLogHeader header(column_family_id, compression, has_ttl,
|
|
expiration_range);
|
|
s = blob_log_writer->WriteHeader(WriteOptions(), header);
|
|
if (!s.ok()) {
|
|
RemoveFilePartitionMapping(blob_file_number);
|
|
return s;
|
|
}
|
|
|
|
partition->writer = std::move(blob_log_writer);
|
|
partition->file_number = blob_file_number;
|
|
partition->file_size = BlobLogHeader::kSize;
|
|
partition->blob_count = 0;
|
|
partition->total_blob_bytes = 0;
|
|
partition->garbage_blob_count = 0;
|
|
partition->garbage_blob_bytes = 0;
|
|
partition->column_family_id = column_family_id;
|
|
partition->compression = compression;
|
|
partition->sync_required = false;
|
|
return Status::OK();
|
|
}
|
|
|
|
Status BlobFilePartitionManager::SealActiveBlobFile(
|
|
const WriteOptions& write_options, Partition* partition,
|
|
SealedFile* sealed_file) {
|
|
assert(partition != nullptr);
|
|
assert(partition->writer);
|
|
assert(sealed_file != nullptr);
|
|
|
|
const uint64_t file_number = partition->file_number;
|
|
Status s =
|
|
FinalizeBlobFile(write_options, partition->writer.get(), file_number,
|
|
partition->blob_count, partition->total_blob_bytes,
|
|
&sealed_file->addition);
|
|
if (!s.ok()) {
|
|
RemoveFilePartitionMapping(file_number);
|
|
ResetPartitionState(partition);
|
|
return s;
|
|
}
|
|
|
|
sealed_file->garbage_blob_count = partition->garbage_blob_count;
|
|
sealed_file->garbage_blob_bytes = partition->garbage_blob_bytes;
|
|
ResetPartitionState(partition);
|
|
return Status::OK();
|
|
}
|
|
|
|
Status BlobFilePartitionManager::SealDeferredFile(
|
|
const WriteOptions& write_options, DeferredFile* deferred,
|
|
SealedFile* sealed_file) {
|
|
assert(deferred != nullptr);
|
|
assert(deferred->writer);
|
|
assert(sealed_file != nullptr);
|
|
|
|
Status s = FinalizeBlobFile(
|
|
write_options, deferred->writer.get(), deferred->file_number,
|
|
deferred->blob_count, deferred->total_blob_bytes, &sealed_file->addition);
|
|
if (!s.ok()) {
|
|
RemoveFilePartitionMapping(deferred->file_number);
|
|
return s;
|
|
}
|
|
|
|
sealed_file->garbage_blob_count = deferred->garbage_blob_count;
|
|
sealed_file->garbage_blob_bytes = deferred->garbage_blob_bytes;
|
|
deferred->writer.reset();
|
|
return Status::OK();
|
|
}
|
|
|
|
Status BlobFilePartitionManager::FinalizeBlobFile(
|
|
const WriteOptions& write_options, BlobLogWriter* writer,
|
|
uint64_t file_number, uint64_t blob_count, uint64_t total_blob_bytes,
|
|
BlobFileAddition* addition) {
|
|
assert(writer != nullptr);
|
|
assert(addition != nullptr);
|
|
|
|
BlobLogFooter footer;
|
|
footer.blob_count = blob_count;
|
|
|
|
std::string checksum_method;
|
|
std::string checksum_value;
|
|
Status s = writer->AppendFooter(write_options, footer, &checksum_method,
|
|
&checksum_value);
|
|
if (!s.ok()) {
|
|
return s;
|
|
}
|
|
|
|
if (blob_callback_ != nullptr) {
|
|
const std::string blob_file_path = BlobFileName(db_path_, file_number);
|
|
s = blob_callback_->OnBlobFileCompleted(
|
|
blob_file_path, column_family_name_, /*job_id=*/0, file_number,
|
|
BlobFileCreationReason::kFlush, s, checksum_value, checksum_method,
|
|
blob_count, total_blob_bytes);
|
|
if (!s.ok()) {
|
|
return s;
|
|
}
|
|
}
|
|
|
|
*addition =
|
|
BlobFileAddition(file_number, blob_count, total_blob_bytes,
|
|
std::move(checksum_method), std::move(checksum_value));
|
|
return Status::OK();
|
|
}
|
|
|
|
void BlobFilePartitionManager::AddSealedFileGarbage(
|
|
const SealedFile& sealed_file, std::vector<BlobFileGarbage>* garbages) {
|
|
assert(garbages != nullptr);
|
|
|
|
if (sealed_file.garbage_blob_count == 0) {
|
|
assert(sealed_file.garbage_blob_bytes == 0);
|
|
return;
|
|
}
|
|
|
|
garbages->emplace_back(sealed_file.addition.GetBlobFileNumber(),
|
|
sealed_file.garbage_blob_count,
|
|
sealed_file.garbage_blob_bytes);
|
|
}
|
|
|
|
bool BlobFilePartitionManager::MarkPartitionGarbage(Partition* partition,
|
|
uint64_t file_number,
|
|
uint64_t blob_count,
|
|
uint64_t blob_bytes) {
|
|
assert(partition != nullptr);
|
|
|
|
if (!partition->writer || partition->file_number != file_number) {
|
|
return false;
|
|
}
|
|
|
|
partition->garbage_blob_count += blob_count;
|
|
partition->garbage_blob_bytes += blob_bytes;
|
|
assert(partition->garbage_blob_count <= partition->blob_count);
|
|
assert(partition->garbage_blob_bytes <= partition->total_blob_bytes);
|
|
return true;
|
|
}
|
|
|
|
bool BlobFilePartitionManager::MarkDeferredFileGarbage(DeferredFile* deferred,
|
|
uint64_t file_number,
|
|
uint64_t blob_count,
|
|
uint64_t blob_bytes) {
|
|
assert(deferred != nullptr);
|
|
|
|
if (deferred->file_number != file_number) {
|
|
return false;
|
|
}
|
|
|
|
deferred->garbage_blob_count += blob_count;
|
|
deferred->garbage_blob_bytes += blob_bytes;
|
|
assert(deferred->garbage_blob_count <= deferred->blob_count);
|
|
assert(deferred->garbage_blob_bytes <= deferred->total_blob_bytes);
|
|
return true;
|
|
}
|
|
|
|
bool BlobFilePartitionManager::MarkSealedFileGarbage(
|
|
std::vector<SealedFile>* sealed_files, uint64_t file_number,
|
|
uint64_t blob_count, uint64_t blob_bytes) {
|
|
assert(sealed_files != nullptr);
|
|
|
|
for (auto& sealed_file : *sealed_files) {
|
|
if (sealed_file.addition.GetBlobFileNumber() != file_number) {
|
|
continue;
|
|
}
|
|
|
|
sealed_file.garbage_blob_count += blob_count;
|
|
sealed_file.garbage_blob_bytes += blob_bytes;
|
|
assert(sealed_file.garbage_blob_count <=
|
|
sealed_file.addition.GetTotalBlobCount());
|
|
assert(sealed_file.garbage_blob_bytes <=
|
|
sealed_file.addition.GetTotalBlobBytes());
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
Status BlobFilePartitionManager::MaybePrepopulateBlobCache(
|
|
const BlobDirectWriteSettings& settings, const Slice& original_value,
|
|
uint64_t blob_file_number, uint64_t blob_offset) {
|
|
if (settings.blob_cache == nullptr ||
|
|
settings.prepopulate_blob_cache != PrepopulateBlobCache::kFlushOnly) {
|
|
return Status::OK();
|
|
}
|
|
|
|
FullTypedCacheInterface<BlobContents, BlobContentsCreator> blob_cache{
|
|
settings.blob_cache};
|
|
const OffsetableCacheKey base_cache_key(db_id_, db_session_id_,
|
|
blob_file_number);
|
|
const CacheKey cache_key = base_cache_key.WithOffset(blob_offset);
|
|
return blob_cache.InsertSaved(cache_key.AsSlice(), original_value, nullptr,
|
|
Cache::Priority::BOTTOM,
|
|
CacheTier::kVolatileTier);
|
|
}
|
|
|
|
uint32_t BlobFilePartitionManager::SelectWideColumnPartition(
|
|
uint32_t column_family_id, const Slice& key,
|
|
const WideColumns& columns) const {
|
|
return strategy_->SelectPartition(num_partitions_, column_family_id, key,
|
|
columns) %
|
|
num_partitions_;
|
|
}
|
|
|
|
Status BlobFilePartitionManager::WriteBlob(
|
|
const WriteOptions& write_options, uint32_t column_family_id,
|
|
CompressionType compression, const Slice& key, const Slice& value,
|
|
uint64_t* blob_file_number, uint64_t* blob_offset, uint64_t* blob_size,
|
|
const BlobDirectWriteSettings& settings, const uint32_t* partition_idx) {
|
|
assert(blob_file_number != nullptr);
|
|
assert(blob_offset != nullptr);
|
|
assert(blob_size != nullptr);
|
|
|
|
// Do compression before taking the partition mutex so large-value CPU work
|
|
// does not serialize writers. A concurrent file rollover can still cause
|
|
// this compressed buffer to be discarded below, which is acceptable on this
|
|
// non-hot failure/configuration-change path.
|
|
GrowableBuffer compressed_value;
|
|
Slice write_value = value;
|
|
if (compression != kNoCompression) {
|
|
auto& compression_state =
|
|
GetDirectWriteCompressionState(compression, settings.compression_opts);
|
|
if (compression_state.compressor == nullptr) {
|
|
return Status::NotSupported(
|
|
"Blob direct write compression type not supported");
|
|
}
|
|
Status s = LegacyForceBuiltinCompression(*compression_state.compressor,
|
|
&compression_state.working_area,
|
|
value, &compressed_value);
|
|
if (!s.ok()) {
|
|
return s;
|
|
}
|
|
write_value = Slice(compressed_value);
|
|
}
|
|
|
|
// Partition selection is based on the logical write inputs. In particular,
|
|
// strategies that inspect value contents or size see the original
|
|
// uncompressed value rather than `write_value`. The modulo here is
|
|
// intentional so custom strategies can return arbitrary hashed or sentinel
|
|
// values without violating the partition bounds.
|
|
const uint32_t selected_partition_idx =
|
|
partition_idx != nullptr
|
|
? (*partition_idx % num_partitions_)
|
|
: (strategy_->SelectPartition(num_partitions_, column_family_id, key,
|
|
value) %
|
|
num_partitions_);
|
|
|
|
{
|
|
MutexLock lock(&mutex_);
|
|
Partition* partition = partitions_[selected_partition_idx].get();
|
|
|
|
auto seal_current_file = [&]() -> Status {
|
|
if (!partition->writer) {
|
|
return Status::OK();
|
|
}
|
|
SealedFile sealed_file;
|
|
Status s = SealActiveBlobFile(write_options, partition, &sealed_file);
|
|
if (s.ok()) {
|
|
current_generation_sealed_files_.push_back(std::move(sealed_file));
|
|
}
|
|
return s;
|
|
};
|
|
|
|
const uint64_t record_size =
|
|
BlobLogRecord::kHeaderSize + key.size() + write_value.size();
|
|
const uint64_t future_file_size =
|
|
partition->file_size + record_size + BlobLogFooter::kSize;
|
|
|
|
if (partition->writer &&
|
|
(partition->column_family_id != column_family_id ||
|
|
partition->compression != compression ||
|
|
(partition->blob_count > 0 && future_file_size > blob_file_size_))) {
|
|
Status s = seal_current_file();
|
|
if (!s.ok()) {
|
|
return s;
|
|
}
|
|
}
|
|
|
|
if (!partition->writer) {
|
|
Status s = OpenNewBlobFile(partition, column_family_id, compression,
|
|
selected_partition_idx);
|
|
if (!s.ok()) {
|
|
return s;
|
|
}
|
|
}
|
|
|
|
uint64_t key_offset = 0;
|
|
Status s = partition->writer->AddRecord(write_options, key, write_value,
|
|
&key_offset, blob_offset);
|
|
if (!s.ok()) {
|
|
return s;
|
|
}
|
|
|
|
partition->sync_required = true;
|
|
partition->blob_count += 1;
|
|
partition->total_blob_bytes += record_size;
|
|
partition->file_size = BlobLogHeader::kSize + partition->total_blob_bytes;
|
|
|
|
*blob_file_number = partition->file_number;
|
|
*blob_size = write_value.size();
|
|
}
|
|
|
|
Status prepopulate_s = MaybePrepopulateBlobCache(
|
|
settings, value, *blob_file_number, *blob_offset);
|
|
if (!prepopulate_s.ok() && info_log_ != nullptr) {
|
|
ROCKS_LOG_WARN(info_log_,
|
|
"Failed to pre-populate direct-write blob cache entry: %s",
|
|
prepopulate_s.ToString().c_str());
|
|
}
|
|
|
|
return Status::OK();
|
|
}
|
|
|
|
void BlobFilePartitionManager::RotateCurrentGeneration() {
|
|
MutexLock lock(&mutex_);
|
|
|
|
GenerationBatch batch;
|
|
batch.sealed_files = std::move(current_generation_sealed_files_);
|
|
current_generation_sealed_files_.clear();
|
|
|
|
for (auto& partition : partitions_) {
|
|
if (!partition->writer) {
|
|
continue;
|
|
}
|
|
|
|
DeferredFile deferred;
|
|
deferred.writer = std::move(partition->writer);
|
|
deferred.file_number = partition->file_number;
|
|
deferred.blob_count = partition->blob_count;
|
|
deferred.total_blob_bytes = partition->total_blob_bytes;
|
|
deferred.garbage_blob_count = partition->garbage_blob_count;
|
|
deferred.garbage_blob_bytes = partition->garbage_blob_bytes;
|
|
ResetPartitionState(partition.get());
|
|
batch.deferred_files.emplace_back(std::move(deferred));
|
|
}
|
|
|
|
pending_generations_.emplace_back(std::move(batch));
|
|
}
|
|
|
|
Status BlobFilePartitionManager::PrepareFlushAdditions(
|
|
const WriteOptions& write_options, size_t num_generations,
|
|
std::vector<BlobFileAddition>* additions,
|
|
std::vector<BlobFileGarbage>* garbages,
|
|
std::vector<std::vector<uint64_t>>* generation_blob_file_numbers) {
|
|
assert(additions != nullptr);
|
|
assert(garbages != nullptr);
|
|
additions->clear();
|
|
garbages->clear();
|
|
if (generation_blob_file_numbers != nullptr) {
|
|
generation_blob_file_numbers->clear();
|
|
}
|
|
|
|
MutexLock lock(&mutex_);
|
|
if (num_generations > pending_generations_.size()) {
|
|
return Status::Corruption(
|
|
"Missing blob direct write generation metadata for flush");
|
|
}
|
|
|
|
for (size_t i = 0; i < num_generations; ++i) {
|
|
GenerationBatch& batch = pending_generations_[i];
|
|
while (!batch.deferred_files.empty()) {
|
|
DeferredFile deferred = std::move(batch.deferred_files.front());
|
|
batch.deferred_files.pop_front();
|
|
|
|
SealedFile sealed_file;
|
|
Status s = SealDeferredFile(write_options, &deferred, &sealed_file);
|
|
if (!s.ok()) {
|
|
return s;
|
|
}
|
|
// Keep each successfully sealed file attached to the generation
|
|
// immediately. If a later seal or the flush job itself fails, retry must
|
|
// reuse these exact on-disk files instead of finalizing replacements.
|
|
batch.sealed_files.push_back(std::move(sealed_file));
|
|
}
|
|
|
|
std::vector<uint64_t>* generation_file_numbers = nullptr;
|
|
if (generation_blob_file_numbers != nullptr) {
|
|
generation_blob_file_numbers->emplace_back();
|
|
generation_file_numbers = &generation_blob_file_numbers->back();
|
|
generation_file_numbers->reserve(batch.sealed_files.size());
|
|
}
|
|
|
|
for (const auto& sealed_file : batch.sealed_files) {
|
|
additions->push_back(sealed_file.addition);
|
|
AddSealedFileGarbage(sealed_file, garbages);
|
|
if (generation_file_numbers != nullptr) {
|
|
generation_file_numbers->push_back(
|
|
sealed_file.addition.GetBlobFileNumber());
|
|
}
|
|
}
|
|
}
|
|
|
|
return Status::OK();
|
|
}
|
|
|
|
Status BlobFilePartitionManager::MarkBlobWriteAsGarbage(uint64_t file_number,
|
|
uint64_t blob_count,
|
|
uint64_t blob_bytes) {
|
|
if (blob_count == 0) {
|
|
assert(blob_bytes == 0);
|
|
return Status::OK();
|
|
}
|
|
|
|
MutexLock lock(&mutex_);
|
|
for (auto& partition : partitions_) {
|
|
if (MarkPartitionGarbage(partition.get(), file_number, blob_count,
|
|
blob_bytes)) {
|
|
return Status::OK();
|
|
}
|
|
}
|
|
|
|
if (MarkSealedFileGarbage(¤t_generation_sealed_files_, file_number,
|
|
blob_count, blob_bytes)) {
|
|
return Status::OK();
|
|
}
|
|
|
|
for (auto& batch : pending_generations_) {
|
|
for (auto& deferred : batch.deferred_files) {
|
|
if (MarkDeferredFileGarbage(&deferred, file_number, blob_count,
|
|
blob_bytes)) {
|
|
return Status::OK();
|
|
}
|
|
}
|
|
if (MarkSealedFileGarbage(&batch.sealed_files, file_number, blob_count,
|
|
blob_bytes)) {
|
|
return Status::OK();
|
|
}
|
|
}
|
|
|
|
const std::string message =
|
|
"Could not match failed blob direct-write rollback for file #" +
|
|
std::to_string(file_number);
|
|
if (info_log_ != nullptr) {
|
|
ROCKS_LOG_ERROR(info_log_, "%s", message.c_str());
|
|
}
|
|
return Status::Corruption(message);
|
|
}
|
|
|
|
void BlobFilePartitionManager::CommitPreparedGenerations(
|
|
size_t num_generations) {
|
|
MutexLock lock(&mutex_);
|
|
while (num_generations-- > 0 && !pending_generations_.empty()) {
|
|
pending_generations_.pop_front();
|
|
}
|
|
}
|
|
|
|
Status BlobFilePartitionManager::SyncAllOpenFiles(
|
|
const WriteOptions& write_options) {
|
|
MutexLock lock(&mutex_);
|
|
for (const auto& partition : partitions_) {
|
|
if (!partition->writer || !partition->sync_required) {
|
|
continue;
|
|
}
|
|
Status s = partition->writer->Sync(write_options);
|
|
if (!s.ok()) {
|
|
return s;
|
|
}
|
|
partition->sync_required = false;
|
|
}
|
|
return Status::OK();
|
|
}
|
|
|
|
void BlobFilePartitionManager::GetActiveBlobFileNumbers(
|
|
UnorderedSet<uint64_t>* file_numbers) const {
|
|
assert(file_numbers != nullptr);
|
|
ReadLock lock(&file_partition_mutex_);
|
|
for (const auto& entry : file_to_partition_) {
|
|
file_numbers->insert(entry.first);
|
|
}
|
|
}
|
|
|
|
void BlobFilePartitionManager::GetProtectedBlobFileNumbers(
|
|
UnorderedSet<uint64_t>* file_numbers) const {
|
|
assert(file_numbers != nullptr);
|
|
ReadLock lock(&file_partition_mutex_);
|
|
for (const auto& entry : protected_blob_file_refs_) {
|
|
file_numbers->insert(entry.first);
|
|
}
|
|
}
|
|
|
|
bool BlobFilePartitionManager::IsTrackedBlobFileNumber(
|
|
uint64_t file_number) const {
|
|
ReadLock lock(&file_partition_mutex_);
|
|
return file_to_partition_.find(file_number) != file_to_partition_.end() ||
|
|
protected_blob_file_refs_.find(file_number) !=
|
|
protected_blob_file_refs_.end();
|
|
}
|
|
|
|
void BlobFilePartitionManager::ProtectSealedBlobFileNumbers(
|
|
const std::vector<uint64_t>& file_numbers) {
|
|
if (file_numbers.empty()) {
|
|
return;
|
|
}
|
|
|
|
WriteLock lock(&file_partition_mutex_);
|
|
for (uint64_t file_number : file_numbers) {
|
|
++protected_blob_file_refs_[file_number];
|
|
}
|
|
}
|
|
|
|
void BlobFilePartitionManager::UnprotectSealedBlobFileNumbers(
|
|
const std::vector<uint64_t>& file_numbers) {
|
|
if (file_numbers.empty()) {
|
|
return;
|
|
}
|
|
|
|
WriteLock lock(&file_partition_mutex_);
|
|
for (uint64_t file_number : file_numbers) {
|
|
auto it = protected_blob_file_refs_.find(file_number);
|
|
if (it == protected_blob_file_refs_.end()) {
|
|
if (info_log_ != nullptr) {
|
|
ROCKS_LOG_ERROR(info_log_,
|
|
"Memtable blob protection underflow for file #%" PRIu64,
|
|
file_number);
|
|
}
|
|
continue;
|
|
}
|
|
|
|
assert(it->second > 0);
|
|
--it->second;
|
|
if (it->second == 0) {
|
|
protected_blob_file_refs_.erase(it);
|
|
if (blob_file_cache_ != nullptr) {
|
|
// Once the last memtable-backed reference goes away, any cached reader
|
|
// for this file is either obsolete or can be reopened through the
|
|
// manifest-visible path. Evict it here so delayed protection does not
|
|
// leave an obsolete blob reader behind until DB close.
|
|
blob_file_cache_->Evict(file_number);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void BlobFilePartitionManager::RemoveFilePartitionMappings(
|
|
const std::vector<uint64_t>& file_numbers) {
|
|
if (file_numbers.empty()) {
|
|
return;
|
|
}
|
|
|
|
WriteLock lock(&file_partition_mutex_);
|
|
for (uint64_t file_number : file_numbers) {
|
|
file_to_partition_.erase(file_number);
|
|
if (blob_file_cache_ != nullptr) {
|
|
// In-flight direct-write reads may have cached a footer-less reader for
|
|
// this file before it was sealed. Drop it now so future manifest-visible
|
|
// reads reopen against the finalized on-disk size and footer state.
|
|
blob_file_cache_->Evict(file_number);
|
|
}
|
|
}
|
|
}
|
|
|
|
Status BlobFilePartitionManager::ResolveBlobDirectWriteIndex(
|
|
const ReadOptions& read_options, const Slice& user_key,
|
|
const BlobIndex& blob_idx, const Version* version,
|
|
BlobFileCache* blob_file_cache, FilePrefetchBuffer* prefetch_buffer,
|
|
PinnableSlice* blob_value, uint64_t* bytes_read) {
|
|
assert(blob_value != nullptr);
|
|
|
|
if (version != nullptr) {
|
|
// Only fall back when the blob file is still owned exclusively by the
|
|
// write path and therefore absent from Version metadata. Once Version
|
|
// knows the file number, every result from Version::GetBlob(), including
|
|
// corruption, must propagate directly.
|
|
if (blob_idx.HasTTL() || blob_idx.IsInlined() ||
|
|
version->storage_info()->GetBlobFileMetaData(blob_idx.file_number()) !=
|
|
nullptr) {
|
|
return version->GetBlob(read_options, user_key, blob_idx, prefetch_buffer,
|
|
blob_value, bytes_read);
|
|
}
|
|
}
|
|
|
|
if (blob_file_cache == nullptr) {
|
|
return version != nullptr ? Status::Corruption("Invalid blob file number")
|
|
: Status::NotFound();
|
|
}
|
|
|
|
if (read_options.read_tier == kBlockCacheTier) {
|
|
// The direct-write fallback below may need to open the blob file reader,
|
|
// which `kBlockCacheTier` forbids. Keep the normal Version-backed path
|
|
// above eligible for cache-only hits.
|
|
return Status::Incomplete("Cannot read blob(s): no disk I/O allowed");
|
|
}
|
|
|
|
Status s;
|
|
CacheHandleGuard<BlobFileReader> reader;
|
|
s = blob_file_cache->GetBlobFileReader(read_options, blob_idx.file_number(),
|
|
&reader,
|
|
/*allow_footer_skip_retry=*/true);
|
|
if (!s.ok()) {
|
|
return s;
|
|
}
|
|
|
|
std::unique_ptr<BlobContents> blob_contents;
|
|
s = reader.GetValue()->GetBlob(read_options, user_key, blob_idx.offset(),
|
|
blob_idx.size(), blob_idx.compression(),
|
|
prefetch_buffer, nullptr, &blob_contents,
|
|
bytes_read);
|
|
if (s.ok()) {
|
|
blob_value->PinSelf(blob_contents->data());
|
|
return s;
|
|
}
|
|
|
|
if (!s.IsCorruption()) {
|
|
return s;
|
|
}
|
|
|
|
reader.Reset();
|
|
blob_file_cache->Evict(blob_idx.file_number());
|
|
|
|
std::unique_ptr<BlobFileReader> fresh_reader;
|
|
s = blob_file_cache->OpenBlobFileReaderUncached(
|
|
read_options, blob_idx.file_number(), &fresh_reader,
|
|
/*allow_footer_skip_retry=*/true);
|
|
if (!s.ok()) {
|
|
return s;
|
|
}
|
|
|
|
std::unique_ptr<BlobContents> fresh_contents;
|
|
s = fresh_reader->GetBlob(read_options, user_key, blob_idx.offset(),
|
|
blob_idx.size(), blob_idx.compression(),
|
|
prefetch_buffer, nullptr, &fresh_contents,
|
|
bytes_read);
|
|
if (s.ok()) {
|
|
blob_value->PinSelf(fresh_contents->data());
|
|
CacheHandleGuard<BlobFileReader> ignored;
|
|
blob_file_cache
|
|
->RefreshBlobFileReader(blob_idx.file_number(), &fresh_reader, &ignored)
|
|
.PermitUncheckedError();
|
|
}
|
|
|
|
return s;
|
|
}
|
|
|
|
} // namespace ROCKSDB_NAMESPACE
|