-
Notifications
You must be signed in to change notification settings - Fork 99
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Fix Segment use-after-move when replicating to NFS (#1756)
## Motivation (this section copied from previous (closed) attempt - #1746) The motivation for the change is to allow `arcticdb-enterprise` to copy blocks to NFS storages without a use-after-move. I explained this in man-group/arcticdb-enterprise#139 but to have an open record: CopyCompressedInterStoreTask has: ``` // Don't bother copying the key segment pair when writing to the final target if (it == std::prev(target_stores_.end())) { (*it)->write_compressed_sync(std::move(key_segment_pair)); } else { auto key_segment_pair_copy = key_segment_pair; (*it)->write_compressed_sync(std::move(key_segment_pair_copy)); } ``` KeySegmentPair has a shared_ptr to a KeySegmentPair, which we can think of here as just a `Segment`. Therefore the old `key_segment_pair_copy` is shallow, the underlying Segment is the same. But the segment eventually gets passed as an rvalue reference further down the stack. In `do_write_impl` we call `put_object` which calls `serialize_header`. This modifies the segment in place and passes that buffer to the AWS SDK. In the `NfsBackedStorage` we have: ``` void NfsBackedStorage::do_write(Composite<KeySegmentPair>&& kvs) { auto enc = kvs.transform([] (auto&& key_seg) { return KeySegmentPair{encode_object_id(key_seg.variant_key()), std::move(key_seg.segment())}; }); s3::detail::do_write_impl(std::move(enc), root_folder_, bucket_name_, *s3_client_, NfsBucketizer{}); } ``` where the segment gets moved from. Subsequent attempts to use the segment (eg copying on to the next store) then fail. man-group/arcticdb-enterprise#139 fixed this issue by cloning the segment, but this approach avoids the (expensive) clone. ## Logical Change Copy the `KeySegmentPair`'s pointer to the `Segment` in `nfs_backed_storage.cpp` rather than moving from the segment. ## Refactor and Testing ### Copy Task Move the CopyCompressedInterStoreTask down to ArcticDB from arcticdb-enterprise. Add a test for it on NFS storage. I've verified that the tests in this commit fail without the refactor in the HEAD~1 commit. The only changes to `CopyCompressedInterstoreTask` from enterprise are: - Pass the `KeySegmentPair` by value in to `write_compressed{_sync}`. The `KeySegmentPair` is cheap to copy (especially considering we are about to copy an object across storages, likely with a network hop). - We have adopted the new `set_key` API of `KeySegmentPair`: ``` if (key_to_write_.has_value()) { key_segment_pair.set_key(*key_to_write_); } ``` - We have namespaced the `ProcessingResult` struct in to the task ### KeySegmentPair - Replace methods returning mutable lvalue references to keys with a `set_key` method. - Remove the `release_segment` method as it dangerously leaves the `KeySegmentPair` pointing at a `Segment` object that has been moved from, and it is not actually necessary. ## Follow up work The non-const `Segment& KeySegmentPair#segment()` API is still dangerous and error prone. I have a follow up change to remove it, but that API change affects very many files and will be best raised separately so that it doesn't block this fix for replication. A draft PR showing a proposal for that change is here - #1757 .
- Loading branch information
1 parent
ec65b8e
commit 498c331
Showing
22 changed files
with
360 additions
and
271 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
#include "bit_rate_stats.hpp" | ||
|
||
#include <folly/Likely.h> | ||
|
||
#include "log/log.hpp" | ||
#include "util/format_bytes.hpp" | ||
#include "entity/performance_tracing.hpp" | ||
|
||
constexpr uint64_t max_bytes{0xFFFFFFFFFF}; | ||
constexpr uint64_t max_time_ms{0xFFFFFF}; | ||
constexpr arcticdb::entity::timestamp log_frequency_ns{60LL * 1000L * 1000L * 1000L}; | ||
|
||
namespace arcticdb::async { | ||
|
||
BitRateStats::BitRateStats(): | ||
last_log_time_ns_(util::SysClock::coarse_nanos_since_epoch()) | ||
{} | ||
|
||
void BitRateStats::add_stat(std::size_t bytes, double time_ms) { | ||
auto now = util::SysClock::coarse_nanos_since_epoch(); | ||
uint64_t stat = data_to_stat(bytes, time_ms); | ||
auto previous_stats = stats_.fetch_add(stat); | ||
auto current_stats = previous_stats + stat; | ||
if (now - last_log_time_ns_ > log_frequency_ns && stats_.compare_exchange_strong(current_stats, 0)) { | ||
last_log_time_ns_ = now; | ||
log_stats(current_stats); | ||
} | ||
} | ||
|
||
uint64_t BitRateStats::data_to_stat(std::size_t bytes, double time_ms) const { | ||
if (UNLIKELY(bytes > max_bytes || time_ms > max_time_ms)) { | ||
log::storage().warn("Bit rate stats provided too large to represent, ignoring: {} in {}ms", | ||
format_bytes(bytes), | ||
time_ms); | ||
return 0; | ||
} | ||
uint64_t stat{(bytes << 24) + static_cast<uint64_t>(time_ms)}; | ||
return stat; | ||
} | ||
|
||
void BitRateStats::log_stats(uint64_t stats) const { | ||
double time_s = static_cast<double>(stats & max_time_ms) / 1000; | ||
double bytes = static_cast<double>(stats >> 24); | ||
double bandwidth = bytes / time_s; | ||
log::storage().info("Byte rate {}/s", format_bytes(bandwidth)); | ||
std::string log_msg = "Current BW is " + format_bytes(bandwidth)+"/s"; | ||
ARCTICDB_SAMPLE_LOG(log_msg.c_str()); | ||
} | ||
|
||
} // arcticdb::async |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
#pragma once | ||
|
||
#include <atomic> | ||
#include <mutex> | ||
|
||
#include "arcticdb/util/clock.hpp" | ||
#include "arcticdb/util/constructors.hpp" | ||
|
||
namespace arcticdb::async { | ||
|
||
class BitRateStats { | ||
public: | ||
BitRateStats(); | ||
void add_stat(std::size_t bytes, double time_ms); | ||
|
||
ARCTICDB_NO_MOVE_OR_COPY(BitRateStats) | ||
private: | ||
uint64_t data_to_stat(std::size_t bytes, double time_ms) const; | ||
void log_stats(uint64_t stats) const; | ||
|
||
// Use an 8 byte atomic for lock free implementation | ||
// Upper 5 bytes represent the number of bytes of data transferred (giving max representable value of 1TB) | ||
// Lower 3 bytes represent the total time in milliseconds (giving max representable value of 4.5 hours) | ||
std::atomic_uint64_t stats_{0}; | ||
|
||
entity::timestamp last_log_time_ns_; | ||
}; | ||
|
||
} // arcticdb::async | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.