-
Notifications
You must be signed in to change notification settings - Fork 1.2k
protocols/kad: Improve options to efficiently retrieve #2712
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
Changes from 1 commit
7ce7057
769ce4b
f68c903
baf3e60
5db1bfa
91b5f59
30eb56c
0720deb
a0b26b0
e0e79fd
a4f0210
4e448c4
96a952e
2724afd
83a2a86
b483983
aa8a6ce
3912acc
e339cdf
3ced598
ac2e525
5ca8d70
bd05da6
c7c4341
5ecf9cb
055636e
d5cb7e9
0e443d4
c9c00df
05e29d4
1223f02
9c28d98
cfd5461
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
GetRecordapi
- Loading branch information
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -452,6 +452,11 @@ where | |||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| /// Returns the currently configured `replication_factor`. | ||||||||||||||||||||||||||||||||||||||||||||
| pub fn replication_factor(&self) -> NonZeroUsize { | ||||||||||||||||||||||||||||||||||||||||||||
| self.queries.config().replication_factor | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
| /// Gets an iterator over immutable references to all running queries. | ||||||||||||||||||||||||||||||||||||||||||||
| pub fn iter_queries(&self) -> impl Iterator<Item = QueryRef<'_>> { | ||||||||||||||||||||||||||||||||||||||||||||
| self.queries.iter().filter_map(|query| { | ||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -682,37 +687,50 @@ where | |||||||||||||||||||||||||||||||||||||||||||
| /// | ||||||||||||||||||||||||||||||||||||||||||||
| /// The result of this operation is delivered in a | ||||||||||||||||||||||||||||||||||||||||||||
| /// [`KademliaEvent::OutboundQueryCompleted{QueryResult::GetRecord}`]. | ||||||||||||||||||||||||||||||||||||||||||||
| pub fn get_record(&mut self, key: record::Key, quorum: Quorum) -> QueryId { | ||||||||||||||||||||||||||||||||||||||||||||
| let quorum = quorum.eval(self.queries.config().replication_factor); | ||||||||||||||||||||||||||||||||||||||||||||
| let mut records = Vec::with_capacity(quorum.get()); | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| if let Some(record) = self.store.get(&key) { | ||||||||||||||||||||||||||||||||||||||||||||
| pub fn get_record(&mut self, key: record::Key) -> QueryId { | ||||||||||||||||||||||||||||||||||||||||||||
| let record = if let Some(record) = self.store.get(&key) { | ||||||||||||||||||||||||||||||||||||||||||||
| if record.is_expired(Instant::now()) { | ||||||||||||||||||||||||||||||||||||||||||||
| self.store.remove(&key) | ||||||||||||||||||||||||||||||||||||||||||||
| self.store.remove(&key); | ||||||||||||||||||||||||||||||||||||||||||||
| None | ||||||||||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||||||||||
| records.push(PeerRecord { | ||||||||||||||||||||||||||||||||||||||||||||
| Some(PeerRecord { | ||||||||||||||||||||||||||||||||||||||||||||
| peer: None, | ||||||||||||||||||||||||||||||||||||||||||||
| record: record.into_owned(), | ||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||||||||||
| None | ||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||
mxinden marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| let done = records.len() >= quorum.get(); | ||||||||||||||||||||||||||||||||||||||||||||
| let target = kbucket::Key::new(key.clone()); | ||||||||||||||||||||||||||||||||||||||||||||
| let info = QueryInfo::GetRecord { | ||||||||||||||||||||||||||||||||||||||||||||
| key, | ||||||||||||||||||||||||||||||||||||||||||||
| records, | ||||||||||||||||||||||||||||||||||||||||||||
| quorum, | ||||||||||||||||||||||||||||||||||||||||||||
| count: 1, | ||||||||||||||||||||||||||||||||||||||||||||
| record_to_cache: record.as_ref().map(|r| r.record.clone()), | ||||||||||||||||||||||||||||||||||||||||||||
| cache_candidates: BTreeMap::new(), | ||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||
| let peers = self.kbuckets.closest_keys(&target); | ||||||||||||||||||||||||||||||||||||||||||||
| let inner = QueryInner::new(info); | ||||||||||||||||||||||||||||||||||||||||||||
| let id = self.queries.add_iter_closest(target.clone(), peers, inner); // (*) | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
| let id = self.queries.add_iter_closest(target.clone(), peers, inner); // (*) | |
| let id = self.queries.add_iter_closest(target.clone(), peers, inner); |
Is this reference this relevant?
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.
removed, not sure what this was from
Outdated
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.
Say we discover multiple records for the same key. With the above only the first record is replicated. libp2p-kad can not know which of the records is the one to be cached, i.e. it can not make a qualified judgement on which of the records is the best one.
How about removing this automated publishing mechanism and depend on the user to call Kademlia::put_record_to instead?
rust-libp2p/protocols/kad/src/behaviour.rs
Lines 778 to 798 in 6da8497
| /// Stores a record at specific peers, without storing it locally. | |
| /// | |
| /// The given [`Quorum`] is understood in the context of the total | |
| /// number of distinct peers given. | |
| /// | |
| /// If the record's expiration is `None`, the configured record TTL is used. | |
| /// | |
| /// > **Note**: This is not a regular Kademlia DHT operation. It may be | |
| /// > used to selectively update or store a record to specific peers | |
| /// > for the purpose of e.g. making sure these peers have the latest | |
| /// > "version" of a record or to "cache" a record at further peers | |
| /// > to increase the lookup success rate on the DHT for other peers. | |
| /// > | |
| /// > In particular, if lookups are performed with a quorum > 1 multiple | |
| /// > possibly different records may be returned and the standard Kademlia | |
| /// > procedure of "caching" (i.e. storing) a found record at the closest | |
| /// > node to the key that _did not_ return it cannot be employed | |
| /// > transparently. In that case, client code can explicitly choose | |
| /// > which record to store at which peers for analogous write-back | |
| /// > caching or for other reasons. | |
| pub fn put_record_to<I>(&mut self, mut record: Record, peers: I, quorum: Quorum) -> QueryId |
Either way, the comment on put_record_to needs updating.
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 removed the caching, but I am confused why the behaviour of put_record_to changed if I only changed the behaviour of get_record, does that rely on that caching to do this?
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.
According to the Kademlia paper:
For caching purposes, once a lookup succeeds, the requesting node stores the (key, value) pair at the closest node it observed to the key that did not return the value.
Before this pull request, when we did a Kademlia::get_record with a quorum of 1, we could replicate the (key, value) pair to the closest node that did not have the pair. But if we did a quorum of >1, the Kademlia::get_record query would potentially discover more than one value for the given key. Which value would you replicate to the closest node that did not have the value? libp2p-kad can not make that choice. With a quorum of >1 Kademlia::get_record would thus not replicate the pair to the closest node that did not have the value. Instead it relied on the user first picking the right value and then calling Kademlia::put_record_to.
This pull request removes the quorum mechanism. To keep things simple, I suggest also removing the automated replication to the closest node that does not have the value, but instead entirely rely on the user to call put_record_to.
Does that make sense @dignifiedquire? Happy to expand further.
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 d8ac894 does what you requested
Outdated
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 record found in this iteration, including the peer that returned them. | |
| /// The record found in this iteration, including the peer that returned it. |
Outdated
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 find it confusing that this is an Option. At least we need to document what None means here. Or we refactor GetRecordOk:
enum GetRecordOk {
FoundRecord (PeerRecord),
FinishedWithNoAdditionalRecord,
}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 idea, fixed
Outdated
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.
| /// A query initiated by [`Kademlia::get_closest_peers`]. | |
| /// A (repeated) query initiated by [`Kademlia::get_closest_peers`]. |
To be consistent with the comment updates below.
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.
fixed
Outdated
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.
In my eyes tracking this as a usize is error prone and non-descriptive. Why not use ProgressStep here?
Done via cc1b2bd in case you want to cherry-pick.
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.
fixed
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.
Thinking about this some more, this is only needed in tests. In those tests, we can get the value from the
Defaultimplementation ofKademliaConfig.I don't think we should introduce a public facing method just for testing.
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.
fixed