Range keys for subspace don't include first or last key

I’m trying to get the key ranges for a directory, in order to back it up. I noticed that if a subspace has a prefix of \x01 for example, the range of the subspace, using FDBRangeKeys(), will be \x01\x00 to \x01\xff. (go bindings). (I see the same logic in c) But if someone were to write an empty key at the directory root (\x01 -> foo) it wouldn’t be included in the range.

Similarly, the range reported would not include key/value written at \x01\xff, since ExactRange is end-exclusive.

I noticed the backup logic is closer to what I expected–If I were to back up -k \x01 it would back up \x01 (inclusive) to \x02 (exclusive). What is the reasoning for the subspace FDBRangeKeys() behavior? (My guess is it could be related to tuple encoding?) Would it make more sense if it behaved the same as the backup range? Should we be preventing writing to subspace/directory roots?

My current plan is to specify only the directory prefix and let the backup logic calculate the appropriate start and end, rather than using the directory’s FDBRangeKeys().

The Go binding documentation for this is not as clear, but the Python documentation for Subspace.range is a little more so:

Returns a range representing all keys in the subspace that encode tuples strictly starting with the specifed tuple.

In other words, getting the range of a subspace is intended to return all tuples that have the subspace as a prefix (generally a prefix tuple), but not the subspace prefix itself. This is why we choose the end key prefix + \xff – there can be no encoded tuples that come after this. It is correct, though, that there could be non-tuple keys under this prefix that exist after it (e.g. prefix + \xff\xff).

I believe the choice to include or not include the prefix itself was made because it was what we thought would be most useful for this particular API. There’s no fundamental reason it needs to be so, and certainly there will be use cases for either approach.

This sounds like the right idea if your goal is to capture everything starting with that prefix, including the prefix itself and regardless of whether it is a tuple.

Hey A.J., thanks for the explanation. I didn’t expect it to purposely only cover tuples but that makes sense. Would be great if the Go documentation reflected this

Sure thing, I’ve created https://github.com/apple/foundationdb/pull/3783.