@@ -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) {
307319std::unique_ptr<rts::DerivedFactOwnershipIterator>
308320DatabaseImpl::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
348397std::unique_ptr<rts::OwnershipUnitIterator>
@@ -608,7 +657,7 @@ std::unique_ptr<rts::Ownership> DatabaseImpl::getOwnership() {
608657}
609658
610659void DatabaseImpl::cacheOwnership () {
611- factOwnerCache_.enable (container_);
660+ factOwnerCache_.enable (container_, ownership_format_version );
612661}
613662
614663void DatabaseImpl::prepareFactOwnerCache () {
@@ -649,7 +698,9 @@ static const size_t PAGE_BITS = 12;
649698static 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) {
690763std::unique_ptr<DatabaseImpl::FactOwnerCache::Page>
691764DatabaseImpl::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