From 8b354ebac08bcde7a63d0f4391aa4aec58ccdceb Mon Sep 17 00:00:00 2001 From: liulanzheng Date: Tue, 27 Oct 2026 14:34:36 +0800 Subject: [PATCH] Align compacted data and indexes to 4 KiB Pad each non-zero local extent to a 4 KiB file offset during compaction and align the index start after the data. Apply the layout directly to ordinary and warp commits without a command-line option. Keep zero extents index-only, preserve remote mappings, and retain the existing 512-byte sector units and on-disk record format. Signed-off-by: Lanzheng Liu --- src/overlaybd/lsmt/file.cpp | 32 +++++++++++-- src/overlaybd/lsmt/format_spec.md | 7 +++ src/overlaybd/lsmt/test/test.cpp | 80 +++++++++++++++++++++++++++++++ 3 files changed, 115 insertions(+), 4 deletions(-) diff --git a/src/overlaybd/lsmt/file.cpp b/src/overlaybd/lsmt/file.cpp index 78d65317..585f65a8 100644 --- a/src/overlaybd/lsmt/file.cpp +++ b/src/overlaybd/lsmt/file.cpp @@ -331,11 +331,29 @@ static int is_zero_block(char *buf, size_t n) { return 0; } +// Offsets and the return value are in 512-byte sectors. +static ssize_t pad_compact_data(IFile *dest, uint64_t moffset) { + const uint64_t sectors_per_page = ALIGNMENT4K / ALIGNMENT; + auto padding = (sectors_per_page - moffset % sectors_per_page) % sectors_per_page; + if (padding) { + ALIGNED_MEM4K(zeros, ALIGNMENT4K); + memset(zeros, 0, ALIGNMENT4K); + auto bytes = padding * ALIGNMENT; + if (dest->write(zeros, bytes) != (ssize_t)bytes) + LOG_ERRNO_RETURN(0, -1, "failed to write compact padding"); + } + return padding; +} + static ssize_t pcopy(const CompactOptions &opt, const SegmentMapping &m, uint64_t moffset, vector &index) { + auto padding = pad_compact_data(opt.commit_args->as, moffset); + if (padding < 0) + return -1; + moffset += padding; auto offset = m.moffset * ALIGNMENT; auto count = m.length * ALIGNMENT; - auto bytes = 0; + auto bytes = padding * ALIGNMENT; const size_t BUFFER_SIZE = 32 * 1024; ALIGNED_MEM4K(buf, BUFFER_SIZE); ALIGNED_MEM4K(data, BUFFER_SIZE); @@ -462,7 +480,10 @@ static int compact(const CompactOptions &opt, atomic_uint64_t &compacted_idx_siz return (int)ret; moffset += ret; } - uint64_t index_offset = moffset * ALIGNMENT; + auto padding_sectors = pad_compact_data(dest_file, moffset); + if (padding_sectors < 0) + return -1; + uint64_t index_offset = (moffset + padding_sectors) * ALIGNMENT; auto index_size = compress_raw_index(&compact_index[0], compact_index.size()); LOG_DEBUG("write index to dest_file `, size: `*`", dest_file, index_size, sizeof(SegmentMapping)); @@ -1549,7 +1570,7 @@ class LSMTWarpFile : public LSMTFile { return nwrite; } - size_t compact(CompactOptions &opts, size_t moffset, size_t &nindex) const { + ssize_t compact(CompactOptions &opts, size_t moffset, size_t &nindex) const { auto dest_file = opts.commit_args->as; auto marray = ptr_array(&opts.raw_index[0], opts.index_size); @@ -1574,7 +1595,10 @@ class LSMTWarpFile : public LSMTFile { return (int)ret; moffset += ret; } - uint64_t index_offset = moffset * ALIGNMENT; + auto padding_sectors = pad_compact_data(dest_file, moffset); + if (padding_sectors < 0) + return -1; + uint64_t index_offset = (moffset + padding_sectors) * ALIGNMENT; auto index_size = compress_raw_index(&compact_index[0], compact_index.size()); LOG_DEBUG("write index to dest_file `, offset: `, size: `*`", dest_file, index_offset, index_size, sizeof(SegmentMapping)); diff --git a/src/overlaybd/lsmt/format_spec.md b/src/overlaybd/lsmt/format_spec.md index a47d1a6f..34feed3f 100644 --- a/src/overlaybd/lsmt/format_spec.md +++ b/src/overlaybd/lsmt/format_spec.md @@ -50,6 +50,13 @@ supposed to be referenced by the index entries. And it's possible for this secti to contain garbage data blocks that are not referenced by the any entry of the index. +Compaction aligns the file offset of each non-zero local data extent to 4096 +bytes, and aligns the index start to 4096 bytes after the data. Padding is +zero-filled and adds at most 3584 bytes before each extent. Explicit zero +extents remain index-only, and remote mappings keep their original offsets. +The sector units and wire format are unchanged; mapping offsets and lengths +are still expressed in 512-byte sectors. + ## index The index section is a table that associates logical block addressing (LBA) with diff --git a/src/overlaybd/lsmt/test/test.cpp b/src/overlaybd/lsmt/test/test.cpp index 454a1e4d..558d6154 100644 --- a/src/overlaybd/lsmt/test/test.cpp +++ b/src/overlaybd/lsmt/test/test.cpp @@ -113,6 +113,77 @@ TEST_F(FileTest, reject_oversized_rw_index) { void lookup_test(IMemoryIndex &idx); +static void check_compact_layout(IFileRW *rw, IFile *dest, bool warp) { + SegmentMapping extents[] = {{1, 1, 0}, {8, 1, 0}, {12, 3, 0}, {17, 80, 0}}; + extents[2].discard(); + vector expected(104 * ALIGNMENT, 0); + for (size_t i = 0; i < LEN(extents); ++i) { + auto &m = extents[i]; + auto size = m.length * ALIGNMENT; + auto offset = m.offset * ALIGNMENT; + if (m.zeroed) { + ASSERT_EQ(0, rw->fallocate(3, offset, size)); + } else { + memset(expected.data() + offset, 0x31 + i, size); + ASSERT_EQ((ssize_t)size, rw->pwrite(expected.data() + offset, size, offset)); + } + } + CommitArgs args(dest); + ASSERT_EQ(0, rw->commit(args)); + ALIGNED_MEM4K(trailer, HeaderTrailer::SPACE); + auto end = dest->lseek(0, SEEK_END); + ASSERT_EQ((ssize_t)HeaderTrailer::SPACE, + dest->pread(trailer, HeaderTrailer::SPACE, end - HeaderTrailer::SPACE)); + auto ht = (HeaderTrailer *)trailer; + vector index(ht->index_size); + auto bytes = index.size() * sizeof(SegmentMapping); + ASSERT_EQ((ssize_t)bytes, dest->pread(index.data(), bytes, ht->index_offset)); + uint64_t cursor = HeaderTrailer::SPACE; + vector payload; + size_t n = 0; + for (auto &m : index) { + if (m.offset == Segment::INVALID_OFFSET) + continue; + ASSERT_LT(n, LEN(extents)); + EXPECT_EQ(extents[n].offset + 0, m.offset + 0); + EXPECT_EQ(extents[n].length + 0, m.length + 0); + EXPECT_EQ(extents[n].zeroed + 0, m.zeroed + 0); + if (!m.zeroed) + cursor = (cursor + ALIGNMENT4K - 1) / ALIGNMENT4K * ALIGNMENT4K; + EXPECT_EQ(cursor, m.moffset * ALIGNMENT); + if (!m.zeroed) { + payload.resize(cursor - HeaderTrailer::SPACE + m.length * ALIGNMENT, 0); + memcpy(payload.data() + cursor - HeaderTrailer::SPACE, + expected.data() + m.offset * ALIGNMENT, m.length * ALIGNMENT); + cursor += m.length * ALIGNMENT; + } + ++n; + } + EXPECT_EQ(LEN(extents), n); + cursor = (cursor + ALIGNMENT4K - 1) / ALIGNMENT4K * ALIGNMENT4K; + EXPECT_EQ(cursor, ht->index_offset + 0); + payload.resize(cursor - HeaderTrailer::SPACE, 0); + vector actual(payload.size()); + ASSERT_EQ((ssize_t)actual.size(), dest->pread(actual.data(), actual.size(), HeaderTrailer::SPACE)); + EXPECT_EQ(payload, actual); + // There are no remote extents; the warp reader still requires a target. + unique_ptr ro(warp ? open_warpfile_ro(dest, dest, false) + : LSMT::open_file_ro(dest, false)); + ASSERT_NE(nullptr, ro); + actual.resize(expected.size()); + ASSERT_EQ((ssize_t)actual.size(), ro->pread(actual.data(), actual.size(), 0)); + EXPECT_EQ(expected, actual); +} + +TEST_F(FileTest, compact_layout_4k) { + unique_ptr rw(create_file_rw()); + unique_ptr dest(lfs->open(layer_name.back().c_str(), + O_RDWR | O_CREAT | O_TRUNC, S_IRWXU)); + ASSERT_NE(nullptr, rw); + ASSERT_NE(nullptr, dest); + check_compact_layout(rw.get(), dest.get(), false); +} + TEST(Index, lookup) { const static SegmentMapping mapping[] = {{0, 10, 0}, {10, 10, 50}, {100, 10, 20}}; Index idx(mapping, LEN(mapping), false); @@ -1921,6 +1992,15 @@ TEST_F(WarpFileTest, commit) { delete file; } +TEST_F(WarpFileTest, compact_layout_4k) { + unique_ptr rw(create_warpfile_rw(ut_io_engine)); + unique_ptr dest(lfs->open(layer_name.back().c_str(), + O_RDWR | O_CREAT | O_TRUNC, S_IRWXU)); + ASSERT_NE(nullptr, rw); + ASSERT_NE(nullptr, dest); + check_compact_layout(rw.get(), dest.get(), true); +} + TEST_F(WarpFileTest, large_lba) { CleanUp(); log_output_level = FLAGS_log_level;