-
Notifications
You must be signed in to change notification settings - Fork 4
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
Tree-based Kademlia Data Structure - k-bucket library #158
Comments
The key difference is that our implementation is flat, while This question/answer explains the difference between different kademlia implementations and what kind of tradeoffs there here: |
Further reading and based on the experiment #326 (comment) have revealed to me a different bucket indexing order. So far we've been using bucket 0 as the closest bucket, and bucket 255 as the farthest bucket where 0 would contain only 1 node ID, and 255 would contain 50% of all possible buckets. However in the kademlia theory, and discussions, the bucket indexing seems to be in reverse instead. This is because of the trie structure used to explain the more advanced kademlia implementations. The trie is also known as a prefix tree, and in this case the prefixes are the bit-prefixes of the node IDs. Which means the literature says that bucket 0 is actually the farthest bucket, and bucket 255 would be the closest bucket. The bucket index then has a direct relationship with how many bits are shared between all the node IDs in the bucket. Consider this image: The largest bucket is the first bucket on the left. You can say this is bucket 1 using 1-based indexing. In this bucket, ALL node IDs must share 1 bit prefix which is The first (1th) bucket can have:
And you can see that they all share the The second (2) bucket can have:
And in this case it must share the 2 bits of the prefix Finally the last bucket shares all 3 bits:
And thus can only 1 node ID in it. The own-node ID is not put in to the bucket, but is shown on the graph above to indicate its bitwise/numeric position among all node IDs. This means the bucket index (1-based indexing) is actually similar to a CIDR notation used in IP subnets. Where in this case One way to notate a bucket is therefore This all makes sense and it means it should be possible to store k-buckets using a binary prefix tree. And no-matter how large the bit size of node IDs are as long as they are >1, the total number of possible node IDs is always an even number. The first-bit therefore determines the first halving of the keyspace. For the 3-bit size node ID, we have 8 possible node IDs, and the first
It also turns out that doing this ensures that buckets always cover contiguous node IDs. In reality this is not the case because not all node IDs will be participating in a network. In the wiki page it says:
This only makes sense if For the answer https://stackoverflow.com/a/51161732/582917:
For our implementation this doesn't really affect it, whether we say that 255 bucket is the largest or 0 bucket is the largest. However if we want to make our impl match the spec, it's probably a good idea to reverse our bucket indexing, however because of lexicographic indexing, that would mean the farthest nodes are going to be at the top of the level DB, whereas right now the closest nodes are at the top. Level DB docs say that |
In leveldb because keys are stored lexicographically and it's possible to create sublevels arbitrarily, it seems that instead of storing buckets using the lexi packed bucket index integer, one could instead just store the bitwise-prefix of the In fact, given that Because we are talking about bit-level prefixes, sublevels won't come into play here because sublevels atm can only use strings, and even if it took bytes, it would only work byte-by-byte, you couldn't get a sublevel of bit-prefixes, only byte-prefixes. But |
I'm looking into https://github.com/tristanls/k-bucket and comparing http://www.scs.stanford.edu/~dm/home/papers/kpos.pdf preprint with the longer paper https://pdos.csail.mit.edu/~petar/papers/maymounkov-kademlia-lncs.pdf. The longer paper goes into more detail regarding bucket splitting, and it appears to be implemented by https://github.com/tristanls/k-bucket. Reading through the readme, it demonstrates that it's an in-memory data structure. It's a growing binary tree, where the tree paths are the bit-prefixes and thus represents a bitwise trie. It grows because it first starts out with only just 1 bucket. Nodes are entered into this bucket until it hits the bucket limit. That bucket limit is still 20. When this occurs, the bucket is split into 2-subbuckets. One of the buckets is the near bucket, and other is the far bucket. The bucket split depends on the prefix bit difference. The first split works by moving all node IDs with 0 as the first bit into one of the buckets, and all other node IDs with 1 as the first bit into the other bucket. Which bucket depends on which bucket is considered far or near depending on the own-node ID. Subsequently new node IDs may be entered into the far bucket or the near bucket. But the far bucket is no longer split, instead it operates on the recycling policy of old node IDs, and there is a parameter of 3 nodes to be contacted before deciding to remove old node IDs. For the near bucket, it continues receiving new node IDs, but once it hits 20, it splits again. Each time it splits again, the splitting behaviour based on the bit-prefix increases. So now it splits on the second bit. The splits continue until the number of buckets equals the bit-length of the own-node ID. This implementation cannot work inside leveldb because leveldb is a flat structure where we store key-values in. However as explained above, the lexicographic encoding seems to imply we can just store node IDs directly and rely on the range querying such as the The concepts of bucket splitting are even unnecessary for us. According to the longer paper, it is the bucket who's range covers the own-node ID which gets split. Which means the far-bucket is the bucket not containing the own-node ID and the near-bucket is the bucket containing the own-node ID. |
In the k-bucket trie implementation, the
This means even after getting all the nodes in the relevant bucket, it still has to do a distance sort. This is a similar result to what happened in our experiment #212 (comment) which means we would need to sort by distance at the very end anyway before returning. The trie prefix keys is not enough to determine actual distance sort. At the same time we did index the The performance is not that bad because buckets have fixed sizes even in the trie implementation. |
Attempting to implement the trie into the leveldb hits a problem. The basic idea is that instead of putting the bucket index into the leveldb as a sublevel, we just store the entire NodeId and rely on the prefix lookup to find buckets. Now to look up all the nodes in the bucket, you have to do a prefix search. This means using the An algorithm needs to turn own Now according to the distance equation:
This means the smallest distance that could exist for a node in that bucket would be And because XOR is commutative, you can do:
You would also need to take For the LTE Now this is getting quite complicated, it seems not worth it. The existing implementation which just stored the bucket indexes directly in the sublevel key seems much simpler. This system wouldn't change the fact we still need the index sublevel to index by the last updated time, it just makes the At any case this exercise was interesting for understanding the structure. And therefore this issue can actually be closed, because we have no need of doing any kind of bucket splitting, since we don't really have a tree structure at all. |
What we learned here is that we do need to sort the bucket distance at the very end. Neither flat-list nor trie indexing ensures that |
This means for #212 @tegefaulkes, you must sort the buckets every time you are acquiring all the nodes in them before buffering them into the closest list. In k-buckets, they just do a full concatenation and then slice/truncation on each step. But it can be done a bit more efficiently I reckon. |
I think your conclusion about bucket 0 being the farthest is incorrect. Note that in the diagram the numbers at the bottom are the IDs of each node, not the distance. Also note that the diagram show bucketing relative to the ID Quote reply didn't work properly. I'm referring to this comment. #158 (comment) |
Doesn't impact the implementation as is. I concluded that trie structure is irrelevant to us, and we have no need for the more advanced kademlia details. We can proceed with our |
Since this is irrelevant now I think we should close this issue and/or remove it from #378. |
It can be auto-closed when #378 merges. |
This was part of our package.json - https://github.com/tristanls/k-bucket.
It may not be used due our kademlia being persistent. But it may be a valuable source to understand how to make our own kademlia implementation more performant.
For now I'm removing k-bucket from package.json.
The text was updated successfully, but these errors were encountered: