Conversation
4e3c483 to
d16f5b2
Compare
6cad84d to
9bd54ba
Compare
9bd54ba to
9b5bb41
Compare
8129731 to
b060d52
Compare
cc979b9 to
66ab0a4
Compare
3642888 to
c4f311a
Compare
18b6384 to
3955aa1
Compare
3955aa1 to
a72b4b0
Compare
230477c to
53e47a7
Compare
baf5649 to
80d6600
Compare
4d5a41f to
c1d8d60
Compare
| // ResetCids purges the KeyStore and repopulates it with the provided cids. | ||
| func (s *KeyStore) ResetCids(ctx context.Context, keysChan <-chan cid.Cid) error { |
There was a problem hiding this comment.
Let's name it Purge
| // ResetCids purges the KeyStore and repopulates it with the provided cids. | |
| func (s *KeyStore) ResetCids(ctx context.Context, keysChan <-chan cid.Cid) error { | |
| // Purge removes all cids from the KeyStore and repopulates it with the provided cids. | |
| func (s *KeyStore) Purge(ctx context.Context, keysChan <-chan cid.Cid) error { |
There was a problem hiding this comment.
I would like to reserve Purge for the following function
func (s *KeyStore) Purge(ctx context.Context, keysChan <-chan mh.Multihash) error { }The system is working with multihashes only, this is the only place where we deal with cids. For now it is easier to consume cids because we need to keep compatibility with the old system, which consumes cids. However, when we deprecate the old system, we should be able to switch to using Multihashes only.
Reserving the name would allow a smoother transition.
| for prefix, hs := range groups { | ||
| dsKey := s.dsKey(prefix) | ||
| var stored []mh.Multihash | ||
| data, err := s.ds.Get(ctx, dsKey) |
There was a problem hiding this comment.
Is it really worth checking if the key is already stored, instead of just overwriting any existing key?
There was a problem hiding this comment.
Yes, because in the current state, we read the stored values, append new values to the existing ones, and write back everything to the datastore.
It would probably be better to store keys directly though (instead of slices of keys).
|
|
||
| for prefix, toDel := range groups { | ||
| dsKey := s.dsKey(prefix) | ||
| data, err := s.ds.Get(ctx, dsKey) |
There was a problem hiding this comment.
Is it necessary to Get before Delete? Seems better to just delete whether or not item is in datastore.
There was a problem hiding this comment.
Yes, we need to get the group of keys to which the target belong, remove it from the group, and write back the group to the datastore.
|
Closing in favor of #1095 |
Note
This PR may be replaced by
Summary
Problem
Reproviding many keys to the DHT one by one is inefficient, because it requires a
GetClosestPeers(orGCP) request for every key.Current state
Currently, reprovides are managed in
boxo/provider. EveryReprovideInterval(22hin Amino DHT), all keys matching the reprovide strategy are reprovided at once. The process is slightly different depending on whether the accelerated DHT client is enabled.Default DHT client
All the keys are reprovided sequentially, using the
go-libp2p-kad-dhtProvide() method. This operation consists in finding thekclosest peers to the given key, and then request them all to store the associated provider record.The process is expensive because it requires a
GCPfor each key (opening approx. 20-30 connections). Timeouts due to unreachable peers make this process very long, resulting in a mean of ~10s in provide time (source: probelab.io 2025-06-13).With 10 seconds per provide, a node using this process could reprovide less than 8'000 keys over the reprovide interval of 22h (using a single thread).
Accelerated DHT client (
fullrt)The accelerated DHT client periodically (every 1h) crawls the DHT swarm to cache the addresses of all discovered peers. It allows it to skip the GCP during the provide request, since it already knows the
kclosest peers and the associated multiaddrs.Hence, the accelerated DHT client is able to provide much more keys during the reprovide interval compared with the default DHT client. However, crawling the DHT swarm is an expensive operation (networking, memory), and since all the keys are reprovided at once, the node will experience a bust period until all keys are reprovided.
Ideally, nodes wouldn't have to crawl the swarm to reprovide content, and the reprovide operation could be smoothed over time to avoid a bust during which the libp2p node is incapable of performing other actions.
Pooling Reprovides
If there are more keys to be reprovided than the number of nodes in the DHT swarm divided by the replication factor (
k), then it means that there are at least two keys that will be provided to the exact same set of peers. This means that the number ofGCPis less than the number of keys to reprovide.For the Amino DHT, containing ~10k DHT servers and having a replication factor of 20, pooling reprovides becomes efficient starting from 500 keys.
Reprovide Sweep
The current process of reproviding all keys at once is bad because it creates a bust. In order to smooth the reprovide process, we can sweep the keyspace from left to right, in order to cover all peers over time. This consists of exploring keyspace regions, corresponding to a set of peers that are close to each other in the Kademlia XOR distance metric.
A keyspace region is explored using a few (typically 2-4 GCP) to discover all the peers it contains. A keyspace region can be identified by a Kademlia identifier prefix, the kademlia identifiers of all peers within this region start with the region's prefix.
Once a region is fully explored, all the keys matching the keyspace region's prefix can be allocated to this set of peers. No additional GCP is needed.
Implementation
This PR contains an implementation of the Reprovide Sweep strategy. The
SweepingReproviderbasically does the following:Provide()andProvideMany()methods. All cids passed through this methods are provided to the DHT as expected.Features
ResetReprovideSetmethod to replace the cids that must be reprovided.Missing features
trie.Triein memory containing all cids to be reprovidedprefix->timestamp).lastProvided->[prefix, timestamp])cid->timestamp). These can {expire, be garbage collected} afterreprovideInterval.Provide()Provide(cidA)beforeProvide(cidB)doesn't mean thatcidAwill be provided beforecidB)[ ] The Dual DHT (used by Kubo) currently has 1SweepingReproviderfor each DHT (LAN and WAN)Allow theSweepingReproviderto (re)provide content to multiple DHT swarms with a single scheduler and cids store (trie)It means that pending regions/cids have to be distinct for each swarm since provide could work for a swarm, but fail in another oneIt will probably require multipleConnectivityCheckers, one for each DHT swarm.ReprovideSweepers is the set of cids that needs to be reprovided (datastore).kuboget rid of theboxo/provider.Systemimplementation ingo-libp2p-kad-dht/dual/reprovider.goTODO
go-libp2p-kad-dht/reprovider/README.mdkubofeat: opt-in new Sweep provide system ipfs/kubo#10834Admin
Depends on:
Need new release of:
Closes #824
Part of ipshipyard/roadmaps#6, ipshipyard/roadmaps#7, ipshipyard/roadmaps#8