Skip to content

Commit 04f52c5

Browse files
simonhollismeta-codesync[bot]
authored andcommitted
Add backwards compabibility for 32-bit DBs
Summary: # What The code here allows for backward compatibility with existing 32-bit identifier databases, whilst creating new databases with 64-bit identifiers. # Stack This is diff (2/4) Reviewed By: iamirzhan Differential Revision: D88271493 fbshipit-source-id: 85ae13b64245a2af510d81be12fc6f7299d456f6
1 parent 3867100 commit 04f52c5

5 files changed

Lines changed: 218 additions & 32 deletions

File tree

glean/rocksdb/database-impl.cpp

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,18 +25,49 @@ const char* admin_names[] = {
2525
"FIRST_UNIT_ID",
2626
"NEXT_UNIT_ID",
2727
"ORPHAN_FACTS",
28+
"OWNERSHIP_FORMAT_VERSION",
2829
};
2930

3031
namespace {
3132

33+
// Read an admin value with backward compatibility for 32-bit -> 64-bit
34+
// migration. If the stored value is 4 bytes (32-bit) and we expect 8 bytes
35+
// (64-bit), read the 32-bit value and widen it.
36+
template <typename T>
37+
folly::Optional<T> readAdminValueCompat(ContainerImpl& container_, AdminId id) {
38+
rocksdb::PinnableSlice val;
39+
auto s = container_.db->Get(
40+
rocksdb::ReadOptions(),
41+
container_.family(Family::admin),
42+
toSlice(id),
43+
&val);
44+
if (s.IsNotFound()) {
45+
return folly::none;
46+
}
47+
check(s);
48+
49+
// Handle backward compatibility: if stored as 32-bit but expected as 64-bit
50+
if constexpr (sizeof(T) == 8) {
51+
if (val.size() == 4) {
52+
// Old 32-bit value, widen to 64-bit
53+
binary::Input inp(byteRange(val));
54+
return static_cast<T>(inp.fixed<uint32_t>());
55+
}
56+
}
57+
58+
// Normal case: size matches expected
59+
binary::Input inp(byteRange(val));
60+
return inp.fixed<T>();
61+
}
62+
3263
template <typename T, typename F>
3364
T initAdminValue(
3465
ContainerImpl& container_,
3566
AdminId id,
3667
T def,
3768
bool write,
3869
F&& notFound) {
39-
auto current = readAdminValue<T>(container_, id);
70+
auto current = readAdminValueCompat<T>(container_, id);
4071
if (current.hasValue()) {
4172
return *current;
4273
} else {
@@ -115,6 +146,24 @@ DatabaseImpl::DatabaseImpl(
115146
rts::error("unexpected database version {}", db_version);
116147
}
117148

149+
// Initialize ownership format version
150+
// For new DBs: write the current (64-bit) format version
151+
// For existing DBs: if no version marker exists, assume 32-bit format
152+
ownership_format_version = static_cast<uint32_t>(initAdminValue(
153+
container_,
154+
AdminId::OWNERSHIP_FORMAT_VERSION,
155+
static_cast<uint64_t>(
156+
container_.mode == Mode::Create ? OWNERSHIP_FORMAT_VERSION_CURRENT
157+
: OWNERSHIP_FORMAT_VERSION_32BIT),
158+
container_.mode == Mode::Create,
159+
[] {}));
160+
161+
VLOG(1) << folly::sformat(
162+
"ownership_format_version: {} ({})",
163+
ownership_format_version,
164+
ownership_format_version == OWNERSHIP_FORMAT_VERSION_32BIT ? "32-bit"
165+
: "64-bit");
166+
118167
stats_.set(loadStats());
119168

120169
if (container_.mode != Mode::ReadOnly) {

glean/rocksdb/database-impl.h

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,17 @@ enum class AdminId : uint32_t {
2929
FIRST_UNIT_ID,
3030
NEXT_UNIT_ID,
3131
ORPHAN_FACTS,
32+
OWNERSHIP_FORMAT_VERSION,
3233
};
3334

35+
// Ownership format versions for backward compatibility:
36+
// - Version 1 (32-bit/absent): 32-bit UnitId and UsetId
37+
// - Version 2: 64-bit UnitId and UsetId
38+
constexpr uint32_t OWNERSHIP_FORMAT_VERSION_32BIT = 1;
39+
constexpr uint32_t OWNERSHIP_FORMAT_VERSION_64BIT = 2;
40+
constexpr uint32_t OWNERSHIP_FORMAT_VERSION_CURRENT =
41+
OWNERSHIP_FORMAT_VERSION_64BIT;
42+
3443
struct DatabaseImpl final : Database {
3544
int64_t db_version;
3645
ContainerImpl container_;
@@ -42,6 +51,11 @@ struct DatabaseImpl final : Database {
4251
std::vector<size_t> ownership_unit_counters;
4352
folly::F14FastMap<uint64_t, size_t> ownership_derived_counters;
4453

54+
// Ownership format version for backward compatibility
55+
// OWNERSHIP_FORMAT_VERSION_32BIT (1): 32-bit UnitId/UsetId (or absent =
56+
// 32-bit) OWNERSHIP_FORMAT_VERSION_64BIT (2): 64-bit UnitId/UsetId
57+
uint32_t ownership_format_version;
58+
4559
// Cached ownership sets, only used when writing.
4660
// Note: must only be accessed under the write lock
4761
std::unique_ptr<rts::Usets> usets_;
@@ -177,7 +191,7 @@ struct DatabaseImpl final : Database {
177191

178192
struct FactOwnerCache {
179193
static void prepare(ContainerImpl& container);
180-
void enable(ContainerImpl& container);
194+
void enable(ContainerImpl& container, uint32_t ownership_format_version);
181195

182196
// Lookup in the cache. Returns none if the cache is not enabled
183197
std::optional<rts::UsetId> getOwner(ContainerImpl& container, Id id);
@@ -190,14 +204,18 @@ struct DatabaseImpl final : Database {
190204
static rts::UsetId lookup(const Page& page, Id id);
191205
static std::unique_ptr<Page> readPage(
192206
ContainerImpl& container,
193-
uint64_t prefix);
207+
uint64_t prefix,
208+
uint32_t ownership_format_version);
194209

195210
struct Cache {
196211
std::vector<rts::UsetId> index;
197212
std::vector<std::unique_ptr<Page>> pages;
198213

199214
// tracks the memory usage of the cache
200215
size_t size_;
216+
217+
// ownership format version for legacy compatibility
218+
uint32_t ownership_format_version_;
201219
};
202220

203221
// nullptr means the cache is disabled (while the DB is writable)

glean/rocksdb/ownership.cpp

Lines changed: 118 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -208,8 +208,20 @@ folly::Optional<UnitId> DatabaseImpl::getUnitId(folly::ByteRange unit) {
208208
&val);
209209
if (!s.IsNotFound()) {
210210
check(s);
211-
assert(val.size() == sizeof(uint64_t));
212-
return folly::loadUnaligned<uint64_t>(val.data());
211+
// Backward compatibility: detect format based on stored value size
212+
// - Legacy format (32-bit): 4 bytes
213+
// - Current format (64-bit): 8 bytes
214+
if (val.size() == sizeof(uint32_t)) {
215+
// Legacy 32-bit format - read as uint32_t and widen to UnitId (uint64_t)
216+
return static_cast<UnitId>(folly::loadUnaligned<uint32_t>(val.data()));
217+
} else if (val.size() == sizeof(uint64_t)) {
218+
// Current 64-bit format
219+
return folly::loadUnaligned<uint64_t>(val.data());
220+
} else {
221+
rts::error(
222+
"rocksdb: invalid UnitId size in ownershipUnits: {} (expected 4 or 8)",
223+
val.size());
224+
}
213225
} else {
214226
return folly::none;
215227
}
@@ -307,8 +319,13 @@ void DatabaseImpl::addOwnership(const std::vector<OwnershipSet>& ownership) {
307319
std::unique_ptr<rts::DerivedFactOwnershipIterator>
308320
DatabaseImpl::getDerivedFactOwnershipIterator(Pid pid) {
309321
struct DerivedFactIterator : rts::DerivedFactOwnershipIterator {
310-
explicit DerivedFactIterator(Pid pid, std::unique_ptr<rocksdb::Iterator> i)
311-
: pid_(pid), iter(std::move(i)) {}
322+
explicit DerivedFactIterator(
323+
Pid pid,
324+
std::unique_ptr<rocksdb::Iterator> i,
325+
uint32_t format_version)
326+
: pid_(pid),
327+
iter(std::move(i)),
328+
ownership_format_version_(format_version) {}
312329

313330
folly::Optional<DerivedFactOwnership> get() override {
314331
if (iter->Valid()) {
@@ -318,19 +335,50 @@ DatabaseImpl::getDerivedFactOwnershipIterator(Pid pid) {
318335
return {};
319336
}
320337
const auto val = iter->value();
321-
const size_t elts = val.size() / (sizeof(uint64_t) + sizeof(uint64_t));
322-
const Id* ids = reinterpret_cast<const Id*>(val.data());
323-
const UsetId* owners = reinterpret_cast<const UsetId*>(
324-
val.data() + elts * sizeof(uint64_t));
325-
iter->Next();
326-
return rts::DerivedFactOwnership{{ids, elts}, {owners, elts}};
338+
339+
// Backward compatibility: element size depends on format version
340+
// 32-bit: sizeof(uint64_t) for Id + sizeof(uint32_t) for UsetId
341+
// 64-bit: sizeof(uint64_t) for Id + sizeof(uint64_t) for UsetId
342+
if (ownership_format_version_ == OWNERSHIP_FORMAT_VERSION_32BIT) {
343+
// 32-bit UsetId format
344+
const size_t uset_size = sizeof(uint32_t);
345+
const size_t elts = val.size() / (sizeof(uint64_t) + uset_size);
346+
const Id* ids = reinterpret_cast<const Id*>(val.data());
347+
const uint32_t* owners32 = reinterpret_cast<const uint32_t*>(
348+
val.data() + elts * sizeof(uint64_t));
349+
350+
// Convert 32-bit owners to 64-bit on the fly
351+
if (elts > 0) {
352+
owners_32bit_.resize(elts);
353+
for (size_t i = 0; i < elts; i++) {
354+
owners_32bit_[i] = static_cast<UsetId>(owners32[i]);
355+
}
356+
} else {
357+
owners_32bit_.clear();
358+
}
359+
360+
iter->Next();
361+
return rts::DerivedFactOwnership{
362+
{ids, elts}, {owners_32bit_.data(), elts}};
363+
} else {
364+
// Current 64-bit UsetId format
365+
const size_t elts =
366+
val.size() / (sizeof(uint64_t) + sizeof(uint64_t));
367+
const Id* ids = reinterpret_cast<const Id*>(val.data());
368+
const UsetId* owners = reinterpret_cast<const UsetId*>(
369+
val.data() + elts * sizeof(uint64_t));
370+
iter->Next();
371+
return rts::DerivedFactOwnership{{ids, elts}, {owners, elts}};
372+
}
327373
} else {
328374
return folly::none;
329375
}
330376
}
331377

332378
Pid pid_;
333379
std::unique_ptr<rocksdb::Iterator> iter;
380+
uint32_t ownership_format_version_;
381+
std::vector<UsetId> owners_32bit_; // buffer for converting 32->64 bit
334382
};
335383

336384
std::unique_ptr<rocksdb::Iterator> iter(container_.db->NewIterator(
@@ -342,7 +390,8 @@ DatabaseImpl::getDerivedFactOwnershipIterator(Pid pid) {
342390

343391
EncodedNat key(pid.toWord());
344392
iter->Seek(slice(key.byteRange()));
345-
return std::make_unique<DerivedFactIterator>(pid, std::move(iter));
393+
return std::make_unique<DerivedFactIterator>(
394+
pid, std::move(iter), ownership_format_version);
346395
}
347396

348397
std::unique_ptr<rts::OwnershipUnitIterator>
@@ -608,7 +657,7 @@ std::unique_ptr<rts::Ownership> DatabaseImpl::getOwnership() {
608657
}
609658

610659
void DatabaseImpl::cacheOwnership() {
611-
factOwnerCache_.enable(container_);
660+
factOwnerCache_.enable(container_, ownership_format_version);
612661
}
613662

614663
void DatabaseImpl::prepareFactOwnerCache() {
@@ -649,7 +698,9 @@ static const size_t PAGE_BITS = 12;
649698
static const uint64_t PAGE_MASK = (1 << PAGE_BITS) - 1;
650699
} // namespace
651700

652-
void DatabaseImpl::FactOwnerCache::enable(ContainerImpl& container) {
701+
void DatabaseImpl::FactOwnerCache::enable(
702+
ContainerImpl& container,
703+
uint32_t format_version) {
653704
auto cache = cache_.ulock();
654705
if (*cache) {
655706
return;
@@ -669,19 +720,41 @@ void DatabaseImpl::FactOwnerCache::enable(ContainerImpl& container) {
669720
}
670721

671722
check(s);
672-
CHECK_EQ(val.size() % sizeof(UsetId), 0);
673-
size_t num = val.size() / sizeof(UsetId);
723+
724+
// Backward compatibility: detect format based on index entry size
725+
// 32-bit: index entries are 4 bytes each
726+
// 64-bit: index entries are 8 bytes each
727+
bool is_32bit_format = (format_version == OWNERSHIP_FORMAT_VERSION_32BIT);
728+
729+
size_t entry_size = is_32bit_format ? sizeof(uint32_t) : sizeof(uint64_t);
730+
CHECK_EQ(val.size() % entry_size, 0);
731+
size_t num = val.size() / entry_size;
674732
std::vector<UsetId> index(num);
675-
const UsetId* start = reinterpret_cast<const UsetId*>(val.data());
676-
std::copy(start, start + num, index.data());
733+
734+
if (is_32bit_format) {
735+
// Read 32-bit entries and widen to 64-bit
736+
const uint32_t* start = reinterpret_cast<const uint32_t*>(val.data());
737+
for (size_t i = 0; i < num; i++) {
738+
index[i] = static_cast<UsetId>(start[i]);
739+
}
740+
} else {
741+
// Read 64-bit entries directly
742+
const UsetId* start = reinterpret_cast<const UsetId*>(val.data());
743+
std::copy(start, start + num, index.data());
744+
}
745+
677746
size_t size = index.size() * sizeof(UsetId);
678747
Cache content{
679748
.index = std::move(index),
680749
.pages = {},
681750
.size_ = size,
751+
.ownership_format_version_ = format_version,
682752
};
683753

684-
VLOG(1) << folly::sformat("owner cache index: {} entries", num);
754+
VLOG(1) << folly::sformat(
755+
"owner cache index: {} entries (format: {})",
756+
num,
757+
is_32bit_format ? "32-bit" : "64-bit");
685758

686759
auto wcache = cache.moveFromUpgradeToWrite();
687760
*wcache = std::make_unique<Cache>(std::move(content));
@@ -690,7 +763,8 @@ void DatabaseImpl::FactOwnerCache::enable(ContainerImpl& container) {
690763
std::unique_ptr<DatabaseImpl::FactOwnerCache::Page>
691764
DatabaseImpl::FactOwnerCache::readPage(
692765
ContainerImpl& container,
693-
uint64_t prefix) {
766+
uint64_t prefix,
767+
uint32_t format_version) {
694768
rocksdb::PinnableSlice val;
695769
auto s = container.db->Get(
696770
rocksdb::ReadOptions(),
@@ -704,14 +778,33 @@ DatabaseImpl::FactOwnerCache::readPage(
704778
}
705779

706780
auto p = std::make_unique<FactOwnerCache::Page>();
707-
size_t num = val.size() / (sizeof(int16_t) + sizeof(UsetId));
781+
782+
// Backward compatibility: page format depends on ownership format version
783+
// 32-bit UsetId: sizeof(int16_t) + sizeof(uint32_t) per entry
784+
// 64-bit UsetId: sizeof(int16_t) + sizeof(uint64_t) per entry
785+
bool is_32bit_format = (format_version == OWNERSHIP_FORMAT_VERSION_32BIT);
786+
size_t uset_size = is_32bit_format ? sizeof(uint32_t) : sizeof(uint64_t);
787+
size_t num = val.size() / (sizeof(int16_t) + uset_size);
788+
708789
p->factIds.resize(num);
709790
p->setIds.resize(num);
791+
710792
const uint16_t* ids = reinterpret_cast<const uint16_t*>(val.data());
711-
const UsetId* sets =
712-
reinterpret_cast<const UsetId*>(val.data() + num * sizeof(uint16_t));
713793
std::copy(ids, ids + num, p->factIds.data());
714-
std::copy(sets, sets + num, p->setIds.data());
794+
795+
if (is_32bit_format) {
796+
// Read 32-bit UsetIds and widen to 64-bit
797+
const uint32_t* sets32 =
798+
reinterpret_cast<const uint32_t*>(val.data() + num * sizeof(uint16_t));
799+
for (size_t i = 0; i < num; i++) {
800+
p->setIds[i] = static_cast<UsetId>(sets32[i]);
801+
}
802+
} else {
803+
// Read 64-bit UsetIds directly
804+
const UsetId* sets =
805+
reinterpret_cast<const UsetId*>(val.data() + num * sizeof(uint16_t));
806+
std::copy(sets, sets + num, p->setIds.data());
807+
}
715808

716809
return p;
717810
}
@@ -790,7 +883,8 @@ std::optional<UsetId> DatabaseImpl::FactOwnerCache::getOwner(
790883
} else {
791884
cachePtr.unlock();
792885

793-
auto p = FactOwnerCache::readPage(container, prefix);
886+
auto format_version = cache->ownership_format_version_;
887+
auto p = FactOwnerCache::readPage(container, prefix, format_version);
794888
auto wlock = cache_.wlock();
795889
auto wcache = wlock->get();
796890
auto size = wcache->size_;

0 commit comments

Comments
 (0)