-
Notifications
You must be signed in to change notification settings - Fork 1.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement history shards (RIPD-1289) #2258
Conversation
Interesting! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Partial review progress, focusing on Shard
and DatabaseShard
. Generally looks good, but it would be nice to have unit tests covering those classes.
Looks like the AppVeyor build failed due to missing boost serialization lib.
CMakeLists.txt
Outdated
else() | ||
set(BOOST_ROOT ${Boost_INCLUDE_DIRS}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Was moving this set
intentional?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can't find a reason for this change, will revert.
src/ripple/nodestore/impl/Shard.cpp
Outdated
//------------------------------------------------------------------------------ | ||
/* | ||
This file is part of rippled: https://github.com/ripple/rippled | ||
Copyright (c) 2012, 2013 Ripple Labs Inc. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: stale dates?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will Fix.
src/ripple/nodestore/impl/Shard.cpp
Outdated
int cacheAge, beast::Journal& j) | ||
: index_(index) | ||
, firstSeq_(std::max(genesisSeq, detail::firstSeq(index))) | ||
, lastSeq_(detail::lastSeq(index)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this std::max(genesisSeq, detail::lastSeq(index))
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, should be std::max(firstSeq_, detail::lastSeq(index))
src/ripple/nodestore/impl/Shard.cpp
Outdated
namespace NodeStore { | ||
|
||
Shard::Shard(std::uint32_t index, int cacheSz, | ||
int cacheAge, beast::Journal& j) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should cacheAge
be chrono::duration
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The TaggedCache ctor takes a rep
and converts it to a duration. I'll change shard to the same type but the TaggedCache ctor is beyond the PR scope.
return false; | ||
} | ||
|
||
if (backend_->fdlimit() == 0) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What does an fdlimit
of 0 mean? Its not a failure in opening the shard?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some of the backends do not use files and therefore have no file handle requirements. eg. MemoryBackend
, NullBackend
. They can be used in a unit test.
|
||
private: | ||
Application& app_; | ||
mutable std::mutex m_; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see all member functions taking this lock (init
, prepare
). Do only some members need protection?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
prepare
takes the lock. There are a couple of private functions that assume the lock is held and that is noted in the comments. init
doesn't as it should only be called once and before anything else. I will lock and throw if erroneously called more than once. Similar thing for validate
. Thanks!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 One option that I learned from @scottschurr is to have those other private functions take the lock by reference to make it clearer for readers that the lock was already held. I trust the code more than the comments ;). Not necessary, just for your consideration.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the suggestion, will add.
return {}; | ||
} | ||
ledger->setFull(); | ||
//ledger->setImmutable(app_.config()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this commented line as intended?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unnecessary, removed.
return fetchInternal(hash, *backend); | ||
} | ||
|
||
// Lock must be held |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see the lock acquired prior to calling.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
findShardIndexToAdd
is called from prepare
. The first line in prepare
takes the lock.
src/ripple/nodestore/DatabaseShard.h
Outdated
std::uint32_t | ||
seqToShardIndex(std::uint32_t const seq) | ||
{ | ||
return (seq - 1) / ledgersPerShard; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not a sequence number we should see, but if seq=0
this will not return the expected result.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
True, I'll add an assert.
assert(numShards <= maxShardIndex + 1); | ||
|
||
// If equal, have all the shards | ||
if (numShards >= maxShardIndex + 1) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This comparison seems inconsistent with the assert
above.
23ae954
to
773fe36
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I’m still working through this. Left some minor comments, will focus on the logic next.
prevMissing(mCompleteLedgers, mPubLedger->info().seq); | ||
ScopedLockType sl(mCompleteLock); | ||
missing = prevMissing(mCompleteLedgers, | ||
mPubLedger->info().seq, std::uint32_t(32600)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be a symbolic constant, like earliestAvailableLedger
or somesuch.
"Invalid [shard_db] configuration"; | ||
return false; | ||
} | ||
shardStore_->validate(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn’t this just return a true
or false
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not sure a boolean return would be very useful. One or more shards may fail to validate and that is logged but it is up to the user to take action. In the unlikely event that a shard is corrupt, they must delete the shard, same as the node store. If it is missing a node (should never happen on a complete shard), it will auto heal through SHAMap triggering the acquire of the missing nodes.
src/ripple/nodestore/DatabaseNode.h
Outdated
namespace ripple { | ||
namespace NodeStore { | ||
|
||
class DatabaseNode : public Database |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The purpose of this class
is unclear to me. Why do we need it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't, will fix.
src/ripple/nodestore/DatabaseShard.h
Outdated
*/ | ||
virtual | ||
bool | ||
hasLedger(std::uint32_t seq) = 0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No change needed, just food for thought. Should we prefer generic names like contains
over something like hasLedger
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Definitely.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Finished first pass on all the changes. Looks great overall.
src/ripple/app/ledger/LedgerMaster.h
Outdated
@@ -180,7 +181,7 @@ class LedgerMaster | |||
LedgerIndex ledgerIndex); | |||
|
|||
boost::optional <NetClock::time_point> getCloseTimeByHash ( | |||
LedgerHash const& ledgerHash); | |||
LedgerHash const& ledgerHash, std::uint32_t index); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider changing std::uint32_t index
to LedgerIndex index
to match the rest of this file.
{ | ||
JLOG(m_journal.trace()) | ||
<< "fetchForHistory want fetch pack " << missing; | ||
fetch_seq_ = missing; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just trying to follow the larger flow here, but does fetch_seq_ = missing
imply only one ledger is being fetched at a time?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fetch_seq
simply tracks the ledger sequence for the last fetch pack requested. Its purpose is to prevent requesting the same fetch more than once. Generally, more than one ledger is fetched at the same time. For instance, if the ledger isn't present locally and there isn't a fetch pack available, we request the prior ten ledgers at once.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So if someone calls
fetchForHistory(2);
fetchForHistory(1);
fetchForHistory(2);
fetchForHistory(1);
Does that means multiple fetches will happen for the same ledger?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am glad you asked as it made me realize fetchForHistory
should be private! The code in the function was originally in doAdvance
and I broke it out as it was too tall. DoAdvance
is called from the jobQueue and it can and will call fetchForHistory
with a prior sequence. That is by design and it won't hurt anything as inboundledgers::acquire will maintain only one fetch operation per ledger. So multiple fetches will not happen for the same ledger no matter how many times its called.
if (inbound && inbound->isComplete ()) | ||
return inbound->getLedger(); | ||
return {}; | ||
if (inbound->isFailed()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Previously there was a check that inbound
was not null. No longer needed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not needed.
mHash << " cannot be a ledger"; | ||
mFailed = true; | ||
return true; | ||
deserializeHeader(makeSlice(node->getData()), true), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This branch looks very similar to lines 293-307. Consider making a local lambda to combine them.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will do.
src/ripple/shamap/SHAMap.h
Outdated
@@ -87,6 +87,8 @@ class SHAMap | |||
mutable SHAMapState state_; | |||
SHAMapType type_; | |||
bool backed_ = true; // Map is backed by the database | |||
bool full_ = false; // Indicates all nodes should reside in a local store. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How should I think of full_
in relation to backed_
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If backed_
is true, then full_
determines whether or not every node pertaining to this SHAMap resides in a database.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The wording might be confusing. Maybe we can change "full_" to "Map is believed complete in database" or something.
src/ripple/shamap/SHAMap.h
Outdated
@@ -150,6 +158,8 @@ class SHAMap | |||
Marked `const` because the data is not part of | |||
the map contents. | |||
*/ | |||
void setFull (); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is the comment above on L158-160 meant for some other member function?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, will fix, thanks!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
} | ||
|
||
boost::optional<Blob> | ||
AccountStateSF::getNode(SHAMapHash const& nodeHash) const | ||
AccountStateSF::getNode(SHAMapHash const& nodeHash, | ||
std::uint32_t ledgerSeq) const |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: should we leave the second parameter unnamed here since it is not used ?
Blob&& nodeData, SHAMapTreeNode::TNType type) const | ||
void | ||
ConsensusTransSetSF::gotNode(bool fromFilter, SHAMapHash const& nodeHash, | ||
std::uint32_t ledgerSeq, Blob&& nodeData, SHAMapTreeNode::TNType type) const |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same here - both ledgerSeq
are unused
if (mLedger) | ||
tryDB(mLedger->stateMap().family()); | ||
else if(mReason == Reason::SHARD) | ||
tryDB(*app_.shardFamily()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
do we need to check app_.shardFamily()
is set/true or is that already guaranteed by callers?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, guaranteed by the caller.
{ | ||
case Reason::SHARD: | ||
app_.getShardStore()->setStored(mLedger); | ||
// fall through |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
consider wording like this //TODO c++17: [[fallthrough]]
void | ||
save(Archive& ar, | ||
ripple::ClosedInterval<T> const& ci, | ||
const unsigned int version) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
version
is unused - is it just an optional way to version your object internals?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes
case ok: | ||
++fetchHitCount_; | ||
if (nObj) | ||
fetchSz_ += nObj->getData().size(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
//TODO c++17: [[fallthrough]]
... or just add break
if (ledger->info().hash != hash || ledger->info().seq != seq) | ||
{ | ||
JLOG(j_.error()) << | ||
"shard " << std::to_string(seqToShardIndex(seq)) << |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
std::uint32_t
is streamable, so is there any benefit in using std::to_string
for these ?
s += std::to_string(incomplete_->index()); | ||
else | ||
s.pop_back(); | ||
JLOG(j_.fatal()) << s; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is this a FATAL log because it's an error, or just to ensure it shows up in the log?
DatabaseShardImp::asyncFetch(uint256 const& hash, | ||
std::uint32_t seq, std::shared_ptr<NodeObject>& object) | ||
{ | ||
TaggedCache<uint256, NodeObject>* pCache; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
consider making these 18 or so lines a selectCache
function.
int | ||
DatabaseShardImp::getDesiredAsyncReadCount(std::uint32_t seq) | ||
{ | ||
auto const shardIndex {seqToShardIndex(seq)}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
that proposed selectCache
function could be used here too
3bdeb78
to
3e111eb
Compare
The latest commit is causing all kinds of build errors. |
0b8ac7b
to
48a64e3
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 on the latest commits.
auto it = complete_.find(shardIndex); | ||
if (it != complete_.end()) | ||
{ | ||
cache->first = it->second->pCache(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You need to initialize the optional before assigning to the pair members, or just do a cache = std::make_pair
. This crashed for me when running with assert
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch, fixed.
44b5d3b
to
19c1971
Compare
cache = std::make_pair(incomplete_->pCache(), | ||
incomplete_->nCache()); | ||
} | ||
return std::move(cache); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You don't need the std::move
here.
In file included from /home/eah/dev/rippled-merge/src/ripple/unity/nodestore.cpp:32:
/home/eah/dev/rippled-merge/src/ripple/nodestore/impl/DatabaseShardImp.cpp:740:12: warning:
moving a local object in a return statement prevents copy elision
[-Wpessimizing-move]
return std::move(cache);
^
/home/eah/dev/rippled-merge/src/ripple/nodestore/impl/DatabaseShardImp.cpp:740:12: note:
remove std::move call here
return std::move(cache);
^~~~~~~~~~ ~
1 warning generated.
Codecov Report
@@ Coverage Diff @@
## develop #2258 +/- ##
========================================
Coverage 69.64% 69.64%
========================================
Files 705 705
Lines 58426 58426
========================================
Hits 40691 40691
Misses 17735 17735 Continue to review full report at Codecov.
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have not done a full review, but the issues I had with building and the move
warning have been resolved. 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 I believe these changes are safe and should be merged to allow work on shards to continue and to get the network supporting the protocol changes for shards. But I do believe we should also hold off advising people to enable sharding until we get some of the remaining missing features taken care off -- particularly ensuring that enabling sharding doesn't significantly increase resource consumption.
Zarathustra?
|
f235eed
to
149614f
Compare
Jenkins Build SummaryBuilt from this commit Built at 20171219 - 22:52:17 Test Results
|
f46b7d3
to
35decbf
Compare
* Use the Visual Studio 2017 image * Update to rippled_deps17.01
35decbf
to
a75d4ea
Compare
Introduce ledger history shards.
Ripple sharding is a way of distributing historical data. Rippled can be easily configured to begin storing ledger history in shards. Shard data is automatically shared with peers via the ledger acquire process. It is worth noting that shards do not replace the node store. Although redundant, it is entirely possible to hold full history in the node store and the shard store. However, an effective configuration might limit the node store only to recent history. With the current implementation, each shard stores 2^14 ledgers, which may not be optimal. Determining a suitable value will involve carefully considering different aspects of the process. For one, the node store history size should at minimum be twice the ledgers per shard. Reason being that the current shard may be chosen to be stored and it would be wasteful to reacquire the data. Another consideration is the unit of work performed when the server decides to retain the current shard. The time to acquire, number of file handles, and memory cache usage is also affected.
Acquiring shards begins after synchronizing with the network and ledger backfilling. During this time of lower network activity, the shard database may be asked to select a shard to acquire. If a shard is selected, the ledger acquire process begins with the sequence of the last ledger in the shard and works backward to the first. Shards will continue to be acquired until the maximum allocated disk space for shards is reached at which point the current shard may replace an older shard.
Like the node store, the shard store derives from the base class Database. The common interface facilitates replacing the node store when storing or fetching from various places in the project. For instance, synchronization filters are used by the inbound ledger process to save data received from peers to a store. Specifying the database object for the filter allows us to choose where the data is stored. Similarly, SHAMaps are told through a Family, which database to use when fetching or storing. The derived stores themselves rely on the commonality when copying ledgers from one store to another or validating their state and transaction trees.
The following is a simplified high-level explanation of the process involved to store shards.
LedgerMaster asks DatabaseShard for a ledger to acquire. If a ledger is requested, InboundLedgers is asked to acquire it. If it is not stored locally, InboundLedgers asks the Overlay to request the ledger from peers. As data is received from peers, it is assembled and stored. Once all data pertaining to the ledger has been received, the DatabaseShard is notified.
The focus of this review should take place in the ‘Implement DatabaseShard’ commit. I apologize in advance for its size but slicing it smaller proved to be difficult as the dependencies are well interleaved. I will do my best to answer questions and provide clarity where needed.