Skip to content

Commit a685eea

Browse files
committed
crdbtest: port back Cockroach KeySchema edits
As a part of merging the production KeySchema implementation for CockroachDB, some edits and bug fixes were applied relative to what we've been testing in the Pebble implementation. This commit merges these edits back into the Pebble implementation.
1 parent 5e687eb commit a685eea

1 file changed

Lines changed: 90 additions & 55 deletions

File tree

‎internal/crdbtest/crdb.go‎

Lines changed: 90 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -180,39 +180,31 @@ func EncodeTimestamp(key []byte, walltime uint64, logical uint32) []byte {
180180
return key
181181
}
182182

183-
// DecodeEngineKey decodes an Engine key (the key that gets stored in Pebble).
184-
// Returns:
185-
//
186-
// - roachKey: key prefix without the separator byte; corresponds to
187-
// MVCCKey.Key / EngineKey.Key (roachpb.Key type).
188-
//
189-
// - untypedVersion: when the suffix does not correspond to a timestamp,
190-
// untypedVersion is set to the suffix without the length
191-
// byte; corresponds to EngineKey.Version.
192-
//
193-
// - wallTime, logicalTime: timestamp for the key, or 0 if the key does not
194-
// correspond to a timestamp.
195-
func DecodeEngineKey(
196-
engineKey []byte,
197-
) (roachKey []byte, untypedVersion []byte, wallTime uint64, logicalTime uint32) {
198-
tsLen := int(engineKey[len(engineKey)-1])
199-
tsStart := len(engineKey) - tsLen
200-
if tsStart < 0 {
201-
if invariants.Enabled {
202-
panic("invalid length byte")
203-
}
204-
return nil, nil, 0, 0
183+
// DecodeEngineKey decodes the given bytes as an EngineKey.
184+
func DecodeEngineKey(b []byte) (roachKey, version []byte, ok bool) {
185+
if len(b) == 0 {
186+
return nil, nil, false
205187
}
206-
roachKeyEnd := tsStart - 1
207-
if roachKeyEnd < 0 {
208-
roachKeyEnd = 0
209-
} else if invariants.Enabled && engineKey[roachKeyEnd] != 0 {
210-
panic("invalid separator byte")
188+
// Last byte is the version length + 1 when there is a version,
189+
// else it is 0.
190+
versionLen := int(b[len(b)-1])
191+
if versionLen == 1 {
192+
// The key encodes an empty version, which is not valid.
193+
return nil, nil, false
211194
}
212-
213-
roachKey = engineKey[:roachKeyEnd]
214-
untypedVersion, wallTime, logicalTime = DecodeSuffix(engineKey[tsStart:])
215-
return roachKey, untypedVersion, wallTime, logicalTime
195+
// keyPartEnd points to the sentinel byte.
196+
keyPartEnd := len(b) - 1 - versionLen
197+
if keyPartEnd < 0 || b[keyPartEnd] != 0x00 {
198+
return nil, nil, false
199+
}
200+
// roachKey excludes the sentinel byte.
201+
roachKey = b[:keyPartEnd]
202+
if versionLen > 0 {
203+
// version consists of the bytes after the sentinel and before the
204+
// length.
205+
version = b[keyPartEnd+1 : len(b)-1]
206+
}
207+
return roachKey, version, true
216208
}
217209

218210
// DecodeSuffix decodes the suffix of a key. If the suffix is a timestamp,
@@ -606,9 +598,9 @@ func (kw *cockroachKeyWriter) ComparePrev(key []byte) colblk.KeyComparison {
606598
lp := kw.roachKeys.UnsafeGet(kw.roachKeys.Rows() - 1)
607599
cmpv.CommonPrefixLen = int32(crbytes.CommonPrefix(lp, key[:cmpv.PrefixLen-1]))
608600
if cmpv.CommonPrefixLen == cmpv.PrefixLen-1 {
609-
// Adjust CommonPrefixLen to include the sentinel byte,
601+
// Adjust CommonPrefixLen to include the sentinel byte.
610602
cmpv.CommonPrefixLen = cmpv.PrefixLen
611-
cmpv.UserKeyComparison = int32(CompareRangeSuffixes(key[cmpv.PrefixLen:], kw.prevSuffix))
603+
cmpv.UserKeyComparison = int32(ComparePointSuffixes(key[cmpv.PrefixLen:], kw.prevSuffix))
612604
return cmpv
613605
}
614606
// The keys have different MVCC prefixes. We haven't determined which is
@@ -628,28 +620,48 @@ func (kw *cockroachKeyWriter) ComparePrev(key []byte) colblk.KeyComparison {
628620
func (kw *cockroachKeyWriter) WriteKey(
629621
row int, key []byte, keyPrefixLen, keyPrefixLenSharedWithPrev int32,
630622
) {
623+
if len(key) == 0 {
624+
panic(errors.AssertionFailedf("empty key"))
625+
}
626+
// Last byte is the version length + 1 when there is a version,
627+
// else it is 0.
628+
versionLen := int(key[len(key)-1])
629+
if (len(key)-versionLen) != int(keyPrefixLen) || key[keyPrefixLen-1] != 0x00 {
630+
panic(errors.AssertionFailedf("invalid %d-byte key with %d-byte prefix (%q)",
631+
len(key), keyPrefixLen, key))
632+
}
631633
// TODO(jackson): Avoid copying the previous suffix.
632-
// TODO(jackson): Use keyPrefixLen to speed up decoding.
633-
roachKey, untypedVersion, wallTime, logicalTime := DecodeEngineKey(key)
634634
kw.prevSuffix = append(kw.prevSuffix[:0], key[keyPrefixLen:]...)
635+
635636
// When the roach key is the same, keyPrefixLenSharedWithPrev includes the
636637
// separator byte.
637-
kw.roachKeys.Put(roachKey, min(int(keyPrefixLenSharedWithPrev), len(roachKey)))
638-
kw.wallTimes.Set(row, wallTime)
639-
// The w.logicalTimes builder was initialized with InitWithDefault, so if we
640-
// don't set a value, the column value is implicitly zero. We only need to
641-
// Set anything for non-zero values.
642-
if logicalTime > 0 {
643-
kw.logicalTimes.Set(row, uint64(logicalTime))
644-
}
645-
kw.untypedVersions.Put(untypedVersion)
646-
if wallTime != 0 || logicalTime != 0 {
638+
kw.roachKeys.Put(key[:keyPrefixLen-1], min(int(keyPrefixLenSharedWithPrev), int(keyPrefixLen)-1))
639+
640+
// NB: The w.logicalTimes builder was initialized with InitWithDefault, so
641+
// if we don't set a value, the column value is implicitly zero. We only
642+
// need to Set anything for non-zero values.
643+
var wallTime uint64
644+
var untypedVersion []byte
645+
switch versionLen {
646+
case 0:
647+
// No-op.
648+
kw.suffixTypes |= hasNonMVCCSuffixes
649+
case 9:
647650
kw.suffixTypes |= hasMVCCSuffixes
648-
} else if len(untypedVersion) == 0 {
651+
wallTime = binary.BigEndian.Uint64(key[keyPrefixLen : keyPrefixLen+8])
652+
case 13, 14:
653+
kw.suffixTypes |= hasMVCCSuffixes
654+
wallTime = binary.BigEndian.Uint64(key[keyPrefixLen : keyPrefixLen+8])
655+
kw.logicalTimes.Set(row, uint64(binary.BigEndian.Uint32(key[keyPrefixLen+8:keyPrefixLen+12])))
656+
// NOTE: byte 13 used to store the timestamp's synthetic bit, but this is no
657+
// longer consulted and can be ignored during decoding.
658+
default:
659+
// Not a MVCC timestamp.
649660
kw.suffixTypes |= hasEmptySuffixes
650-
} else {
651-
kw.suffixTypes |= hasNonMVCCSuffixes
661+
untypedVersion = key[keyPrefixLen:]
652662
}
663+
kw.wallTimes.Set(row, wallTime)
664+
kw.untypedVersions.Put(untypedVersion)
653665
}
654666

655667
func (kw *cockroachKeyWriter) MaterializeKey(dst []byte, i int) []byte {
@@ -744,23 +756,46 @@ func (ks *cockroachKeySeeker) init(d *colblk.DataBlockDecoder) {
744756

745757
// IsLowerBound is part of the KeySeeker interface.
746758
func (ks *cockroachKeySeeker) IsLowerBound(k []byte, syntheticSuffix []byte) bool {
747-
roachKey, untypedVersion, wallTime, logicalTime := DecodeEngineKey(k)
748-
if v := Compare(ks.roachKeys.UnsafeFirstSlice(), roachKey); v != 0 {
759+
roachKey, version, ok := DecodeEngineKey(k)
760+
if !ok {
761+
panic(errors.AssertionFailedf("invalid key %q", k))
762+
}
763+
if v := bytes.Compare(ks.roachKeys.UnsafeFirstSlice(), roachKey); v != 0 {
749764
return v > 0
750765
}
766+
// If there's a synthetic suffix, we ignore the block's suffix columns and
767+
// compare the key's suffix to the synthetic suffix.
751768
if len(syntheticSuffix) > 0 {
752-
return Compare(syntheticSuffix, k[len(roachKey)+1:]) >= 0
769+
return ComparePointSuffixes(syntheticSuffix, k[len(roachKey)+1:]) >= 0
753770
}
754-
if len(untypedVersion) > 0 {
771+
var wallTime uint64
772+
var logicalTime uint32
773+
switch len(version) {
774+
case engineKeyNoVersion:
775+
case engineKeyVersionWallTimeLen:
776+
wallTime = binary.BigEndian.Uint64(version[:8])
777+
case engineKeyVersionWallAndLogicalTimeLen, engineKeyVersionWallLogicalAndSyntheticTimeLen:
778+
wallTime = binary.BigEndian.Uint64(version[:8])
779+
logicalTime = binary.BigEndian.Uint32(version[8:12])
780+
default:
781+
// The provided key `k` is not a MVCC key. Assert that the first key in
782+
// the block is also not an MVCC key. If it were, that would mean there
783+
// exists both a MVCC key and a non-MVCC key with the same prefix.
784+
//
785+
// TODO(jackson): Double check that we'll never produce index separators
786+
// that are invalid version lengths.
755787
if invariants.Enabled && ks.mvccWallTimes.At(0) != 0 {
756788
panic("comparing timestamp with untyped suffix")
757789
}
758-
return Compare(ks.untypedVersions.At(0), untypedVersion) >= 0
790+
return ComparePointSuffixes(ks.untypedVersions.At(0), version) >= 0
759791
}
792+
793+
// NB: The sign comparison is inverted because suffixes are sorted such that
794+
// the largest timestamps are "smaller" in the lexicographical ordering.
760795
if v := cmp.Compare(ks.mvccWallTimes.At(0), wallTime); v != 0 {
761-
return v > 0
796+
return v < 0
762797
}
763-
return cmp.Compare(uint32(ks.mvccLogical.At(0)), logicalTime) >= 0
798+
return cmp.Compare(uint32(ks.mvccLogical.At(0)), logicalTime) <= 0
764799
}
765800

766801
// SeekGE is part of the KeySeeker interface.

0 commit comments

Comments
 (0)