Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion conanfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

class HomestoreConan(ConanFile):
name = "homestore"
version = "6.20.31"
version = "6.20.32"

homepage = "https://github.com/eBay/Homestore"
description = "HomeStore Storage Engine"
Expand Down
4 changes: 3 additions & 1 deletion src/include/homestore/replication/repl_dev.h
Original file line number Diff line number Diff line change
Expand Up @@ -413,8 +413,10 @@ class ReplDevListener {
// @param blkid - original blkid of the log entry
// @param sgs - sgs to be filled with data
// @param lsn - lsn of the log entry
// @param is_originator - I am the originator or not
virtual folly::Future< std::error_code > on_fetch_data(const int64_t lsn, const sisl::blob& header,
const MultiBlkId& blkid, sisl::sg_list& sgs) {
const MultiBlkId& blkid, sisl::sg_list& sgs,
bool is_originator) {
// default implementation is reading by blkid directly
return data_service().async_read(blkid, sgs, sgs.size);
}
Expand Down
55 changes: 35 additions & 20 deletions src/lib/replication/repl_dev/raft_repl_dev.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1206,7 +1206,28 @@ void RaftReplDev::fetch_data_from_remote(std::vector< repl_req_ptr_t > rreqs) {
shared< flatbuffers::FlatBufferBuilder > builder = std::make_shared< flatbuffers::FlatBufferBuilder >();
RD_LOGD(NO_TRACE_ID, "Data Channel : FetchData from remote: rreq.size={}, my server_id={}", rreqs.size(),
server_id());
auto const& originator = rreqs.front()->remote_blkid().server_id;

int32_t fetch_from_peer_id{0};

if (RaftReplDev::is_fetch_data_only_from_originator()) {
// in raft_repl_dev UT, we do not implement the header parser, so it has to fetch data from originator.
fetch_from_peer_id = rreqs.front()->remote_blkid().server_id;
} else {
auto const& srv_configs = raft_server()->get_config()->get_servers();
std::vector< int32_t > peer_ids;
for (const auto& srv_config : srv_configs) {
auto peer_id = srv_config->get_id();
if (peer_id != m_raft_server_id) peer_ids.emplace_back(peer_id);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fetch data might fail and then generate some garbage here if the selected peer doesn't have data in the following case, but anyway, raft will retry:
T1: leader push blob=100 to F1 and F2
T2: F1 and F2 cannot alloc blk because the related shard not committed
T3: F1 append log, fetch data from F2
T4: F2 append log, fetch data from F1
then F1 and F2 will failed to get data, waiting raft retry and select the leader

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks this is a good point.

Maybe we should only fetch data when we are in resync mode ? in that case (only consider 3 copies) leader and another follower both committed.

Lets track this and verify in SH testing, and decide based on metrics,

  • how much improvement we see in fetch latency ?
  • how many more garbages this approach create vs previous?

}
const auto available_peer_size = peer_ids.size();
if (!available_peer_size) {
RD_LOGE(NO_TRACE_ID, "Data Channel: No available peer to fetch data from, ignoring this fetch call");
return;
}

// here we use time as the random generator to select a random peer
fetch_from_peer_id = peer_ids[static_cast< long int >(std::time(nullptr)) % available_peer_size];
}

for (auto const& rreq : rreqs) {
entries.push_back(CreateRequestEntry(*builder, rreq->lsn(), rreq->term(), rreq->dsn(),
Expand All @@ -1215,15 +1236,9 @@ void RaftReplDev::fetch_data_from_remote(std::vector< repl_req_ptr_t > rreqs) {
rreq->remote_blkid().server_id /* blkid_originator */,
builder->CreateVector(rreq->remote_blkid().blkid.serialize().cbytes(),
rreq->remote_blkid().blkid.serialized_size())));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the originator != leader, will the remote_blkid change (due to garbage and vchunk->pchunk mapping)?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

HO will read from the given remote blkid and verify data, if failed, it will try to read from index.
This fallback allow us send fetch data to anyone.

@Besroy Besroy Jul 16, 2025 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for your explanation. I'm not sure if we need to add some logic in HO to handle scenarios like chunk reclamation or disk bad here. Nevertheless, it looks good to me.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

chunk reclaim is fine as validate_blob will fail. Bad drive (or degrade mode) is a concern..... lets create an issue and wait zhiteng's POC on single-disk-mode....

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Created a issue eBay/HomeObject#330 in HO

// relax this assert if there is a case in same batch originator can be different (can't think of one now)
// but if there were to be such case, we need to group rreqs by originator and send them in separate
// batches;
RD_DBG_ASSERT_EQ(rreq->remote_blkid().server_id, originator, "Unexpected originator for rreq={}",
rreq->to_string());

RD_LOGT(rreq->traceID(),
"Fetching data from originator={}, remote: rreq=[{}], remote_blkid={}, my server_id={}", originator,
rreq->to_string(), rreq->remote_blkid().blkid.to_string(), server_id());
RD_LOGT(rreq->traceID(), "Fetching data from peer={}, remote: rreq=[{}], remote_blkid={}, my server_id={}",
fetch_from_peer_id, rreq->to_string(), rreq->remote_blkid().blkid.to_string(), server_id());
}

builder->FinishSizePrefixed(
Expand All @@ -1233,16 +1248,14 @@ void RaftReplDev::fetch_data_from_remote(std::vector< repl_req_ptr_t > rreqs) {
COUNTER_INCREMENT(m_metrics, fetch_total_entries_cnt, rreqs.size());
COUNTER_INCREMENT(m_metrics, outstanding_data_fetch_cnt, 1);

// leader can change, on the receiving side, we need to check if the leader is still the one who originated the
// blkid;
auto const fetch_start_time = Clock::now();
group_msg_service()
->data_service_request_bidirectional(
originator, FETCH_DATA,
fetch_from_peer_id, FETCH_DATA,
sisl::io_blob_list_t{
sisl::io_blob{builder->GetBufferPointer(), builder->GetSize(), false /* is_aligned */}})
.via(&folly::InlineExecutor::instance())
.thenValue([this, builder, rreqs = std::move(rreqs), fetch_start_time](auto response) {
.thenValue([this, builder, fetch_from_peer_id, rreqs = std::move(rreqs), fetch_start_time](auto response) {
COUNTER_DECREMENT(m_metrics, outstanding_data_fetch_cnt, 1);
auto const fetch_latency_us = get_elapsed_time_us(fetch_start_time);
HISTOGRAM_OBSERVE(m_metrics, rreq_data_fetch_latency_us, fetch_latency_us);
Expand All @@ -1253,9 +1266,9 @@ void RaftReplDev::fetch_data_from_remote(std::vector< repl_req_ptr_t > rreqs) {
// if we are here, it means the original who sent the log entries are down.
// we need to handle error and when the other member becomes leader, it will resend the log entries;
RD_LOGE(NO_TRACE_ID,
"Not able to fetching data from originator={}, error={}, probably originator is down. Will "
"retry when new leader start appending log entries",
rreqs.front()->remote_blkid().server_id, response.error());
"Not able to fetching data from peer={}, error={}, probably leader is down. Will retry when "
"new leader start appending log entries",
fetch_from_peer_id, response.error());
for (auto const& rreq : rreqs) {
// TODO: Set the data_received promise with error, so that waiting threads can be unblocked and
// reject the request. Without that, it will timeout and then reject it.
Expand Down Expand Up @@ -1313,17 +1326,19 @@ void RaftReplDev::on_fetch_data_received(intrusive< sisl::GenericRpcData >& rpc_
// accumulate the sgs for later use (send back to the requester));
sgs_vec.push_back(sgs);

if (originator != server_id()) {
const auto is_originator = (originator == server_id());

if (is_originator) {
RD_LOGD(NO_TRACE_ID, "Data Channel: FetchData received: dsn={} lsn={}", req->dsn(), lsn);
} else {
RD_LOGD(NO_TRACE_ID, "non-originator FetchData received: dsn={} lsn={} originator={}, my_server_id={}",
req->dsn(), lsn, originator, server_id());
} else {
RD_LOGT(NO_TRACE_ID, "Data Channel: FetchData received: dsn={} lsn={}", req->dsn(), lsn);
}

auto const& header = req->user_header();
sisl::blob user_header = sisl::blob{header->Data(), header->size()};
RD_LOGT(NO_TRACE_ID, "Data Channel: FetchData handled, my_blkid={}", local_blkid.to_string());
futs.emplace_back(std::move(m_listener->on_fetch_data(lsn, user_header, local_blkid, sgs)));
futs.emplace_back(std::move(m_listener->on_fetch_data(lsn, user_header, local_blkid, sgs, is_originator)));
}

folly::collectAllUnsafe(futs).thenValue(
Expand Down
8 changes: 8 additions & 0 deletions src/lib/replication/repl_dev/raft_repl_dev.h
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,10 @@ class RaftReplDev : public ReplDev,
std::atomic_uint64_t m_pending_init_req_num;
std::atomic< bool > m_in_quience;

// TODO::remove this after we change raft_repl_dev UT to support fetching data from non-originitor
inline static bool m_fetch_data_only_from_originator =
false; // If true, fetch data only from originator, not from other peers

public:
friend class RaftStateMachine;

Expand Down Expand Up @@ -434,6 +438,10 @@ class RaftReplDev : public ReplDev,
// clear reqs that has allocated blks on the given chunk.
void clear_chunk_req(chunk_num_t chunk_id);

static void enable_fetch_data_only_from_originator(bool enable) { m_fetch_data_only_from_originator = enable; }

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggest add virtual bool ReplDevListener::support_fetch_from_non_leader() {return false}

It will be cleaner.
In HO we overwrite this member function to return true.


static bool is_fetch_data_only_from_originator() { return m_fetch_data_only_from_originator; }

protected:
//////////////// All nuraft::state_mgr overrides ///////////////////////
nuraft::ptr< nuraft::cluster_config > load_config() override;
Expand Down
2 changes: 2 additions & 0 deletions src/tests/test_raft_repl_dev.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -824,6 +824,8 @@ int main(int argc, char* argv[]) {
args.emplace_back(argv[i]);
}

RaftReplDev::enable_fetch_data_only_from_originator(true);

::testing::InitGoogleTest(&parsed_argc, argv);

SISL_OPTIONS_LOAD(parsed_argc, argv, logging, config, test_raft_repl_dev, iomgr, test_common_setup,
Expand Down
2 changes: 2 additions & 0 deletions src/tests/test_raft_repl_dev_dynamic.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -474,6 +474,8 @@ int main(int argc, char* argv[]) {
args.emplace_back(argv[i]);
}

RaftReplDev::enable_fetch_data_only_from_originator(true);

::testing::InitGoogleTest(&parsed_argc, argv);

SISL_OPTIONS_LOAD(parsed_argc, argv, logging, config, test_raft_repl_dev, iomgr, test_common_setup,
Expand Down
Loading