From d61bdb1962230b2e6913da950295a7f43995b2e9 Mon Sep 17 00:00:00 2001 From: nym21 Date: Sun, 2 Aug 2026 22:47:43 +0200 Subject: [PATCH] global: fjall+lsm-tree+vecdb --- .gitignore | 1 + Cargo.lock | 79 +- Cargo.toml | 6 +- crates/brk_store/src/lib.rs | 17 +- crates/byteview/Cargo.toml | 21 + crates/byteview/LICENSE-APACHE | 176 +++ crates/byteview/LICENSE-MIT | 21 + crates/byteview/README.md | 76 + crates/byteview/src/builder.rs | 33 + crates/byteview/src/byteview.rs | 1283 ++++++++++++++++ crates/byteview/src/lib.rs | 57 + crates/byteview/src/strview.rs | 429 ++++++ crates/fjall/.config/nextest.toml | 2 - crates/fjall/.gitignore | 24 - crates/fjall/.rustfmt.toml | 3 - crates/fjall/.vscode/settings.json | 3 - crates/fjall/CHANGELOG.md | 50 - crates/fjall/CONTRIBUTING.md | 11 - crates/fjall/Cargo.toml | 18 +- crates/fjall/README.md | 207 --- crates/fjall/commit.nu | 64 - crates/fjall/compile_examples.mjs | 60 - crates/fjall/kawaii.png | Bin 92194 -> 0 bytes crates/fjall/logo.png | Bin 17908 -> 0 bytes crates/fjall/renovate.json | 6 - crates/fjall/src/batch/item.rs | 1 - crates/fjall/src/batch/mod.rs | 1 - crates/fjall/src/builder.rs | 14 +- crates/fjall/src/compaction/mod.rs | 2 +- crates/fjall/src/compaction/worker.rs | 6 +- crates/fjall/src/db.rs | 40 +- crates/fjall/src/db_test.rs | 108 +- crates/fjall/src/drop.rs | 25 - crates/fjall/src/error.rs | 2 +- crates/fjall/src/ingestion.rs | 46 +- crates/fjall/src/iter.rs | 2 +- crates/fjall/src/journal/batch_reader.rs | 10 +- crates/fjall/src/journal/entry.rs | 4 +- crates/fjall/src/journal/manager.rs | 6 - crates/fjall/src/journal/mod.rs | 8 +- crates/fjall/src/journal/writer.rs | 2 +- .../fjall/src/keyspace/config/compression.rs | 2 +- crates/fjall/src/keyspace/mod.rs | 62 +- crates/fjall/src/keyspace/options.rs | 230 +-- crates/fjall/src/lib.rs | 23 +- crates/fjall/src/locked_file.rs | 4 +- crates/fjall/src/meta_keyspace.rs | 96 +- crates/fjall/src/poison.rs | 2 +- crates/fjall/src/recovery.rs | 7 +- crates/fjall/src/snapshot.rs | 2 +- crates/fjall/src/snapshot_nonce.rs | 2 +- crates/fjall/src/snapshot_tracker.rs | 4 +- crates/fjall/src/supervisor.rs | 2 +- crates/fjall/src/tx/mod.rs | 7 - .../src/tx/optimistic/conflict_manager.rs | 197 --- crates/fjall/src/tx/optimistic/keyspace.rs | 502 ------- crates/fjall/src/tx/optimistic/mod.rs | 184 --- crates/fjall/src/tx/optimistic/oracle.rs | 121 -- crates/fjall/src/tx/optimistic/write_tx.rs | 891 ------------ crates/fjall/src/tx/single_writer/keyspace.rs | 469 ------ crates/fjall/src/tx/single_writer/mod.rs | 189 --- crates/fjall/src/tx/single_writer/write_tx.rs | 375 ----- crates/fjall/src/tx/write_tx.rs | 404 ----- crates/fjall/src/version.rs | 13 +- crates/fjall/src/worker_pool.rs | 6 +- crates/fjall/src/write_buffer_manager.rs | 2 +- .../v1_keyspace/partitions/a/config | Bin 16 -> 0 bytes .../v1_keyspace/partitions/a/levels | Bin 45 -> 0 bytes .../v1_keyspace/partitions/a/segments/0 | Bin 856 -> 0 bytes .../v1_keyspace/partitions/a/version | Bin 5 -> 0 bytes .../v1_keyspace/partitions/b/config | Bin 16 -> 0 bytes .../v1_keyspace/partitions/b/levels | Bin 45 -> 0 bytes .../v1_keyspace/partitions/b/segments/1 | Bin 616 -> 0 bytes .../v1_keyspace/partitions/b/version | Bin 5 -> 0 bytes .../v1_keyspace/partitions/c/config | Bin 16 -> 0 bytes .../v1_keyspace/partitions/c/levels | Bin 37 -> 0 bytes .../v1_keyspace/partitions/c/version | Bin 5 -> 0 bytes crates/fjall/test_fixture/v1_keyspace/version | Bin 5 -> 0 bytes .../partitions/a/config | Bin 16 -> 0 bytes .../partitions/a/levels | Bin 45 -> 0 bytes .../partitions/a/segments/0 | Bin 856 -> 0 bytes .../partitions/a/version | Bin 5 -> 0 bytes .../partitions/b/config | Bin 16 -> 0 bytes .../partitions/b/levels | Bin 45 -> 0 bytes .../partitions/b/segments/1 | Bin 616 -> 0 bytes .../partitions/b/version | Bin 5 -> 0 bytes .../partitions/c/config | Bin 16 -> 0 bytes .../partitions/c/levels | Bin 37 -> 0 bytes .../partitions/c/version | Bin 5 -> 0 bytes .../v1_keyspace_corrupt_journal/version | Bin 5 -> 0 bytes .../fjall/test_fixture/v2_keyspace/journals/2 | Bin 56240 -> 0 bytes .../v2_keyspace/partitions/default1/config | Bin 30 -> 0 bytes .../v2_keyspace/partitions/default1/levels | Bin 41 -> 0 bytes .../v2_keyspace/partitions/default1/manifest | Bin 7 -> 0 bytes .../partitions/default1/segments/0 | Bin 732 -> 0 bytes .../partitions/default2/blobs/.vlog | 1 - .../partitions/default2/blobs/segments/0 | Bin 1337 -> 0 bytes .../partitions/default2/blobs/vlog_manifest | Bin 16 -> 0 bytes .../v2_keyspace/partitions/default2/config | Bin 44 -> 0 bytes .../v2_keyspace/partitions/default2/levels | Bin 41 -> 0 bytes .../v2_keyspace/partitions/default2/manifest | Bin 7 -> 0 bytes .../partitions/default2/segments/0 | Bin 576 -> 0 bytes crates/fjall/test_fixture/v2_keyspace/version | 1 - .../v2_keyspace_corrupt_journal/journals/2 | Bin 16777216 -> 0 bytes .../partitions/default1/config | Bin 30 -> 0 bytes .../partitions/default1/levels | Bin 41 -> 0 bytes .../partitions/default1/manifest | Bin 7 -> 0 bytes .../partitions/default1/segments/0 | Bin 732 -> 0 bytes .../partitions/default2/blobs/.vlog | 1 - .../partitions/default2/blobs/segments/0 | Bin 1337 -> 0 bytes .../partitions/default2/blobs/vlog_manifest | Bin 16 -> 0 bytes .../partitions/default2/config | Bin 44 -> 0 bytes .../partitions/default2/levels | Bin 41 -> 0 bytes .../partitions/default2/manifest | Bin 7 -> 0 bytes .../partitions/default2/segments/0 | Bin 576 -> 0 bytes .../v2_keyspace_corrupt_journal/version | 1 - .../journals/0.sealed | Bin 46 -> 0 bytes .../journals/1.sealed | Bin 46 -> 0 bytes .../v2_sealed_journal_shenanigans/journals/2 | Bin 46 -> 0 bytes .../v2_sealed_journal_shenanigans/journals/3 | 0 .../partitions/default/config | Bin 30 -> 0 bytes .../partitions/default/levels | Bin 33 -> 0 bytes .../partitions/default/manifest | Bin 7 -> 0 bytes .../v2_sealed_journal_shenanigans/version | 1 - crates/fjall/tests/batch.rs | 29 +- crates/fjall/tests/blob_kv_simple.rs | 63 - crates/fjall/tests/clear_dirty_read.rs | 35 - crates/fjall/tests/fifo_dirty_read.rs | 31 - .../tests/{format_v4.rs => format_v5.rs} | 10 +- crates/fjall/tests/journal_large_value.rs | 31 +- crates/fjall/tests/keyspace_recover.rs | 104 +- crates/fjall/tests/keyspace_snapshot.rs | 12 +- .../fjall/tests/keyspace_v1_load_fixture.rs | 30 - .../fjall/tests/keyspace_v2_load_fixture.rs | 32 - crates/fjall/tests/prefix_complex.rs | 45 - crates/fjall/tests/standard_keyspace.rs | 8 +- crates/fjall/tests/tx_ryow.rs | 31 - crates/fjall/tests/tx_ryow_snapshot.rs | 59 - crates/fjall/tests/write_buffer_size.rs | 31 +- crates/fjall/tests/write_tx.rs | 20 - crates/lsm-tree/.config/nextest.toml | 2 - crates/lsm-tree/.gitignore | 26 - crates/lsm-tree/.rustfmt.toml | 3 - crates/lsm-tree/CONTRIBUTING.md | 11 - crates/lsm-tree/Cargo.toml | 26 +- crates/lsm-tree/README.md | 83 -- crates/lsm-tree/benches/block.rs | 4 +- crates/lsm-tree/benches/bloom.rs | 2 +- crates/lsm-tree/benches/fd_table.rs | 2 +- crates/lsm-tree/benches/level_manifest.rs | 4 +- crates/lsm-tree/benches/memtable.rs | 2 +- crates/lsm-tree/benches/merge.rs | 4 +- crates/lsm-tree/benches/tree.rs | 26 +- crates/lsm-tree/logo.png | Bin 19998 -> 0 bytes crates/lsm-tree/renovate.json | 6 - crates/lsm-tree/src/abstract_tree.rs | 55 +- crates/lsm-tree/src/any_tree.rs | 17 - crates/lsm-tree/src/blob_tree/gc.rs | 307 ---- crates/lsm-tree/src/blob_tree/handle.rs | 32 - crates/lsm-tree/src/blob_tree/ingest.rs | 274 ---- crates/lsm-tree/src/blob_tree/mod.rs | 631 -------- crates/lsm-tree/src/cache.rs | 77 +- crates/lsm-tree/src/compaction/drop_range.rs | 100 -- crates/lsm-tree/src/compaction/fifo.rs | 310 ---- crates/lsm-tree/src/compaction/filter.rs | 156 +- crates/lsm-tree/src/compaction/flavour.rs | 426 +----- crates/lsm-tree/src/compaction/leveled/mod.rs | 42 +- .../lsm-tree/src/compaction/leveled/test.rs | 4 +- crates/lsm-tree/src/compaction/maintenance.rs | 179 --- crates/lsm-tree/src/compaction/major.rs | 2 +- crates/lsm-tree/src/compaction/mod.rs | 27 +- crates/lsm-tree/src/compaction/movedown.rs | 38 - crates/lsm-tree/src/compaction/pulldown.rs | 4 +- crates/lsm-tree/src/compaction/state/mod.rs | 17 +- crates/lsm-tree/src/compaction/stream.rs | 131 +- crates/lsm-tree/src/compaction/tiered.rs | 343 ----- crates/lsm-tree/src/compaction/worker.rs | 438 +----- crates/lsm-tree/src/config/mod.rs | 152 +- crates/lsm-tree/src/descriptor_table.rs | 37 +- crates/lsm-tree/src/file.rs | 6 +- crates/lsm-tree/src/file_accessor.rs | 20 +- crates/lsm-tree/src/format_version.rs | 5 + crates/lsm-tree/src/ingestion.rs | 126 -- crates/lsm-tree/src/iter_guard.rs | 17 +- crates/lsm-tree/src/key.rs | 1 - crates/lsm-tree/src/lib.rs | 31 +- crates/lsm-tree/src/manifest.rs | 22 +- crates/lsm-tree/src/memtable/mod.rs | 2 +- crates/lsm-tree/src/metrics.rs | 212 --- crates/lsm-tree/src/mvcc_stream.rs | 2 +- crates/lsm-tree/src/range.rs | 2 +- crates/lsm-tree/src/run_reader.rs | 2 +- crates/lsm-tree/src/run_scanner.rs | 2 +- crates/lsm-tree/src/seqno.rs | 2 +- crates/lsm-tree/src/slice/mod.rs | 20 +- crates/lsm-tree/src/slice/slice_bytes/mod.rs | 115 -- crates/lsm-tree/src/stop_signal.rs | 2 +- .../src/table/block_index/two_level.rs | 16 - .../src/table/block_index/volatile.rs | 16 - crates/lsm-tree/src/table/data_block/mod.rs | 33 - .../table/filter/blocked_bloom/builder._rs | 169 --- .../src/table/filter/blocked_bloom/mod._rs | 193 --- crates/lsm-tree/src/table/filter/mod.rs | 1 - crates/lsm-tree/src/table/id.rs | 13 +- crates/lsm-tree/src/table/inner.rs | 12 +- crates/lsm-tree/src/table/iter.rs | 14 - crates/lsm-tree/src/table/meta.rs | 19 +- crates/lsm-tree/src/table/mod.rs | 132 +- crates/lsm-tree/src/table/multi_writer.rs | 61 +- crates/lsm-tree/src/table/regions.rs | 6 - crates/lsm-tree/src/table/tests.rs | 57 - crates/lsm-tree/src/table/util.rs | 51 - crates/lsm-tree/src/table/writer/mod.rs | 52 +- crates/lsm-tree/src/time.rs | 23 - crates/lsm-tree/src/tree/ingest.rs | 325 ++--- crates/lsm-tree/src/tree/inner.rs | 34 +- crates/lsm-tree/src/tree/mod.rs | 223 +-- crates/lsm-tree/src/util.rs | 2 +- crates/lsm-tree/src/value.rs | 2 +- crates/lsm-tree/src/value_type.rs | 11 - crates/lsm-tree/src/version/blob_file_list.rs | 60 - crates/lsm-tree/src/version/mod.rs | 252 +--- crates/lsm-tree/src/version/persist.rs | 5 +- crates/lsm-tree/src/version/recovery.rs | 77 +- crates/lsm-tree/src/version/run.rs | 16 +- crates/lsm-tree/src/version/super_version.rs | 32 +- crates/lsm-tree/src/vlog/accessor.rs | 56 - crates/lsm-tree/src/vlog/blob_file/merge.rs | 203 --- crates/lsm-tree/src/vlog/blob_file/meta.rs | 186 --- crates/lsm-tree/src/vlog/blob_file/mod.rs | 156 -- .../src/vlog/blob_file/multi_writer.rs | 242 --- crates/lsm-tree/src/vlog/blob_file/reader.rs | 161 -- crates/lsm-tree/src/vlog/blob_file/scanner.rs | 169 --- crates/lsm-tree/src/vlog/blob_file/writer.rs | 247 ---- crates/lsm-tree/src/vlog/handle.rs | 49 - crates/lsm-tree/src/vlog/mod.rs | 150 -- crates/lsm-tree/test_fixture/v1_tree/config | Bin 16 -> 0 bytes crates/lsm-tree/test_fixture/v1_tree/levels | Bin 45 -> 0 bytes .../lsm-tree/test_fixture/v1_tree/segments/0 | Bin 832 -> 0 bytes crates/lsm-tree/test_fixture/v1_tree/version | Bin 5 -> 0 bytes .../test_fixture/v1_tree_corrupt/config | Bin 16 -> 0 bytes .../test_fixture/v1_tree_corrupt/levels | Bin 45 -> 0 bytes .../test_fixture/v1_tree_corrupt/segments/0 | Bin 832 -> 0 bytes .../test_fixture/v1_tree_corrupt/version | Bin 5 -> 0 bytes crates/lsm-tree/tests/blob_compression.rs | 99 -- crates/lsm-tree/tests/blob_empty_flush.rs | 30 - crates/lsm-tree/tests/blob_fifo_limit.rs | 28 - .../tests/blob_file_full_file_checksum.rs | 121 -- crates/lsm-tree/tests/blob_flush_gc_stats.rs | 71 - crates/lsm-tree/tests/blob_gc_watermark.rs | 105 -- crates/lsm-tree/tests/blob_ingest.rs | 61 - crates/lsm-tree/tests/blob_ingest_relink.rs | 66 - .../blob_major_compact_drop_dead_files.rs | 100 -- .../tests/blob_major_compact_gc_stats.rs | 205 --- .../tests/blob_major_compact_relink.rs | 69 - .../tests/blob_major_compact_relocation.rs | 326 ----- .../blob_major_compact_relocation_recovery.rs | 130 -- crates/lsm-tree/tests/blob_nuke_gc_stats.rs | 110 -- crates/lsm-tree/tests/blob_pinned_fd.rs | 58 - .../lsm-tree/tests/blob_recover_gc_stats.rs | 82 -- crates/lsm-tree/tests/blob_recovery.rs | 49 - .../tests/blob_register_table_rotation.rs | 135 -- crates/lsm-tree/tests/blob_sep_threshold.rs | 30 - crates/lsm-tree/tests/blob_simple.rs | 77 - .../lsm-tree/tests/blob_tree_guarded_size.rs | 25 - .../lsm-tree/tests/blob_tree_reload_blob.rs | 144 -- crates/lsm-tree/tests/compaction_filter.rs | 26 +- .../compaction_filter_gc_stats_change.rs | 263 ---- .../compaction_filter_gc_stats_destroy.rs | 90 -- .../compaction_filter_gc_stats_remove.rs | 90 -- .../compaction_filter_gc_stats_remove_weak.rs | 90 -- .../tests/compaction_filter_snapshot.rs | 2 +- .../lsm-tree/tests/compaction_filter_ttl.rs | 4 +- .../tests/compaction_readers_grouping.rs | 48 +- crates/lsm-tree/tests/dirty_snapshot.rs | 24 - .../lsm-tree/tests/expect_point_read_hits.rs | 2 +- crates/lsm-tree/tests/fifo_ttl.rs | 134 -- crates/lsm-tree/tests/format_v5.rs | 92 ++ crates/lsm-tree/tests/guard_if.rs | 38 +- .../lsm-tree/tests/ingest_dirty_snapshot.rs | 2 +- crates/lsm-tree/tests/ingestion_api.rs | 169 +-- crates/lsm-tree/tests/ingestion_invariants.rs | 54 +- crates/lsm-tree/tests/ingestion_seqno.rs | 2 +- crates/lsm-tree/tests/latest_snapshot.rs | 2 +- crates/lsm-tree/tests/leveled_trivial_move.rs | 2 +- crates/lsm-tree/tests/memtable_id.rs | 2 +- crates/lsm-tree/tests/model_1.rs | 2 +- crates/lsm-tree/tests/model_2.rs | 48 - crates/lsm-tree/tests/model_3.rs | 70 - crates/lsm-tree/tests/model_4.rs | 112 -- crates/lsm-tree/tests/model_5.rs | 87 -- crates/lsm-tree/tests/model_6.rs | 1293 ----------------- crates/lsm-tree/tests/multi_trees.rs | 2 +- crates/lsm-tree/tests/mvcc_slab.rs | 39 +- .../lsm-tree/tests/recover_cleanup_orphans.rs | 52 +- .../lsm-tree/tests/recovery_mac_ds_store.rs | 2 +- .../lsm-tree/tests/recovery_mac_underscore.rs | 2 +- crates/lsm-tree/tests/regression_286.rs | 32 - crates/lsm-tree/tests/snapshot_compact.rs | 2 +- crates/lsm-tree/tests/snapshot_len.rs | 2 +- crates/lsm-tree/tests/snapshot_point_read.rs | 2 +- crates/lsm-tree/tests/snapshot_zombie.rs | 2 +- crates/lsm-tree/tests/static_iterators.rs | 38 +- .../tests/table_full_file_checksum.rs | 2 +- crates/lsm-tree/tests/table_point_reads.rs | 53 +- crates/lsm-tree/tests/table_range.rs | 4 +- crates/lsm-tree/tests/table_range_oob.rs | 2 +- crates/lsm-tree/tests/table_remove_weak.rs | 2 +- .../lsm-tree/tests/table_weak_tombstones.rs | 2 +- crates/lsm-tree/tests/tree_approx_len.rs | 109 +- crates/lsm-tree/tests/tree_bulk_ingest.rs | 108 +- crates/lsm-tree/tests/tree_clear.rs | 2 +- crates/lsm-tree/tests/tree_clear_snapshot.rs | 2 +- crates/lsm-tree/tests/tree_count.rs | 39 +- crates/lsm-tree/tests/tree_delete_loop.rs | 2 +- .../tests/tree_different_block_size.rs | 2 +- crates/lsm-tree/tests/tree_disjoint_iter.rs | 2 +- .../tests/tree_disjoint_point_read.rs | 36 +- crates/lsm-tree/tests/tree_disjoint_prefix.rs | 2 +- crates/lsm-tree/tests/tree_disjoint_range.rs | 2 +- crates/lsm-tree/tests/tree_drop_range.rs | 277 ---- crates/lsm-tree/tests/tree_filter_hit_rate.rs | 86 -- crates/lsm-tree/tests/tree_flush_eviction.rs | 2 +- crates/lsm-tree/tests/tree_guarded_range.rs | 38 +- crates/lsm-tree/tests/tree_kv.rs | 2 +- crates/lsm-tree/tests/tree_l0_point_read.rs | 48 - crates/lsm-tree/tests/tree_l0_range.rs | 51 - .../lsm-tree/tests/tree_major_compaction.rs | 2 +- crates/lsm-tree/tests/tree_mvcc_simple.rs | 2 +- .../tests/tree_non_disjoint_point_read.rs | 2 +- crates/lsm-tree/tests/tree_range.rs | 68 +- .../tests/tree_range_memtable_only.rs | 2 +- crates/lsm-tree/tests/tree_recover_counter.rs | 2 +- .../tests/tree_recover_large_value.rs | 2 +- .../lsm-tree/tests/tree_recovery_versions.rs | 2 +- crates/lsm-tree/tests/tree_reload.rs | 81 +- crates/lsm-tree/tests/tree_reload_pwd.rs | 2 +- .../lsm-tree/tests/tree_sealed_shadowing.rs | 2 +- crates/lsm-tree/tests/tree_seqno.rs | 2 +- crates/lsm-tree/tests/tree_shadowing.rs | 239 +-- crates/lsm-tree/tests/tree_trace.rs | 2 +- crates/lsm-tree/tests/tree_v1_load_fixture.rs | 34 - crates/lsm-tree/tests/tree_weak_delete.rs | 2 +- .../tests/tree_weak_delete_eviction.rs | 2 +- .../lsm-tree/tests/tree_weak_delete_queue.rs | 2 +- crates/lsm-tree/tests/tree_write_read.rs | 2 +- crates/rawdb/Cargo.toml | 2 +- crates/vecdb/Cargo.toml | 4 +- crates/vecdb_derive/Cargo.toml | 2 +- 349 files changed, 2922 insertions(+), 18868 deletions(-) create mode 100644 crates/byteview/Cargo.toml create mode 100644 crates/byteview/LICENSE-APACHE create mode 100644 crates/byteview/LICENSE-MIT create mode 100644 crates/byteview/README.md create mode 100644 crates/byteview/src/builder.rs create mode 100644 crates/byteview/src/byteview.rs create mode 100644 crates/byteview/src/lib.rs create mode 100644 crates/byteview/src/strview.rs delete mode 100644 crates/fjall/.config/nextest.toml delete mode 100644 crates/fjall/.gitignore delete mode 100644 crates/fjall/.rustfmt.toml delete mode 100644 crates/fjall/.vscode/settings.json delete mode 100644 crates/fjall/CHANGELOG.md delete mode 100644 crates/fjall/CONTRIBUTING.md delete mode 100644 crates/fjall/README.md delete mode 100644 crates/fjall/commit.nu delete mode 100644 crates/fjall/compile_examples.mjs delete mode 100644 crates/fjall/kawaii.png delete mode 100644 crates/fjall/logo.png delete mode 100644 crates/fjall/renovate.json delete mode 100644 crates/fjall/src/drop.rs delete mode 100644 crates/fjall/src/tx/mod.rs delete mode 100644 crates/fjall/src/tx/optimistic/conflict_manager.rs delete mode 100644 crates/fjall/src/tx/optimistic/keyspace.rs delete mode 100644 crates/fjall/src/tx/optimistic/mod.rs delete mode 100644 crates/fjall/src/tx/optimistic/oracle.rs delete mode 100644 crates/fjall/src/tx/optimistic/write_tx.rs delete mode 100644 crates/fjall/src/tx/single_writer/keyspace.rs delete mode 100644 crates/fjall/src/tx/single_writer/mod.rs delete mode 100644 crates/fjall/src/tx/single_writer/write_tx.rs delete mode 100644 crates/fjall/src/tx/write_tx.rs delete mode 100644 crates/fjall/test_fixture/v1_keyspace/partitions/a/config delete mode 100644 crates/fjall/test_fixture/v1_keyspace/partitions/a/levels delete mode 100644 crates/fjall/test_fixture/v1_keyspace/partitions/a/segments/0 delete mode 100644 crates/fjall/test_fixture/v1_keyspace/partitions/a/version delete mode 100644 crates/fjall/test_fixture/v1_keyspace/partitions/b/config delete mode 100644 crates/fjall/test_fixture/v1_keyspace/partitions/b/levels delete mode 100644 crates/fjall/test_fixture/v1_keyspace/partitions/b/segments/1 delete mode 100644 crates/fjall/test_fixture/v1_keyspace/partitions/b/version delete mode 100644 crates/fjall/test_fixture/v1_keyspace/partitions/c/config delete mode 100644 crates/fjall/test_fixture/v1_keyspace/partitions/c/levels delete mode 100644 crates/fjall/test_fixture/v1_keyspace/partitions/c/version delete mode 100644 crates/fjall/test_fixture/v1_keyspace/version delete mode 100644 crates/fjall/test_fixture/v1_keyspace_corrupt_journal/partitions/a/config delete mode 100644 crates/fjall/test_fixture/v1_keyspace_corrupt_journal/partitions/a/levels delete mode 100644 crates/fjall/test_fixture/v1_keyspace_corrupt_journal/partitions/a/segments/0 delete mode 100644 crates/fjall/test_fixture/v1_keyspace_corrupt_journal/partitions/a/version delete mode 100644 crates/fjall/test_fixture/v1_keyspace_corrupt_journal/partitions/b/config delete mode 100644 crates/fjall/test_fixture/v1_keyspace_corrupt_journal/partitions/b/levels delete mode 100644 crates/fjall/test_fixture/v1_keyspace_corrupt_journal/partitions/b/segments/1 delete mode 100644 crates/fjall/test_fixture/v1_keyspace_corrupt_journal/partitions/b/version delete mode 100644 crates/fjall/test_fixture/v1_keyspace_corrupt_journal/partitions/c/config delete mode 100644 crates/fjall/test_fixture/v1_keyspace_corrupt_journal/partitions/c/levels delete mode 100644 crates/fjall/test_fixture/v1_keyspace_corrupt_journal/partitions/c/version delete mode 100644 crates/fjall/test_fixture/v1_keyspace_corrupt_journal/version delete mode 100644 crates/fjall/test_fixture/v2_keyspace/journals/2 delete mode 100644 crates/fjall/test_fixture/v2_keyspace/partitions/default1/config delete mode 100644 crates/fjall/test_fixture/v2_keyspace/partitions/default1/levels delete mode 100644 crates/fjall/test_fixture/v2_keyspace/partitions/default1/manifest delete mode 100644 crates/fjall/test_fixture/v2_keyspace/partitions/default1/segments/0 delete mode 100644 crates/fjall/test_fixture/v2_keyspace/partitions/default2/blobs/.vlog delete mode 100644 crates/fjall/test_fixture/v2_keyspace/partitions/default2/blobs/segments/0 delete mode 100644 crates/fjall/test_fixture/v2_keyspace/partitions/default2/blobs/vlog_manifest delete mode 100644 crates/fjall/test_fixture/v2_keyspace/partitions/default2/config delete mode 100644 crates/fjall/test_fixture/v2_keyspace/partitions/default2/levels delete mode 100644 crates/fjall/test_fixture/v2_keyspace/partitions/default2/manifest delete mode 100644 crates/fjall/test_fixture/v2_keyspace/partitions/default2/segments/0 delete mode 100644 crates/fjall/test_fixture/v2_keyspace/version delete mode 100644 crates/fjall/test_fixture/v2_keyspace_corrupt_journal/journals/2 delete mode 100644 crates/fjall/test_fixture/v2_keyspace_corrupt_journal/partitions/default1/config delete mode 100644 crates/fjall/test_fixture/v2_keyspace_corrupt_journal/partitions/default1/levels delete mode 100644 crates/fjall/test_fixture/v2_keyspace_corrupt_journal/partitions/default1/manifest delete mode 100644 crates/fjall/test_fixture/v2_keyspace_corrupt_journal/partitions/default1/segments/0 delete mode 100644 crates/fjall/test_fixture/v2_keyspace_corrupt_journal/partitions/default2/blobs/.vlog delete mode 100644 crates/fjall/test_fixture/v2_keyspace_corrupt_journal/partitions/default2/blobs/segments/0 delete mode 100644 crates/fjall/test_fixture/v2_keyspace_corrupt_journal/partitions/default2/blobs/vlog_manifest delete mode 100644 crates/fjall/test_fixture/v2_keyspace_corrupt_journal/partitions/default2/config delete mode 100644 crates/fjall/test_fixture/v2_keyspace_corrupt_journal/partitions/default2/levels delete mode 100644 crates/fjall/test_fixture/v2_keyspace_corrupt_journal/partitions/default2/manifest delete mode 100644 crates/fjall/test_fixture/v2_keyspace_corrupt_journal/partitions/default2/segments/0 delete mode 100644 crates/fjall/test_fixture/v2_keyspace_corrupt_journal/version delete mode 100644 crates/fjall/test_fixture/v2_sealed_journal_shenanigans/journals/0.sealed delete mode 100644 crates/fjall/test_fixture/v2_sealed_journal_shenanigans/journals/1.sealed delete mode 100644 crates/fjall/test_fixture/v2_sealed_journal_shenanigans/journals/2 delete mode 100644 crates/fjall/test_fixture/v2_sealed_journal_shenanigans/journals/3 delete mode 100644 crates/fjall/test_fixture/v2_sealed_journal_shenanigans/partitions/default/config delete mode 100644 crates/fjall/test_fixture/v2_sealed_journal_shenanigans/partitions/default/levels delete mode 100644 crates/fjall/test_fixture/v2_sealed_journal_shenanigans/partitions/default/manifest delete mode 100644 crates/fjall/test_fixture/v2_sealed_journal_shenanigans/version delete mode 100644 crates/fjall/tests/blob_kv_simple.rs delete mode 100644 crates/fjall/tests/clear_dirty_read.rs delete mode 100644 crates/fjall/tests/fifo_dirty_read.rs rename crates/fjall/tests/{format_v4.rs => format_v5.rs} (82%) delete mode 100644 crates/fjall/tests/keyspace_v1_load_fixture.rs delete mode 100644 crates/fjall/tests/keyspace_v2_load_fixture.rs delete mode 100644 crates/fjall/tests/prefix_complex.rs delete mode 100644 crates/fjall/tests/tx_ryow.rs delete mode 100644 crates/fjall/tests/tx_ryow_snapshot.rs delete mode 100644 crates/fjall/tests/write_tx.rs delete mode 100644 crates/lsm-tree/.config/nextest.toml delete mode 100644 crates/lsm-tree/.gitignore delete mode 100644 crates/lsm-tree/.rustfmt.toml delete mode 100644 crates/lsm-tree/CONTRIBUTING.md delete mode 100644 crates/lsm-tree/README.md delete mode 100644 crates/lsm-tree/logo.png delete mode 100644 crates/lsm-tree/renovate.json delete mode 100644 crates/lsm-tree/src/any_tree.rs delete mode 100644 crates/lsm-tree/src/blob_tree/gc.rs delete mode 100644 crates/lsm-tree/src/blob_tree/handle.rs delete mode 100644 crates/lsm-tree/src/blob_tree/ingest.rs delete mode 100644 crates/lsm-tree/src/blob_tree/mod.rs delete mode 100644 crates/lsm-tree/src/compaction/drop_range.rs delete mode 100644 crates/lsm-tree/src/compaction/fifo.rs delete mode 100644 crates/lsm-tree/src/compaction/maintenance.rs delete mode 100644 crates/lsm-tree/src/compaction/movedown.rs delete mode 100644 crates/lsm-tree/src/compaction/tiered.rs delete mode 100644 crates/lsm-tree/src/ingestion.rs delete mode 100644 crates/lsm-tree/src/metrics.rs delete mode 100644 crates/lsm-tree/src/slice/slice_bytes/mod.rs delete mode 100644 crates/lsm-tree/src/table/filter/blocked_bloom/builder._rs delete mode 100644 crates/lsm-tree/src/table/filter/blocked_bloom/mod._rs delete mode 100644 crates/lsm-tree/src/version/blob_file_list.rs delete mode 100644 crates/lsm-tree/src/vlog/accessor.rs delete mode 100644 crates/lsm-tree/src/vlog/blob_file/merge.rs delete mode 100644 crates/lsm-tree/src/vlog/blob_file/meta.rs delete mode 100644 crates/lsm-tree/src/vlog/blob_file/mod.rs delete mode 100644 crates/lsm-tree/src/vlog/blob_file/multi_writer.rs delete mode 100644 crates/lsm-tree/src/vlog/blob_file/reader.rs delete mode 100644 crates/lsm-tree/src/vlog/blob_file/scanner.rs delete mode 100644 crates/lsm-tree/src/vlog/blob_file/writer.rs delete mode 100644 crates/lsm-tree/src/vlog/handle.rs delete mode 100644 crates/lsm-tree/src/vlog/mod.rs delete mode 100644 crates/lsm-tree/test_fixture/v1_tree/config delete mode 100644 crates/lsm-tree/test_fixture/v1_tree/levels delete mode 100644 crates/lsm-tree/test_fixture/v1_tree/segments/0 delete mode 100644 crates/lsm-tree/test_fixture/v1_tree/version delete mode 100644 crates/lsm-tree/test_fixture/v1_tree_corrupt/config delete mode 100644 crates/lsm-tree/test_fixture/v1_tree_corrupt/levels delete mode 100644 crates/lsm-tree/test_fixture/v1_tree_corrupt/segments/0 delete mode 100644 crates/lsm-tree/test_fixture/v1_tree_corrupt/version delete mode 100644 crates/lsm-tree/tests/blob_compression.rs delete mode 100644 crates/lsm-tree/tests/blob_empty_flush.rs delete mode 100644 crates/lsm-tree/tests/blob_fifo_limit.rs delete mode 100644 crates/lsm-tree/tests/blob_file_full_file_checksum.rs delete mode 100644 crates/lsm-tree/tests/blob_flush_gc_stats.rs delete mode 100644 crates/lsm-tree/tests/blob_gc_watermark.rs delete mode 100644 crates/lsm-tree/tests/blob_ingest.rs delete mode 100644 crates/lsm-tree/tests/blob_ingest_relink.rs delete mode 100644 crates/lsm-tree/tests/blob_major_compact_drop_dead_files.rs delete mode 100644 crates/lsm-tree/tests/blob_major_compact_gc_stats.rs delete mode 100644 crates/lsm-tree/tests/blob_major_compact_relink.rs delete mode 100644 crates/lsm-tree/tests/blob_major_compact_relocation.rs delete mode 100644 crates/lsm-tree/tests/blob_major_compact_relocation_recovery.rs delete mode 100644 crates/lsm-tree/tests/blob_nuke_gc_stats.rs delete mode 100644 crates/lsm-tree/tests/blob_pinned_fd.rs delete mode 100644 crates/lsm-tree/tests/blob_recover_gc_stats.rs delete mode 100644 crates/lsm-tree/tests/blob_recovery.rs delete mode 100644 crates/lsm-tree/tests/blob_register_table_rotation.rs delete mode 100644 crates/lsm-tree/tests/blob_sep_threshold.rs delete mode 100644 crates/lsm-tree/tests/blob_simple.rs delete mode 100644 crates/lsm-tree/tests/blob_tree_guarded_size.rs delete mode 100644 crates/lsm-tree/tests/blob_tree_reload_blob.rs delete mode 100644 crates/lsm-tree/tests/compaction_filter_gc_stats_change.rs delete mode 100644 crates/lsm-tree/tests/compaction_filter_gc_stats_destroy.rs delete mode 100644 crates/lsm-tree/tests/compaction_filter_gc_stats_remove.rs delete mode 100644 crates/lsm-tree/tests/compaction_filter_gc_stats_remove_weak.rs delete mode 100644 crates/lsm-tree/tests/dirty_snapshot.rs delete mode 100644 crates/lsm-tree/tests/fifo_ttl.rs create mode 100644 crates/lsm-tree/tests/format_v5.rs delete mode 100644 crates/lsm-tree/tests/model_2.rs delete mode 100644 crates/lsm-tree/tests/model_3.rs delete mode 100644 crates/lsm-tree/tests/model_4.rs delete mode 100644 crates/lsm-tree/tests/model_5.rs delete mode 100644 crates/lsm-tree/tests/model_6.rs delete mode 100644 crates/lsm-tree/tests/regression_286.rs delete mode 100644 crates/lsm-tree/tests/tree_drop_range.rs delete mode 100644 crates/lsm-tree/tests/tree_filter_hit_rate.rs delete mode 100644 crates/lsm-tree/tests/tree_l0_point_read.rs delete mode 100644 crates/lsm-tree/tests/tree_l0_range.rs delete mode 100644 crates/lsm-tree/tests/tree_v1_load_fixture.rs diff --git a/.gitignore b/.gitignore index 5a32aa3f6..b1c0d9b7b 100644 --- a/.gitignore +++ b/.gitignore @@ -27,6 +27,7 @@ _* /*.txt /*.csv /tmp +**/.test # Logs *.log* diff --git a/Cargo.lock b/Cargo.lock index d4d318c38..ce36f0441 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -861,9 +861,11 @@ checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" [[package]] name = "byteview" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d74937895e761d5984206f82ca8ff7725d0fd8021921011921894b41ab1b9f7" +version = "0.3.6" +dependencies = [ + "serde", + "serde_json", +] [[package]] name = "cast" @@ -1462,18 +1464,6 @@ version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e5e8f6c15a24b9a3ee5efec809ccd006d3b30e8b3bb63c39af737c7f87daa1d" -[[package]] -name = "enum_dispatch" -version = "0.3.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa18ce2bc66555b3218614519ac839ddb759a7d6720732f979ef8d13be147ecd" -dependencies = [ - "once_cell", - "proc-macro2", - "quote", - "syn 2.0.119", -] - [[package]] name = "env_filter" version = "2.0.0" @@ -1567,7 +1557,6 @@ dependencies = [ "lsm-tree", "lz4_flex", "nanoid", - "rand 0.10.2", "tempfile", "test-log", "xxhash-rust", @@ -1789,12 +1778,6 @@ version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" -[[package]] -name = "hashbrown" -version = "0.16.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" - [[package]] name = "hashbrown" version = "0.17.1" @@ -2288,9 +2271,9 @@ dependencies = [ [[package]] name = "libredox" -version = "0.1.18" +version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c943259e342f1e06ff2da7a83eabdfe7f92ce10262688dbf1895ff0b3e6e4652" +checksum = "2026a5056764a10b2bf5d56488cba40da507f5493a6a429340e2004d9ed085fa" dependencies = [ "libc", ] @@ -2333,18 +2316,16 @@ name = "lsm-tree" version = "0.3.6" dependencies = [ "byteorder-lite", - "bytes", "byteview", "criterion", "crossbeam-skiplist", - "enum_dispatch", "fs_extra", "interval-heap", "log", "lz4_flex", "nanoid", "quick_cache", - "rand 0.9.5", + "rand 0.10.2", "rustc-hash", "self_cell", "sfa", @@ -2468,11 +2449,11 @@ dependencies = [ [[package]] name = "nanoid" -version = "0.4.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ffa00dec017b5b1a8b7cf5e2c008bfda1aa7e0697ac1508b491fdf2622fb4d8" +checksum = "8628de41fe064cc3f0cf07f3d299ee3e73521adaff72278731d5c8cae3797873" dependencies = [ - "rand 0.8.7", + "rand 0.9.5", ] [[package]] @@ -2624,9 +2605,9 @@ dependencies = [ [[package]] name = "pco" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "553ccdc7f6999785559af4998c79712a5ab820e26b68bad9146609c19587ec82" +checksum = "386342cad4c6e97f081568e5d910ea7d871314c843aa8fc564f2a6b64cab9456" dependencies = [ "better_io", "dtype_dispatch", @@ -2781,12 +2762,12 @@ dependencies = [ [[package]] name = "quick_cache" -version = "0.6.24" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9c6658afe513a3b484e3abfdaa0d03ef3c0bbf017542c178dd55f94eb3051f9" +checksum = "403c1a912fec895cafb223201e368234842acb9220aaf08ab042ae89ba5f135c" dependencies = [ "equivalent", - "hashbrown 0.16.1", + "hashbrown 0.17.1", ] [[package]] @@ -2826,24 +2807,13 @@ dependencies = [ "ptr_meta", ] -[[package]] -name = "rand" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22f6172bdec972074665ed81ed53b71da00bfc44b65a753cfde883ec4c702a1a" -dependencies = [ - "libc", - "rand_chacha 0.3.1", - "rand_core 0.6.4", -] - [[package]] name = "rand" version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9ef1d0d795eb7d84685bca4f72f3649f064e6641543d3a8c415898726a57b41" dependencies = [ - "rand_chacha 0.9.0", + "rand_chacha", "rand_core 0.9.5", ] @@ -2858,16 +2828,6 @@ dependencies = [ "rand_core 0.10.1", ] -[[package]] -name = "rand_chacha" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", - "rand_core 0.6.4", -] - [[package]] name = "rand_chacha" version = "0.9.0" @@ -2883,9 +2843,6 @@ name = "rand_core" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" -dependencies = [ - "getrandom 0.2.17", -] [[package]] name = "rand_core" @@ -4047,7 +4004,7 @@ name = "vecdb_derive" version = "0.3.6" dependencies = [ "quote", - "syn 2.0.119", + "syn 3.0.3", "tempfile", "vecdb", ] diff --git a/Cargo.toml b/Cargo.toml index 0ca1fcbf0..9c186a460 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -59,7 +59,7 @@ brk_traversable = { version = "0.3.6", path = "crates/brk_traversable", features brk_traversable_derive = { version = "0.3.6", path = "crates/brk_traversable_derive" } brk_types = { version = "0.3.6", path = "crates/brk_types" } brk_website = { version = "0.3.6", path = "crates/brk_website" } -byteview = "0.10.2" +byteview = { version = "0.3.6", path = "crates/byteview" } color-eyre = "0.6.5" corepc-jsonrpc = { package = "jsonrpc", version = "0.19.0", features = ["simple_http"], default-features = false } corepc-types = { version = "0.15.0", features = ["std"], default-features = false } @@ -70,10 +70,10 @@ jiff = { version = "0.2.35", features = ["perf-inline", "tz-system"], default-fe libc = "0.2" log = "0.4.33" lsm-tree = { path = "crates/lsm-tree", version = "0.3.6", default-features = false } -lz4_flex = { version = "0.13.1", default-features = false } +lz4_flex = { version = "=0.13.1", default-features = false } owo-colors = "4.3.0" parking_lot = "0.12.5" -pco = "1.0.2" +pco = "1.0.3" quickmatch = { path = "crates/quickmatch", version = "0.3.6" } rawdb = { path = "crates/rawdb", version = "0.3.6" } rayon = "1.12.0" diff --git a/crates/brk_store/src/lib.rs b/crates/brk_store/src/lib.rs index f9b4a49cc..a52413687 100644 --- a/crates/brk_store/src/lib.rs +++ b/crates/brk_store/src/lib.rs @@ -299,14 +299,14 @@ where let mut ingestion = keyspace.start_ingestion()?; // FxHashMap/FxHashSet keep keys unique and disjoint; sorting therefore - // proves the strict ordering required by the prevalidated writer. + // proves the strict ordering required by ingestion. for item in items { match item { Item::Value { key, value } => { - ingestion.write_prevalidated(ByteView::from(key), ByteView::from(value))?; + ingestion.write(ByteView::from(key), ByteView::from(value))?; } Item::Tomb(key) => { - ingestion.write_prevalidated_weak_tombstone(ByteView::from(key))?; + ingestion.write_weak_tombstone(ByteView::from(key))?; } } } @@ -335,22 +335,19 @@ where (Some((put_key, _)), Some(del_key)) => match put_key.cmp(del_key) { Ordering::Less => { let (key, value) = puts.next().unwrap(); - ingestion.write_prevalidated(ByteView::from(key), ByteView::from(value))?; + ingestion.write(ByteView::from(key), ByteView::from(value))?; } Ordering::Greater => { - ingestion.write_prevalidated_weak_tombstone(ByteView::from( - dels.next().unwrap(), - ))?; + ingestion.write_weak_tombstone(ByteView::from(dels.next().unwrap()))?; } Ordering::Equal => unreachable!("key is both inserted and deleted"), }, (Some(_), None) => { let (key, value) = puts.next().unwrap(); - ingestion.write_prevalidated(ByteView::from(key), ByteView::from(value))?; + ingestion.write(ByteView::from(key), ByteView::from(value))?; } (None, Some(_)) => { - ingestion - .write_prevalidated_weak_tombstone(ByteView::from(dels.next().unwrap()))?; + ingestion.write_weak_tombstone(ByteView::from(dels.next().unwrap()))?; } (None, None) => break, } diff --git a/crates/byteview/Cargo.toml b/crates/byteview/Cargo.toml new file mode 100644 index 000000000..8c65e8889 --- /dev/null +++ b/crates/byteview/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "byteview" +description = "Thin, immutable zero-copy slice type" +license = "MIT OR Apache-2.0" +version.workspace = true +edition.workspace = true +readme = "README.md" +repository.workspace = true +homepage.workspace = true +categories = ["data-structures"] +keywords = ["german-string", "string-view", "byte-slice"] + +[features] +default = [] +serde = ["dep:serde"] + +[dependencies] +serde = { workspace = true, optional = true } + +[dev-dependencies] +serde_json = { workspace = true } diff --git a/crates/byteview/LICENSE-APACHE b/crates/byteview/LICENSE-APACHE new file mode 100644 index 000000000..0e5254213 --- /dev/null +++ b/crates/byteview/LICENSE-APACHE @@ -0,0 +1,176 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS diff --git a/crates/byteview/LICENSE-MIT b/crates/byteview/LICENSE-MIT new file mode 100644 index 000000000..97f5e0faa --- /dev/null +++ b/crates/byteview/LICENSE-MIT @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 fjall-rs + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/crates/byteview/README.md b/crates/byteview/README.md new file mode 100644 index 000000000..f7338dfdc --- /dev/null +++ b/crates/byteview/README.md @@ -0,0 +1,76 @@ +# byteview + +[![CI](https://github.com/fjall-rs/byteview/actions/workflows/test.yml/badge.svg)](https://github.com/fjall-rs/byteview/actions/workflows/test.yml) +[![CI](https://github.com/fjall-rs/byteview/actions/workflows/miri.yml/badge.svg)](https://github.com/fjall-rs/byteview/actions/workflows/miri.yml) +[![docs.rs](https://img.shields.io/docsrs/byteview?color=green)](https://docs.rs/byteview) +[![Crates.io](https://img.shields.io/crates/v/byteview?color=blue)](https://crates.io/crates/byteview) +![MSRV](https://img.shields.io/badge/MSRV-1.87-blue) + +An immutable byte slice that may be inlined, and can be partially cloned without heap allocation. + +Think of it as a specialized `Arc<[u8]>` that can be inlined (skip allocation for small values) and no weak count. + +![Memory layout](./byteview.png) + +`byteview` was originally designed to speed up deserialization in [`lsm-tree`](https://github.com/fjall-rs/lsm-tree), allow inlining of small values and reduce memory usage compared to Arc'd slices. +Values with a known length can be constructed 2-2.5x faster than using `Arc<[u8]>`: + +![Constructor benchmark](ctor_bench.png) + +## Sponsors + + + + + + Orbitinghail + + + +## Memory usage + +Allocating 200M "" (len=0) strings: + +| Struct | Memory Usage | +|-----------------|--------------| +| `Arc<[u8]>` | 9.6 GB | +| `tokio::Bytes` | 6.4 GB | +| `ByteView` | 4.8 GB | + +Allocating 200M "helloworld" (len=10) strings: + +| Struct | Memory Usage | +|-----------------|--------------| +| `Arc<[u8]>` | 12.8 GB | +| `tokio::Bytes` | 12.8 GB | +| `ByteView` | 4.8 GB | + +Allocating 100M "helloworldhelloworld" (len=20) strings: + +| Struct | Memory Usage | +|-----------------|--------------| +| `Arc<[u8]>` | 6.4 GB | +| `tokio::Bytes` | 6.4 GB | +| `ByteView` | 2.4 GB | + +Allocating 50M "helloworldhelloworldhelloworldhelloworld" (len=30) strings: + +| Struct | Memory Usage | +|-----------------|--------------| +| `Arc<[u8]>` | 4.0 GB | +| `tokio::Bytes` | 4.0 GB | +| `ByteView` | 3.6 GB | + +Allocating 500k `"helloworld".repeat(1000)` (len=10'000) strings: + +| Struct | Memory Usage | +|-----------------|--------------| +| `Arc<[u8]>` | 5 GB | +| `tokio::Bytes` | 5 GB | +| `ByteView` | 5 GB | + +## Run fuzz tests + +```bash +cargo +nightly fuzz run fuzz_target_1 +``` diff --git a/crates/byteview/src/builder.rs b/crates/byteview/src/builder.rs new file mode 100644 index 000000000..ddac7d7b8 --- /dev/null +++ b/crates/byteview/src/builder.rs @@ -0,0 +1,33 @@ +use crate::ByteView; + +/// A builder for a [`ByteView`] that allows mutation before freezing it. +pub struct Builder(ByteView); + +impl Builder { + /// Creates a new builder. + #[must_use] + pub const fn new(inner: ByteView) -> Self { + Self(inner) + } + + /// Converts the builder into a [`ByteView`], making it immutable. + #[must_use] + pub fn freeze(mut self) -> ByteView { + self.0.update_prefix(); + self.0 + } +} + +impl std::ops::Deref for Builder { + type Target = [u8]; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl std::ops::DerefMut for Builder { + fn deref_mut(&mut self) -> &mut Self::Target { + self.0.get_mut_slice() + } +} diff --git a/crates/byteview/src/byteview.rs b/crates/byteview/src/byteview.rs new file mode 100644 index 000000000..5b4aedb3b --- /dev/null +++ b/crates/byteview/src/byteview.rs @@ -0,0 +1,1283 @@ +// Copyright (c) 2024-present, fjall-rs +// This source code is licensed under both the Apache 2.0 and MIT License +// (found in the LICENSE-* files in the repository) + +use std::{ + mem::ManuallyDrop, + ops::Deref, + sync::{ + Arc, + atomic::{AtomicU64, Ordering}, + }, +}; + +pub use crate::builder::Builder; + +#[cfg(target_pointer_width = "64")] +const INLINE_SIZE: usize = 20; + +#[cfg(target_pointer_width = "32")] +const INLINE_SIZE: usize = 16; + +const PREFIX_SIZE: usize = 4; + +#[repr(C)] +struct HeapAllocationHeader { + ref_count: AtomicU64, +} + +#[repr(C)] +struct ShortRepr { + len: u32, + data: [u8; INLINE_SIZE], +} + +#[repr(C)] +struct LongRepr { + len: u32, + prefix: [u8; PREFIX_SIZE], + heap: *const u8, + original_len: u32, + offset: u32, +} + +#[repr(C)] +pub union Trailer { + short: ManuallyDrop, + long: ManuallyDrop, +} + +impl Default for Trailer { + fn default() -> Self { + Self { + short: ManuallyDrop::new(ShortRepr { + len: 0, + data: [0; INLINE_SIZE], + }), + } + } +} + +/// An immutable byte slice +/// +/// Will be inlined (no pointer dereference or heap allocation) +/// if it is 20 characters or shorter (on a 64-bit system). +/// +/// A single heap allocation will be shared between multiple slices. +/// Even subslices of that heap allocation can be cloned without additional heap allocation. +/// +/// [`ByteView`] does not guarantee any sort of alignment for zero-copy (de)serialization. +/// +/// The design is very similar to: +/// +/// - [Polars' strings]() +/// - [CedarDB's German strings]() +/// - [Umbra's string]() +/// - [Velox' String View](https://facebookincubator.github.io/velox/develop/vectors.html) +/// - [Apache Arrow's String View](https://arrow.apache.org/docs/cpp/api/datatype.html#_CPPv4N5arrow14BinaryViewType6c_typeE) +#[repr(C)] +#[derive(Default)] +pub struct ByteView { + trailer: Trailer, +} + +#[allow(clippy::non_send_fields_in_send_ty)] +unsafe impl Send for ByteView {} +#[allow(clippy::non_send_fields_in_send_ty)] +unsafe impl Sync for ByteView {} + +impl Clone for ByteView { + fn clone(&self) -> Self { + self.slice(..) + } +} + +impl Drop for ByteView { + fn drop(&mut self) { + if self.is_inline() { + return; + } + + let heap_region = self.get_heap_region(); + + if heap_region.ref_count.fetch_sub(1, Ordering::AcqRel) != 1 { + return; + } + + unsafe { + let header_size = std::mem::size_of::(); + let alignment = std::mem::align_of::(); + let total_size = header_size + self.trailer.long.original_len as usize; + let layout = std::alloc::Layout::from_size_align(total_size, alignment).unwrap(); + + let ptr = self.trailer.long.heap.cast_mut(); + std::alloc::dealloc(ptr, layout); + } + } +} + +impl Eq for ByteView {} + +impl std::cmp::PartialEq for ByteView { + fn eq(&self, other: &Self) -> bool { + unsafe { + let src_ptr = (self as *const Self).cast::(); + let other_ptr: *const u8 = (other as *const Self).cast::(); + + let a = *src_ptr.cast::(); + let b = *other_ptr.cast::(); + + if a != b { + return false; + } + } + + // NOTE: At this point we know + // both strings must have the same prefix and same length + // + // If we are inlined, the other string must be inlined too, + // so checking the short slice is enough + if self.is_inline() { + self.get_short_slice() == other.get_short_slice() + } else { + self.get_long_slice() == other.get_long_slice() + } + } +} + +impl std::cmp::Ord for ByteView { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.prefix() + .cmp(other.prefix()) + .then_with(|| self.deref().cmp(&**other)) + } +} + +impl std::cmp::PartialOrd for ByteView { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl std::fmt::Debug for ByteView { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{:?}", &**self) + } +} + +impl Deref for ByteView { + type Target = [u8]; + + fn deref(&self) -> &Self::Target { + if self.is_inline() { + self.get_short_slice() + } else { + self.get_long_slice() + } + } +} + +impl std::hash::Hash for ByteView { + fn hash(&self, state: &mut H) { + self.deref().hash(state); + } +} + +/// RAII guard for [`ByteView::get_mut`], so the prefix gets +/// updated properly when the mutation is done +pub struct Mutator<'a>(pub(crate) &'a mut ByteView); + +impl std::ops::Deref for Mutator<'_> { + type Target = [u8]; + + fn deref(&self) -> &Self::Target { + self.0 + } +} + +impl std::ops::DerefMut for Mutator<'_> { + fn deref_mut(&mut self) -> &mut Self::Target { + self.0.get_mut_slice() + } +} + +impl Drop for Mutator<'_> { + fn drop(&mut self) { + self.0.update_prefix(); + } +} + +impl ByteView { + #[doc(hidden)] + #[must_use] + pub unsafe fn builder_unzeroed(len: usize) -> Builder { + // SAFETY: The caller is responsible for initializing every byte before + // the returned builder is frozen. + unsafe { Builder::new(Self::with_size_unzeroed(len)) } + } + + #[doc(hidden)] + #[must_use] + pub fn builder(len: usize) -> Builder { + Builder::new(Self::with_size(len)) + } + + fn prefix(&self) -> &[u8] { + let len = PREFIX_SIZE.min(self.len()); + + // SAFETY: Both trailer layouts have the prefix stored at the same position + unsafe { self.trailer.short.data.get_unchecked(..len) } + } + + fn is_inline(&self) -> bool { + self.len() <= INLINE_SIZE + } + + pub(crate) fn update_prefix(&mut self) { + if !self.is_inline() { + unsafe { + let slice_ptr: &[u8] = &*self; + let slice_ptr = slice_ptr.as_ptr(); + + // Zero out prefix + (*self.trailer.long).prefix[0] = 0; + (*self.trailer.long).prefix[1] = 0; + (*self.trailer.long).prefix[2] = 0; + (*self.trailer.long).prefix[3] = 0; + + let prefix = (*self.trailer.long).prefix.as_mut_ptr(); + std::ptr::copy_nonoverlapping(slice_ptr, prefix, self.len().min(4)); + } + } + } + + /// Returns a mutable reference into the given byteview, if there are no other pointers to the same allocation. + pub fn get_mut(&mut self) -> Option> { + if self.ref_count() == 1 { + Some(Mutator(self)) + } else { + None + } + } + + /// Creates a byteview and populates it with `len` bytes + /// from the given reader. + /// + /// # Errors + /// + /// Returns an error if an I/O error occurred. + pub fn from_reader(reader: &mut R, len: usize) -> std::io::Result { + // NOTE: We can use _unzeroed to skip zeroing of the heap allocated slice + // because we receive the `len` parameter + // If the reader does not give us exactly `len` bytes, `read_exact` fails anyway + let mut s = unsafe { Self::with_size_unzeroed(len) }; + { + let mut builder = Mutator(&mut s); + reader.read_exact(&mut builder)?; + } + Ok(s) + } + + /// Fuses two byte slices into a single byteview. + #[must_use] + pub fn fused(left: &[u8], right: &[u8]) -> Self { + let len = left.len() + right.len(); + let mut builder = unsafe { Self::builder_unzeroed(len) }; + builder[0..left.len()].copy_from_slice(left); + builder[left.len()..].copy_from_slice(right); + builder.freeze() + } + + /// Creates a new zeroed, fixed-length byteview. + /// + /// Use [`ByteView::get_mut`] to mutate the content. + /// + /// # Panics + /// + /// Panics if the length does not fit in a u32 (4 GiB). + #[must_use] + pub fn with_size(slice_len: usize) -> Self { + Self::with_size_zeroed(slice_len) + } + + /// Creates a new zeroed, fixed-length byteview. + /// + /// # Panics + /// + /// Panics if the length does not fit in a u32 (4 GiB). + fn with_size_zeroed(slice_len: usize) -> Self { + let view = if slice_len <= INLINE_SIZE { + Self { + trailer: Trailer { + short: ManuallyDrop::new(ShortRepr { + // SAFETY: We know slice_len is INLINE_SIZE or less, so it must be + // a valid u32 + #[allow(clippy::cast_possible_truncation)] + len: slice_len as u32, + data: [0; INLINE_SIZE], + }), + }, + } + } else { + let Ok(len) = u32::try_from(slice_len) else { + panic!("byte slice too long"); + }; + + unsafe { + const HEADER_SIZE: usize = std::mem::size_of::(); + const ALIGNMENT: usize = std::mem::align_of::(); + + let total_size = HEADER_SIZE + slice_len; + let layout = std::alloc::Layout::from_size_align(total_size, ALIGNMENT).unwrap(); + + // IMPORTANT: Zero-allocate the region + let heap_ptr = std::alloc::alloc_zeroed(layout); + if heap_ptr.is_null() { + std::alloc::handle_alloc_error(layout); + } + + // Set ref count + let heap_region = heap_ptr as *const HeapAllocationHeader; + let heap_region = &*heap_region; + heap_region.ref_count.store(1, Ordering::Release); + + Self { + trailer: Trailer { + long: ManuallyDrop::new(LongRepr { + len, + prefix: [0; PREFIX_SIZE], + heap: heap_ptr, + original_len: len, + offset: 0, + }), + }, + } + } + }; + + debug_assert_eq!(1, view.ref_count()); + + view + } + + /// Creates a new fixed-length byteview, **with uninitialized contents**. + /// + /// # Panics + /// + /// Panics if the length does not fit in a u32 (4 GiB). + #[doc(hidden)] + #[must_use] + pub unsafe fn with_size_unzeroed(slice_len: usize) -> Self { + let view = if slice_len <= INLINE_SIZE { + Self { + trailer: Trailer { + short: ManuallyDrop::new(ShortRepr { + // SAFETY: We know slice_len is INLINE_SIZE or less, so it must be + // a valid u32 + #[allow(clippy::cast_possible_truncation)] + len: slice_len as u32, + data: [0; INLINE_SIZE], + }), + }, + } + } else { + let Ok(len) = u32::try_from(slice_len) else { + panic!("byte slice too long"); + }; + + unsafe { + const HEADER_SIZE: usize = std::mem::size_of::(); + const ALIGNMENT: usize = std::mem::align_of::(); + + let total_size = HEADER_SIZE + slice_len; + let layout = std::alloc::Layout::from_size_align(total_size, ALIGNMENT).unwrap(); + + let heap_ptr = std::alloc::alloc(layout); + if heap_ptr.is_null() { + std::alloc::handle_alloc_error(layout); + } + + // Set ref count + let heap_region = heap_ptr as *const HeapAllocationHeader; + let heap_region = &*heap_region; + heap_region.ref_count.store(1, Ordering::Release); + + Self { + trailer: Trailer { + long: ManuallyDrop::new(LongRepr { + len, + prefix: [0; PREFIX_SIZE], + heap: heap_ptr, + original_len: len, + offset: 0, + }), + }, + } + } + }; + + debug_assert_eq!(1, view.ref_count()); + + view + } + + /// Creates a new byteview from an existing byte slice. + /// + /// Will heap-allocate the slice if it has at least length 21. + /// + /// # Panics + /// + /// Panics if the length does not fit in a u32 (4 GiB). + #[must_use] + pub fn new(slice: &[u8]) -> Self { + let slice_len = slice.len(); + + let mut view = unsafe { Self::with_size_unzeroed(slice_len) }; + + if view.is_inline() { + // SAFETY: We check for inlinability + // so we know the the input slice fits our buffer + unsafe { + let data_ptr = std::ptr::addr_of_mut!((*view.trailer.short).data).cast(); + std::ptr::copy_nonoverlapping(slice.as_ptr(), data_ptr, slice_len); + } + } else { + let long_repr = unsafe { &mut *view.trailer.long }; + + // Copy prefix + // SAFETY: We know that there are at least 4 bytes in the input slice + #[allow(clippy::indexing_slicing)] + long_repr.prefix.copy_from_slice(&slice[0..PREFIX_SIZE]); + + // Copy byte slice into heap allocation + view.get_mut_slice().copy_from_slice(slice); + } + + debug_assert_eq!(1, view.ref_count()); + + view + } + + unsafe fn data_ptr(&self) -> *const u8 { + const HEADER_SIZE: usize = std::mem::size_of::(); + + debug_assert!(!self.is_inline()); + + // SAFETY: The non-inline representation is active, and its allocation + // contains the header followed by `original_len` data bytes. + unsafe { + self.trailer + .long + .heap + .add(HEADER_SIZE) + .add(self.trailer.long.offset as usize) + } + } + + unsafe fn data_ptr_mut(&mut self) -> *mut u8 { + const HEADER_SIZE: usize = std::mem::size_of::(); + + debug_assert!(!self.is_inline()); + + // SAFETY: The non-inline representation is active, and its allocation + // contains the header followed by `original_len` data bytes. + unsafe { + self.trailer + .long + .heap + .add(HEADER_SIZE) + .add(self.trailer.long.offset as usize) + .cast_mut() + } + } + + fn get_heap_region(&self) -> &HeapAllocationHeader { + debug_assert!( + !self.is_inline(), + "inline slice does not have a heap allocation" + ); + + unsafe { + let ptr = self.trailer.long.heap; + let heap_region: *const HeapAllocationHeader = ptr.cast::(); + &*heap_region + } + } + + /// Returns the ref_count of the underlying heap allocation. + #[doc(hidden)] + #[must_use] + pub fn ref_count(&self) -> u64 { + if self.is_inline() { + 1 + } else { + self.get_heap_region().ref_count.load(Ordering::Acquire) + } + } + + /// Clones the contents of this slice into an independently tracked slice. + #[must_use] + pub fn to_detached(&self) -> Self { + Self::new(self) + } + + /// Clones the given range of the existing byteview without heap allocation. + /// + /// # Examples + /// + /// ``` + /// # use byteview::ByteView; + /// let slice = ByteView::from("helloworld_thisisalongstring"); + /// let copy = slice.slice(11..); + /// assert_eq!(b"thisisalongstring", &*copy); + /// ``` + /// + /// # Panics + /// + /// Panics if the slice is out of bounds. + #[must_use] + pub fn slice(&self, range: impl std::ops::RangeBounds) -> Self { + use core::ops::Bound; + + // Credits: This is essentially taken from + // https://github.com/tokio-rs/bytes/blob/291df5acc94b82a48765e67eeb1c1a2074539e68/src/bytes.rs#L264 + + let self_len = self.len(); + + let begin = match range.start_bound() { + Bound::Included(&n) => n, + Bound::Excluded(&n) => n.checked_add(1).expect("out of range"), + Bound::Unbounded => 0, + }; + + let end = match range.end_bound() { + Bound::Included(&n) => n.checked_add(1).expect("out of range"), + Bound::Excluded(&n) => n, + Bound::Unbounded => self_len, + }; + + assert!( + begin <= end, + "range start must not be greater than end: {begin:?} <= {end:?}", + ); + assert!( + end <= self_len, + "range end out of bounds: {end:?} <= {self_len:?}", + ); + + let new_len = end - begin; + let len = u32::try_from(new_len).unwrap(); + + // Target and destination slices are inlined + // so we just need to memcpy the struct, and replace + // the inline slice with the requested range + if new_len <= INLINE_SIZE { + let mut child = Self { + trailer: Trailer { + short: ManuallyDrop::new(ShortRepr { + len, + data: [0; INLINE_SIZE], + }), + }, + }; + + let slice = &self[begin..end]; + debug_assert_eq!(slice.len(), new_len); + + let data_ptr = unsafe { &mut (*child.trailer.short).data }; + + unsafe { + std::ptr::copy_nonoverlapping(slice.as_ptr(), data_ptr.as_mut_ptr(), new_len); + } + + child + } else { + // IMPORTANT: Increase ref count + let heap_region = self.get_heap_region(); + heap_region.ref_count.fetch_add(1, Ordering::Release); + + let mut child = Self { + // SAFETY: self.data must be defined + // we cannot get a range larger than our own slice + // so we cannot be inlined while the requested slice is not inlinable + trailer: Trailer { + long: ManuallyDrop::new(LongRepr { + len, + prefix: [0; PREFIX_SIZE], + heap: unsafe { self.trailer.long.heap }, + offset: unsafe { self.trailer.long.offset } + begin as u32, + original_len: unsafe { self.trailer.long.original_len }, + }), + }, + }; + + let prefix = &self[begin..(begin + 4)]; + debug_assert_eq!(prefix.len(), 4); + + unsafe { + (*child.trailer.long).prefix.copy_from_slice(prefix); + } + + child + } + } + + /// Returns `true` if `needle` is a prefix of the slice or equal to the slice. + pub fn starts_with>(&self, needle: T) -> bool { + let needle = needle.as_ref(); + + unsafe { + let len = PREFIX_SIZE.min(needle.len()); + let needle_prefix: &[u8] = needle.get_unchecked(..len); + + if !self.prefix().starts_with(needle_prefix) { + return false; + } + } + + self.deref().starts_with(needle) + } + + /// Returns `true` if the slice is empty. + #[must_use] + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + /// Returns the amount of bytes in the slice. + #[must_use] + pub fn len(&self) -> usize { + unsafe { self.trailer.short.len as usize } + } + + pub(crate) fn get_mut_slice(&mut self) -> &mut [u8] { + let len = self.len(); + + if self.is_inline() { + unsafe { std::slice::from_raw_parts_mut((*self.trailer.short).data.as_mut_ptr(), len) } + } else { + unsafe { std::slice::from_raw_parts_mut(self.data_ptr_mut(), len) } + } + } + + fn get_short_slice(&self) -> &[u8] { + let len = self.len(); + + debug_assert!( + len <= INLINE_SIZE, + "cannot get short slice - slice is not inlined", + ); + + // SAFETY: Shall only be called if slice is inlined + unsafe { std::slice::from_raw_parts((*self.trailer.short).data.as_ptr(), len) } + } + + fn get_long_slice(&self) -> &[u8] { + let len = self.len(); + + debug_assert!( + len > INLINE_SIZE, + "cannot get long slice - slice is inlined" + ); + + // SAFETY: Shall only be called if slice is heap allocated + unsafe { std::slice::from_raw_parts(self.data_ptr(), len) } + } +} + +impl std::borrow::Borrow<[u8]> for ByteView { + fn borrow(&self) -> &[u8] { + self + } +} + +impl AsRef<[u8]> for ByteView { + fn as_ref(&self) -> &[u8] { + self + } +} + +impl FromIterator for ByteView { + fn from_iter(iter: T) -> Self + where + T: IntoIterator, + { + Self::from(iter.into_iter().collect::>()) + } +} + +impl From<&[u8]> for ByteView { + fn from(value: &[u8]) -> Self { + Self::new(value) + } +} + +impl From> for ByteView { + fn from(value: Arc<[u8]>) -> Self { + Self::new(&value) + } +} + +impl From> for ByteView { + fn from(value: Vec) -> Self { + Self::new(&value) + } +} + +impl From<&str> for ByteView { + fn from(value: &str) -> Self { + Self::from(value.as_bytes()) + } +} + +impl From for ByteView { + fn from(value: String) -> Self { + Self::from(value.as_bytes()) + } +} + +impl From> for ByteView { + fn from(value: Arc) -> Self { + Self::from(&*value) + } +} + +impl From<[u8; N]> for ByteView { + fn from(value: [u8; N]) -> Self { + Self::from(value.as_slice()) + } +} + +impl From<&[u8; N]> for ByteView { + fn from(value: &[u8; N]) -> Self { + Self::from(value.as_slice()) + } +} + +#[cfg(feature = "serde")] +mod serde { + use super::ByteView; + use serde::de::{self, Visitor}; + use serde::{Deserialize, Deserializer, Serialize, Serializer}; + use std::fmt; + + impl Serialize for ByteView { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_bytes(self) + } + } + + impl<'de> Deserialize<'de> for ByteView { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct ByteViewVisitor; + + impl<'de> Visitor<'de> for ByteViewVisitor { + type Value = ByteView; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a byte array") + } + + fn visit_bytes(self, v: &[u8]) -> Result + where + E: de::Error, + { + Ok(ByteView::new(v)) + } + + fn visit_seq(self, seq: A) -> Result + where + A: de::SeqAccess<'de>, + { + let bytes: Vec = + Deserialize::deserialize(de::value::SeqAccessDeserializer::new(seq))?; + + Ok(ByteView::new(&bytes)) + } + } + + deserializer.deserialize_bytes(ByteViewVisitor) + } + } +} + +#[cfg(test)] +mod tests { + use super::{ByteView, HeapAllocationHeader}; + use std::io::Cursor; + + #[test] + #[cfg(target_pointer_width = "64")] + fn memsize() { + use crate::byteview::{LongRepr, ShortRepr, Trailer}; + + assert_eq!( + std::mem::size_of::(), + std::mem::size_of::() + ); + assert_eq!( + std::mem::size_of::(), + std::mem::size_of::() + ); + + assert_eq!(24, std::mem::size_of::()); + assert_eq!( + 32, + std::mem::size_of::() + std::mem::size_of::() + ); + } + + #[test] + fn sliced_clone() { + let s = ByteView::from([ + 1, 255, 255, 255, 251, 255, 255, 255, 255, 255, 1, 21, 255, 255, 255, 255, 5, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 4, 3, 255, + 255, 0, 0, 255, 0, 0, 0, 254, 2, 0, 0, 0, 5, 2, 42, 0, 0, 0, 1, 0, 0, 0, 44, 0, 0, 0, + 2, 0, 0, 0, + ]); + let slice = s.slice(12..(12 + 21)); + + #[allow(clippy::redundant_clone)] + let cloned = slice.clone(); + + assert_eq!(slice.prefix(), cloned.prefix()); + assert_eq!(slice, cloned); + } + + #[test] + fn sized_slice_ref() { + let b = b"hello"; + let _bytes = ByteView::from(b); + } + + #[test] + fn fuse_empty() { + let bytes = ByteView::fused(&[], &[]); + assert_eq!(&*bytes, &[] as &[u8]); + } + + #[test] + fn fuse_one() { + let bytes = ByteView::fused(b"abc", &[]); + assert_eq!(&*bytes, b"abc"); + } + + #[test] + fn fuse_two() { + let bytes = ByteView::fused(b"abc", b"def"); + assert_eq!(&*bytes, b"abcdef"); + } + + #[test] + fn empty_slice() { + let bytes = ByteView::with_size_zeroed(0); + assert_eq!(&*bytes, &[] as &[u8]); + } + + #[test] + fn dealloc_order() { + let bytes = ByteView::new(&(0..32).collect::>()); + let bytes_slice = bytes.slice(..31); + drop(bytes); + drop(bytes_slice); + } + + #[test] + fn dealloc_order_2() { + let bytes = ByteView::new(&(0..32).collect::>()); + let bytes_slice = bytes.slice(..31); + let bytes_slice_2 = bytes.slice(..5); + let bytes_slice_3 = bytes.slice(..6); + + drop(bytes); + drop(bytes_slice); + drop(bytes_slice_2); + drop(bytes_slice_3); + } + + #[test] + fn from_reader_1() -> std::io::Result<()> { + let str = b"abcdef"; + let mut cursor = Cursor::new(str); + + let a = ByteView::from_reader(&mut cursor, 6)?; + assert!(&*a == b"abcdef"); + + Ok(()) + } + + #[test] + fn cmp_misc_1() { + let a = ByteView::from("abcdef"); + let b = ByteView::from("abcdefhelloworldhelloworld"); + assert!(a < b); + } + + #[test] + fn get_mut() { + let mut slice = ByteView::with_size(4); + assert_eq!(4, slice.len()); + assert_eq!([0, 0, 0, 0], &*slice); + + { + let mut mutator = slice.get_mut().unwrap(); + mutator[0] = 1; + mutator[1] = 2; + mutator[2] = 3; + mutator[3] = 4; + } + + assert_eq!(4, slice.len()); + assert_eq!([1, 2, 3, 4], &*slice); + assert_eq!([1, 2, 3, 4], slice.prefix()); + } + + #[test] + fn get_mut_long() { + let mut slice = ByteView::with_size(30); + assert_eq!(30, slice.len()); + assert_eq!([0; 30], &*slice); + + { + let mut mutator = slice.get_mut().unwrap(); + mutator[0] = 1; + mutator[1] = 2; + mutator[2] = 3; + mutator[3] = 4; + } + + assert_eq!(30, slice.len()); + assert_eq!( + [ + 1, 2, 3, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0 + ], + &*slice + ); + assert_eq!([1, 2, 3, 4], slice.prefix()); + } + + #[test] + fn nostr() { + let slice = ByteView::from(""); + assert_eq!(0, slice.len()); + assert_eq!(&*slice, b""); + assert_eq!(1, slice.ref_count()); + assert!(slice.is_inline()); + } + + #[test] + fn default_str() { + let slice = ByteView::default(); + assert_eq!(0, slice.len()); + assert_eq!(&*slice, b""); + assert_eq!(1, slice.ref_count()); + assert!(slice.is_inline()); + } + + #[test] + fn short_str() { + let slice = ByteView::from("abcdef"); + assert_eq!(6, slice.len()); + assert_eq!(&*slice, b"abcdef"); + assert_eq!(1, slice.ref_count()); + assert_eq!(&slice.prefix(), b"abcd"); + assert!(slice.is_inline()); + } + + #[test] + #[cfg(target_pointer_width = "64")] + fn medium_str() { + let slice = ByteView::from("abcdefabcdef"); + assert_eq!(12, slice.len()); + assert_eq!(&*slice, b"abcdefabcdef"); + assert_eq!(1, slice.ref_count()); + assert_eq!(&slice.prefix(), b"abcd"); + assert!(slice.is_inline()); + } + + #[test] + #[cfg(target_pointer_width = "64")] + fn medium_long_str() { + let slice = ByteView::from("abcdefabcdefabcdabcd"); + assert_eq!(20, slice.len()); + assert_eq!(&*slice, b"abcdefabcdefabcdabcd"); + assert_eq!(1, slice.ref_count()); + assert_eq!(&slice.prefix(), b"abcd"); + assert!(slice.is_inline()); + } + + #[test] + #[cfg(target_pointer_width = "64")] + fn medium_str_clone() { + let slice = ByteView::from("abcdefabcdefabcdefab"); + let copy = slice.clone(); + assert_eq!(slice, copy); + assert_eq!(copy.prefix(), slice.prefix()); + + assert_eq!(1, slice.ref_count()); + + drop(copy); + assert_eq!(1, slice.ref_count()); + } + + #[test] + fn long_str() { + let slice = ByteView::from("abcdefabcdefabcdefababcd"); + assert_eq!(24, slice.len()); + assert_eq!(&*slice, b"abcdefabcdefabcdefababcd"); + assert_eq!(1, slice.ref_count()); + assert_eq!(&slice.prefix(), b"abcd"); + assert!(!slice.is_inline()); + } + + #[test] + fn long_str_clone() { + let slice = ByteView::from("abcdefabcdefabcdefababcd"); + let copy = slice.clone(); + assert_eq!(slice, copy); + assert_eq!(copy.prefix(), slice.prefix()); + + assert_eq!(2, slice.ref_count()); + + drop(copy); + assert_eq!(1, slice.ref_count()); + } + + #[test] + fn long_str_slice_full() { + let slice = ByteView::from("helloworld_thisisalongstring"); + + let copy = slice.slice(..); + assert_eq!(copy, slice); + + assert_eq!(2, slice.ref_count()); + + drop(copy); + assert_eq!(1, slice.ref_count()); + } + + #[test] + #[cfg(target_pointer_width = "64")] + fn long_str_slice() { + let slice = ByteView::from("helloworld_thisisalongstring"); + + let copy = slice.slice(11..); + assert_eq!(b"thisisalongstring", &*copy); + assert_eq!(©.prefix(), b"this"); + + assert_eq!(1, slice.ref_count()); + + drop(copy); + assert_eq!(1, slice.ref_count()); + } + + #[test] + #[cfg(target_pointer_width = "64")] + fn long_str_slice_twice() { + let slice = ByteView::from("helloworld_thisisalongstring"); + + let copy = slice.slice(11..); + assert_eq!(b"thisisalongstring", &*copy); + + let copycopy = copy.slice(..); + assert_eq!(copy, copycopy); + + assert_eq!(1, slice.ref_count()); + + drop(copy); + assert_eq!(1, slice.ref_count()); + + drop(slice); + assert_eq!(1, copycopy.ref_count()); + } + + #[test] + #[cfg(target_pointer_width = "64")] + fn long_str_slice_downgrade() { + let slice = ByteView::from("helloworld_thisisalongstring"); + + let copy = slice.slice(11..); + assert_eq!(b"thisisalongstring", &*copy); + + let copycopy = copy.slice(0..4); + assert_eq!(b"this", &*copycopy); + + { + let copycopy = copy.slice(0..=4); + assert_eq!(b"thisi", &*copycopy); + assert_eq!(b't', *copycopy.first().unwrap()); + } + + assert_eq!(1, slice.ref_count()); + + drop(copy); + assert_eq!(1, slice.ref_count()); + + drop(copycopy); + assert_eq!(1, slice.ref_count()); + } + + #[test] + fn short_str_clone() { + let slice = ByteView::from("abcdef"); + let copy = slice.clone(); + assert_eq!(slice, copy); + + assert_eq!(1, slice.ref_count()); + + drop(slice); + assert_eq!(&*copy, b"abcdef"); + + assert_eq!(1, copy.ref_count()); + } + + #[test] + fn short_str_slice_full() { + let slice = ByteView::from("abcdef"); + let copy = slice.slice(..); + assert_eq!(slice, copy); + + assert_eq!(1, slice.ref_count()); + + drop(slice); + assert_eq!(&*copy, b"abcdef"); + + assert_eq!(1, copy.ref_count()); + } + + #[test] + fn short_str_slice_part() { + let slice = ByteView::from("abcdef"); + let copy = slice.slice(3..); + + assert_eq!(1, slice.ref_count()); + + drop(slice); + assert_eq!(&*copy, b"def"); + + assert_eq!(1, copy.ref_count()); + } + + #[test] + fn short_str_slice_empty() { + let slice = ByteView::from("abcdef"); + let copy = slice.slice(0..0); + + assert_eq!(1, slice.ref_count()); + + drop(slice); + assert_eq!(&*copy, b""); + + assert_eq!(1, copy.ref_count()); + } + + #[test] + fn tiny_str_starts_with() { + let a = ByteView::from("abc"); + assert!(a.starts_with(b"ab")); + assert!(!a.starts_with(b"b")); + } + + #[test] + fn long_str_starts_with() { + let a = ByteView::from("abcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdef"); + assert!(a.starts_with(b"abcdef")); + assert!(!a.starts_with(b"def")); + } + + #[test] + fn tiny_str_cmp() { + let a = ByteView::from("abc"); + let b = ByteView::from("def"); + assert!(a < b); + } + + #[test] + fn tiny_str_eq() { + let a = ByteView::from("abc"); + let b = ByteView::from("def"); + assert!(a != b); + } + + #[test] + fn long_str_eq() { + let a = ByteView::from("abcdefabcdefabcdefabcdef"); + let b = ByteView::from("xycdefabcdefabcdefabcdef"); + assert!(a != b); + } + + #[test] + fn long_str_cmp() { + let a = ByteView::from("abcdefabcdefabcdefabcdef"); + let b = ByteView::from("xycdefabcdefabcdefabcdef"); + assert!(a < b); + } + + #[test] + fn long_str_eq_2() { + let a = ByteView::from("abcdefabcdefabcdefabcdef"); + let b = ByteView::from("abcdefabcdefabcdefabcdef"); + assert!(a == b); + } + + #[test] + fn long_str_cmp_2() { + let a = ByteView::from("abcdefabcdefabcdefabcdef"); + let b = ByteView::from("abcdefabcdefabcdefabcdeg"); + assert!(a < b); + } + + #[test] + fn long_str_cmp_3() { + let a = ByteView::from("abcdefabcdefabcdefabcde"); + let b = ByteView::from("abcdefabcdefabcdefabcdef"); + assert!(a < b); + } + + #[test] + fn cmp_fuzz_1() { + let a = ByteView::from([0]); + let b = ByteView::from([]); + + assert!(a > b); + assert!(a != b); + } + + #[test] + fn cmp_fuzz_2() { + let a = ByteView::from([0, 0]); + let b = ByteView::from([0]); + + assert!(a > b); + assert!(a != b); + } + + #[test] + fn cmp_fuzz_3() { + let a = ByteView::from([255, 255, 12, 255, 0]); + let b = ByteView::from([255, 255, 12, 255]); + + assert!(a > b); + assert!(a != b); + } + + #[test] + fn cmp_fuzz_4() { + let a = ByteView::from([ + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + ]); + let b = ByteView::from([ + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, + ]); + + assert!(a > b); + assert!(a != b); + } +} diff --git a/crates/byteview/src/lib.rs b/crates/byteview/src/lib.rs new file mode 100644 index 000000000..4e2088911 --- /dev/null +++ b/crates/byteview/src/lib.rs @@ -0,0 +1,57 @@ +// Copyright (c) 2024-present, fjall-rs +// This source code is licensed under both the Apache 2.0 and MIT License +// (found in the LICENSE-* files in the repository) + +//! An immutable byte slice that may be inlined, and can be partially cloned without heap allocation. +//! +//! The length is limited to 2^32 bytes (4 GiB). +//! +//! ``` +//! # use byteview::ByteView; +//! let slice = ByteView::from("helloworld_thisisaverylongstring"); +//! +//! // No heap allocation - increases the ref count like an Arc<[u8]> +//! let full_copy = slice.clone(); +//! drop(full_copy); +//! +//! // No heap allocation - increases the ref count like an Arc<[u8]>, but we only get a subslice +//! let copy = slice.slice(11..); +//! assert_eq!(b"thisisaverylongstring", &*copy); +//! +//! // No heap allocation - if the slice is small enough, it will be inlined into the struct... +//! let copycopy = copy.slice(0..4); +//! assert_eq!(b"this", &*copycopy); +//! +//! // ...so no ref count incrementing is done +//! assert_eq!(2, slice.ref_count()); +//! +//! drop(copy); +//! assert_eq!(1, slice.ref_count()); +//! +//! drop(copycopy); +//! assert_eq!(1, slice.ref_count()); +//! +//! // Our original slice will be automatically freed if all slices vanish +//! drop(slice); +//! ``` + +#![deny(clippy::all, missing_docs, clippy::cargo)] +#![deny(clippy::unwrap_used)] +#![deny(clippy::indexing_slicing)] +#![warn( + clippy::pedantic, + clippy::nursery, + clippy::expect_used, + clippy::unwrap_used, + clippy::indexing_slicing, + clippy::needless_lifetimes +)] + +mod builder; +mod byteview; +mod strview; + +pub use {byteview::ByteView, strview::StrView}; + +#[doc(hidden)] +pub use byteview::{Builder, Mutator}; diff --git a/crates/byteview/src/strview.rs b/crates/byteview/src/strview.rs new file mode 100644 index 000000000..d3652298a --- /dev/null +++ b/crates/byteview/src/strview.rs @@ -0,0 +1,429 @@ +// Copyright (c) 2024-present, fjall-rs +// This source code is licensed under both the Apache 2.0 and MIT License +// (found in the LICENSE-* files in the repository) + +use crate::ByteView; +use std::{ops::Deref, sync::Arc}; + +/// An immutable, UTF-8–encoded string slice +/// +/// Will be inlined (no pointer dereference or heap allocation) +/// if it is 20 characters or shorter (on a 64-bit system). +/// +/// A single heap allocation will be shared between multiple strings. +/// Even substrings of that heap allocation can be cloned without additional heap allocation. +/// +/// Uses [`ByteView`] internally, but derefs as [`&str`]. +#[repr(C)] +#[derive(Default, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct StrView(ByteView); + +impl std::fmt::Display for StrView { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", &**self) + } +} + +impl std::fmt::Debug for StrView { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{:?}", &**self) + } +} + +impl Deref for StrView { + type Target = str; + + fn deref(&self) -> &Self::Target { + // SAFETY: Constructor takes a &str + unsafe { std::str::from_utf8_unchecked(&self.0) } + } +} + +impl std::hash::Hash for StrView { + fn hash(&self, state: &mut H) { + self.deref().hash(state); + } +} + +impl StrView { + /// Creates a new string from an existing byte string. + /// + /// Will heap-allocate the string if it has at least length 13. + /// + /// # Panics + /// + /// Panics if the length does not fit in a u32 (4 GiB). + #[must_use] + pub fn new(s: &str) -> Self { + Self(ByteView::new(s.as_bytes())) + } + + #[doc(hidden)] + #[must_use] + #[allow(clippy::missing_const_for_fn)] + pub unsafe fn from_raw(view: ByteView) -> Self { + Self(view) + } + + /// Clones the contents of this string into an independently tracked string. + #[must_use] + pub fn to_detached(&self) -> Self { + Self::new(self) + } + + /// Clones the given range of the existing string without heap allocation. + #[must_use] + pub fn slice(&self, range: impl std::ops::RangeBounds) -> Self { + Self(self.0.slice(range)) + } + + /// Returns `true` if the string is empty. + #[must_use] + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } + + /// Returns the amount of bytes in the string. + #[must_use] + pub fn len(&self) -> usize { + self.0.len() + } + + /// Returns `true` if `needle` is a prefix of the string or equal to the string. + #[must_use] + pub fn starts_with(&self, needle: &str) -> bool { + self.0.starts_with(needle.as_bytes()) + } +} + +impl std::borrow::Borrow for StrView { + fn borrow(&self) -> &str { + self + } +} + +impl AsRef for StrView { + fn as_ref(&self) -> &str { + self + } +} + +impl From<&str> for StrView { + fn from(value: &str) -> Self { + Self::new(value) + } +} + +impl From for StrView { + fn from(value: String) -> Self { + Self::new(&value) + } +} + +impl From> for StrView { + fn from(value: Arc) -> Self { + Self::new(&value) + } +} + +impl TryFrom for StrView { + type Error = std::str::Utf8Error; + + fn try_from(value: ByteView) -> Result { + std::str::from_utf8(&value)?; + Ok(Self(value)) + } +} + +impl From for ByteView { + fn from(val: StrView) -> Self { + val.0 + } +} + +#[cfg(feature = "serde")] +mod serde { + use super::StrView; + use serde::de::{self, Visitor}; + use serde::{Deserialize, Deserializer, Serialize, Serializer}; + use std::fmt; + use std::ops::Deref; + + impl Serialize for StrView { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_str(self.deref()) + } + } + + impl<'de> Deserialize<'de> for StrView { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct StrViewVisitor; + + impl<'de> Visitor<'de> for StrViewVisitor { + type Value = StrView; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a string") + } + + fn visit_str(self, v: &str) -> Result + where + E: de::Error, + { + Ok(StrView::new(v)) + } + } + + deserializer.deserialize_str(StrViewVisitor) + } + } +} + +#[cfg(test)] +mod tests { + use super::StrView; + use std::collections::HashMap; + + #[cfg(feature = "serde")] + #[test] + fn serde_roundtrip() { + let a = StrView::from("abcdef"); + let b: StrView = serde_json::from_slice(&serde_json::to_vec(&a).unwrap()).unwrap(); + assert_eq!(a, b); + } + + #[test] + fn strview_hash() { + let a = StrView::from("abcdef"); + + let mut map = HashMap::new(); + map.insert(a, 0); + assert!(map.contains_key("abcdef")); + } + + #[test] + fn cmp_misc_1() { + let a = StrView::from("abcdef"); + let b = StrView::from("abcdefhelloworldhelloworld"); + assert!(a < b); + } + + #[test] + fn nostr() { + let slice = StrView::from(""); + assert_eq!(0, slice.len()); + assert_eq!(&*slice, ""); + } + + #[test] + fn default_str() { + let slice = StrView::default(); + assert_eq!(0, slice.len()); + assert_eq!(&*slice, ""); + } + + #[test] + fn short_str() { + let slice = StrView::from("abcdef"); + assert_eq!(6, slice.len()); + assert_eq!(&*slice, "abcdef"); + } + + #[test] + #[cfg(target_pointer_width = "64")] + fn medium_str() { + let slice = StrView::from("abcdefabcdef"); + assert_eq!(12, slice.len()); + assert_eq!(&*slice, "abcdefabcdef"); + } + + #[test] + #[cfg(target_pointer_width = "64")] + fn medium_long_str() { + let slice = StrView::from("abcdefabcdefabcdabcd"); + assert_eq!(20, slice.len()); + assert_eq!(&*slice, "abcdefabcdefabcdabcd"); + } + + #[test] + #[cfg(target_pointer_width = "64")] + fn medium_str_clone() { + let slice = StrView::from("abcdefabcdefabcdefa"); + + #[allow(clippy::redundant_clone)] + let copy = slice.clone(); + + assert_eq!(slice, copy); + } + + #[test] + fn long_str() { + let slice = StrView::from("abcdefabcdefabcdefababcd"); + assert_eq!(24, slice.len()); + assert_eq!(&*slice, "abcdefabcdefabcdefababcd"); + } + + #[test] + fn long_str_clone() { + let slice = StrView::from("abcdefabcdefabcdefababcd"); + + #[allow(clippy::redundant_clone)] + let copy = slice.clone(); + + assert_eq!(slice, copy); + } + + #[test] + fn long_str_slice_full() { + let slice = StrView::from("helloworld_thisisalongstring"); + + let copy = slice.slice(..); + assert_eq!(copy, slice); + } + + #[test] + #[cfg(target_pointer_width = "64")] + fn long_str_slice() { + let slice = StrView::from("helloworld_thisisalongstring"); + + let copy = slice.slice(11..); + assert_eq!("thisisalongstring", &*copy); + } + + #[test] + #[cfg(target_pointer_width = "64")] + fn long_str_slice_twice() { + let slice = StrView::from("helloworld_thisisalongstring"); + + let copy = slice.slice(11..); + assert_eq!("thisisalongstring", &*copy); + + let copycopy = copy.slice(..); + assert_eq!(copy, copycopy); + } + + #[test] + #[cfg(target_pointer_width = "64")] + fn long_str_slice_downgrade() { + let slice = StrView::from("helloworld_thisisalongstring"); + + let copy = slice.slice(11..); + assert_eq!("thisisalongstring", &*copy); + + let copycopy = copy.slice(0..4); + assert_eq!("this", &*copycopy); + + { + let copycopy = copy.slice(0..=4); + assert_eq!("thisi", &*copycopy); + assert_eq!('t', copycopy.chars().next().unwrap()); + } + } + + #[test] + fn short_str_clone() { + let slice = StrView::from("abcdef"); + let copy = slice.clone(); + assert_eq!(slice, copy); + + drop(slice); + assert_eq!(&*copy, "abcdef"); + } + + #[test] + fn short_str_slice_full() { + let slice = StrView::from("abcdef"); + let copy = slice.slice(..); + assert_eq!(slice, copy); + + drop(slice); + assert_eq!(&*copy, "abcdef"); + } + + #[test] + fn short_str_slice_part() { + let slice = StrView::from("abcdef"); + let copy = slice.slice(3..); + + drop(slice); + assert_eq!(&*copy, "def"); + } + + #[test] + fn short_str_slice_empty() { + let slice = StrView::from("abcdef"); + let copy = slice.slice(0..0); + + drop(slice); + assert_eq!(&*copy, ""); + } + + #[test] + fn tiny_str_starts_with() { + let a = StrView::from("abc"); + assert!(a.starts_with("ab")); + assert!(!a.starts_with("b")); + } + + #[test] + fn long_str_starts_with() { + let a = StrView::from("abcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdef"); + assert!(a.starts_with("abcdef")); + assert!(!a.starts_with("def")); + } + + #[test] + fn tiny_str_cmp() { + let a = StrView::from("abc"); + let b = StrView::from("def"); + assert!(a < b); + } + + #[test] + fn tiny_str_eq() { + let a = StrView::from("abc"); + let b = StrView::from("def"); + assert!(a != b); + } + + #[test] + fn long_str_eq() { + let a = StrView::from("abcdefabcdefabcdefabcdef"); + let b = StrView::from("xycdefabcdefabcdefabcdef"); + assert!(a != b); + } + + #[test] + fn long_str_cmp() { + let a = StrView::from("abcdefabcdefabcdefabcdef"); + let b = StrView::from("xycdefabcdefabcdefabcdef"); + assert!(a < b); + } + + #[test] + fn long_str_eq_2() { + let a = StrView::from("abcdefabcdefabcdefabcdef"); + let b = StrView::from("abcdefabcdefabcdefabcdef"); + assert!(a == b); + } + + #[test] + fn long_str_cmp_2() { + let a = StrView::from("abcdefabcdefabcdefabcdef"); + let b = StrView::from("abcdefabcdefabcdefabcdeg"); + assert!(a < b); + } + + #[test] + fn long_str_cmp_3() { + let a = StrView::from("abcdefabcdefabcdefabcde"); + let b = StrView::from("abcdefabcdefabcdefabcdef"); + assert!(a < b); + } +} diff --git a/crates/fjall/.config/nextest.toml b/crates/fjall/.config/nextest.toml deleted file mode 100644 index d7653a80a..000000000 --- a/crates/fjall/.config/nextest.toml +++ /dev/null @@ -1,2 +0,0 @@ -[profile.default] -slow-timeout = { period = "5s", terminate-after = 3 } diff --git a/crates/fjall/.gitignore b/crates/fjall/.gitignore deleted file mode 100644 index ee08ccef7..000000000 --- a/crates/fjall/.gitignore +++ /dev/null @@ -1,24 +0,0 @@ -# Generated by Cargo -# will have compiled files and executables -debug/ -target/ - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html -Cargo.lock - -# These are backup files generated by rustfmt -**/*.rs.bk - -# MSVC Windows builds of rustc generate these, which store debugging information -*.pdb - -mutants -mutants.out - -.fjall_data -.data -.test -/old_* - -.directory diff --git a/crates/fjall/.rustfmt.toml b/crates/fjall/.rustfmt.toml deleted file mode 100644 index 1c7741475..000000000 --- a/crates/fjall/.rustfmt.toml +++ /dev/null @@ -1,3 +0,0 @@ -reorder_imports = true -# group_imports = "StdExternalCrate" -# imports_granularity = "crate" diff --git a/crates/fjall/.vscode/settings.json b/crates/fjall/.vscode/settings.json deleted file mode 100644 index 875c86731..000000000 --- a/crates/fjall/.vscode/settings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "rust-analyzer.showUnlinkedFileNotification": false -} \ No newline at end of file diff --git a/crates/fjall/CHANGELOG.md b/crates/fjall/CHANGELOG.md deleted file mode 100644 index cbb553a7c..000000000 --- a/crates/fjall/CHANGELOG.md +++ /dev/null @@ -1,50 +0,0 @@ -# 3.1.0 - -- [feat] Implemented support for compaction filters (custom logic during compactions) -- [msrv] Reduced MSRV to 1.90 - -# 3.0.0 - -- [feat] Implemented new block format in `lsm-tree` -- [feat] Bookkeep LSM-tree changes (flushes, compactions) in `Version` history -- [feat] Prefix truncation inside data & index blocks -- [feat] Allow unpinning filter blocks -- [feat] Implemented partitioned filters -- [feat] Allow calling bulk ingestion on non-empty keyspaces -- [feat] Introduced level-based configuration policies for most configuration parameters -- [feat] Journal compression for large values -- [feat] Database locking using the new Rust file locking API -- [feat] Rewritten key-value separation to run during compactions, instead of dedicated GC runs -- [feat] Full file checksums to allow fast database corruption checks (in the future) -- [feat] Checksum check on block & blob reads -- [api] Make Ingestion API more flexible -- [feat] Shortening eligible sequence numbers when compacting into the last level to save disk space -- [api] Change constructor to `Database::builder` instead of `Config::new` -- [api] Changed naming of keyspace->database, and partition->keyspace -- [api] Change transaction feature flags to be separate structs, `OptimisticTxDatabase` and `SingleWriterTxDatabase` -- [api] Changed snapshot error type, fixes #156 -- [api] Unified transactions read operations and snapshots with `Readable` trait -- [api] Guard API for iterator values -- [api] Removed old garbage collection APIs -- [api] `metrics` feature flag for cache hit rates etc. (will be exposed in the future) -- [api] Change `bytes` feature flag to `bytes_1` to pin its version -- [api] Make read operations in optimistic write transactions non-mut -- [fix] Consider blob files in FIFO compaction size limit, fixes #133 -- [perf] Use a single hash per key for filters, instead of two -- [perf] Improve leveled compaction scoring -- [perf] Improve leveled compaction picking to use less hashing and heap allocations -- [perf] Use `quick-cache` for file descriptor caching -- [perf] Promote levels immediately to L6 to get rid of tombstones easily -- [perf] Rewritten maintenance task bookkeeping, and write stalling mechanisms to be less aggressive -- [perf] Allow `lsm-tree` flushes to merge multiple sealed memtables into L0, if necessary -- [perf] Skip heap allocation in blob memtable inserts -- [perf] Skip compression when rewriting compressed blob files -- [msrv] Increased MSRV to **1.91** -- [misc] Blob file descriptor caching -- [misc] Use Rust native `path::absolute`, removing `path-absolutize` dependency -- [misc] Remove `std-semaphore` dependency -- [misc] Remove `miniz` (will be replaced in the future) -- [misc] Use `byteorder-lite` as drop-in replacement for `byteorder` -- [refactor] Changed background workers to be a single thread pool -- [internal] Store keyspace configurations in a meta keyspace, instead of individual binary config files -- [internal] Use `sfa` for most file scaffolding in `lsm-tree` diff --git a/crates/fjall/CONTRIBUTING.md b/crates/fjall/CONTRIBUTING.md deleted file mode 100644 index b2bba5915..000000000 --- a/crates/fjall/CONTRIBUTING.md +++ /dev/null @@ -1,11 +0,0 @@ -# Contributing - -## License - -By contributing to this project, you agree that your contributions will be licensed under the project's license (MIT OR Apache-2.0). - -Thank you for your contribution! - -## Looking for issues? - -https://github.com/fjall-rs/fjall/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22 diff --git a/crates/fjall/Cargo.toml b/crates/fjall/Cargo.toml index 1c445600c..1c99aea48 100644 --- a/crates/fjall/Cargo.toml +++ b/crates/fjall/Cargo.toml @@ -5,11 +5,8 @@ license.workspace = true version.workspace = true edition.workspace = true readme.workspace = true -include = ["src/**/*", "LICENSE-APACHE", "LICENSE-MIT", "README.md"] repository.workspace = true homepage.workspace = true -keywords = ["database", "key-value", "lsm", "rocksdb", "leveldb"] -categories = ["data-structures", "database-implementations", "algorithms"] [lib] name = "fjall" @@ -18,9 +15,6 @@ path = "src/lib.rs" [features] default = ["lz4"] lz4 = ["lsm-tree/lz4", "dep:lz4_flex"] -bytes_1 = ["lsm-tree/bytes_1"] -metrics = ["lsm-tree/metrics"] -__internal_whitebox = [] [dependencies] byteorder = { package = "byteorder-lite", version = "0.1.0" } @@ -28,15 +22,11 @@ byteview = { workspace = true } lsm-tree = { workspace = true, default-features = false, features = [] } log = { workspace = true } tempfile = { workspace = true } -dashmap = "6.1.0" -xxhash-rust = { version = "0.8.15", features = ["xxh3"] } +dashmap = "6.2.1" +xxhash-rust = { version = "0.8.18", features = ["xxh3"] } lz4_flex = { workspace = true, features = ["checked-decode"], optional = true } flume = { version = "0.12.0", default-features = false } [dev-dependencies] -nanoid = "0.4.0" -test-log = "0.2.18" -rand = "0.10.0" - -[package.metadata.cargo-all-features] -denylist = ["__internal_whitebox"] +nanoid = "0.5.0" +test-log = "0.2.21" diff --git a/crates/fjall/README.md b/crates/fjall/README.md deleted file mode 100644 index 0dbeeaa15..000000000 --- a/crates/fjall/README.md +++ /dev/null @@ -1,207 +0,0 @@ -

- -

- -
- -

- - Discord - - - Bluesky - -

- -*Fjall* _(Nordic: "Mountain")_ is a log-structured, embeddable key-value storage engine written in Rust. -It features: - -- A thread-safe BTreeMap-like API -- 100% safe & stable Rust -- LSM-tree-based storage similar to `RocksDB` -- Range & prefix searching with forward and reverse iteration -- Multiple keyspaces (a.k.a. column families) with cross-keyspace atomic semantics -- Built-in compression (default = `LZ4`) -- Serializable transactions (optional) -- Key-value separation for large blob use cases (optional) -- Custom compaction filters to run custom logic during compactions (optional) -- Automatic background maintenance - -It is not: - -- A standalone database server -- A relational or wide-column database: it has no built-in notion of columns or query language - -## Sponsors - - - - - - Orbitinghail - - - -## Basic usage - -```bash -cargo add fjall -``` - -```rust -use fjall::{Database, KeyspaceCreateOptions, PersistMode}; - -fn main() -> fjall::Result<()> { - // A database may contain multiple keyspaces - // You should probably only use a single database for your application - let db = Database::builder(path).open()?; - // TxDatabase::builder for transactional semantics - - // Each keyspace is its own physical LSM-tree, and thus isolated from other keyspaces - let items = db.keyspace("my_items", KeyspaceCreateOptions::default)?; - - // Write some data - items.insert("a", "hello")?; - - // And retrieve it - let bytes = items.get("a")?; - - // Or remove it again - items.remove("a")?; - - // Search by prefix - for kv in items.prefix("prefix") { - // ... - } - - // Search by range - for kv in items.range("a"..="z") { - // ... - } - - // Iterators implement DoubleEndedIterator, so you can search backwards, too! - for kv in items.prefix("prefix").rev() { - // ... - } - - // Sync the journal to disk to make sure data is definitely durable - // When the database is dropped, it will try to persist with `PersistMode::SyncAll` automatically - db.persist(PersistMode::SyncAll) -} -``` - -> [!TIP] -> Like any typical key-value store, keys are stored in lexicographic order. -> If you are storing integer keys (e.g. timeseries data), you should use the big endian form to have predictable ordering. - -## Durability - -To support different kinds of workloads, Fjall is agnostic about the type of durability -your application needs. -After writing data (`insert`, `remove` or committing a write batch/transaction), you can choose to call [`Database::persist`](https://docs.rs/fjall/latest/fjall/struct.Database.html#method.persist) which takes a [`PersistMode`](https://docs.rs/fjall/latest/fjall/enum.PersistMode.html) parameter. -By default, any operation will flush to OS buffers, but **not** to disk. -This matches RocksDB's default durability. -Also, when dropped, the database will try to persist the journal *to disk* synchronously. - -## Multithreading, Async and Multiprocess - -> [!WARNING] -> A single database may **not** be loaded in parallel from separate *processes*. - -Fjall is internally synchronized for multi-*threaded* access, so you can clone around the `Database` and `Keyspace`s as needed, without needing to lock yourself. - -For an async example, see the [`tokio`](https://github.com/fjall-rs/fjall/tree/main/examples/tokio) example. - -## Memory usage - -Generally, memory for loaded data, indexes etc. is managed on a per-block basis, and capped by the block cache capacity. -Note that this also applies to returned values: When you hold a `Slice`, it keeps the backing buffer alive (which may be a block). -If you know that you are going to keep a value around for a long time, you may want to copy it out into a new `Vec`, `Box<[u8]>`, `Arc<[u8]>` or new `Slice` (using `Slice::new`). - -> [!NOTE] -> It is recommended to configure the block cache capacity to be ~20-25% of the available memory - or more **if** the data set fits _fully_ into memory. - -Additionally, orthogonally to the block cache, each `Keyspace` has its own write buffer (["Memtable"](https://docs.rs/fjall/latest/fjall/struct.KeyspaceCreateOptions.html#method.max_memtable_size)) which is the unit of data flushed back into the "proper" index structure. - -## Error handling - -Fjall returns an [error enum](https://docs.rs/fjall/latest/fjall/enum.Error.html), however these variants are mostly used for debugging and tracing purposes, so your application is not expected to handle specific errors. - -It's best to let the application crash and restart, which is the [safest way to recover from transient I/O errors](https://ramalagappan.github.io/pdfs/papers/cuttlefs.pdf). - -## Transactional modes - -The backing store (`lsm-tree`) is a MVCC key-value store, allowing repeatable snapshot reads. -However this isolation level can not do read-modify-write operations without the chance of lost updates. -Also, `WriteBatch` does not allow reading the intermediary state back as you would expect from a proper transaction. -For that reason, if you need transactional semantics, you need to use one of the transactional database implementation (`OptimisticTxDatabase` or `SingleWriterTxDatabase`). - -TL;DR: Fjall supports both transactional and non-transactional workloads. -Chances are you want to use a transactional database, unless you know your workload does not need serializable transaction semantics. - -### Single writer - -Opens a transactional database for single-writer (serialized) transactions. -Single writer means only a single **write** transaction can run at a time. -This is trivially serializable because it _literally_ serializes write transactions. - -### Optimistic - -Opens a transactional database for multi-writer, serializable transactions. -Conflict checking is done using optimistic concurrency control, meaning transactions can conflict and may have to be rerun. - -## Feature flags - -### lz4 - -Allows using `LZ4` compression, powered by [`lz4_flex`](https://github.com/PSeitz/lz4_flex). - -*Enabled by default.* - -### bytes_1 - -Uses [`bytes`](https://github.com/tokio-rs/bytes) 1.x as the underlying `Slice` type. -Otherwise, [`byteview`](https://github.com/fjall-rs/byteview) is used instead. - -*Disabled by default.* - -## Stable disk format - -Future breaking changes will result in a major version bump and a migration path. - -For the underlying LSM-tree implementation, see: . - -## Examples - -[See here](https://github.com/fjall-rs/fjall/tree/main/examples) for practical examples. - -## Contributing - -How can you help? - -- [Ask a question](https://github.com/fjall-rs/fjall/discussions/new?category=q-a) - - or join the Discord server: [https://discord.com/invite/HvYGp4NFFk](https://discord.com/invite/HvYGp4NFFk) -- [Post benchmarks and things you created](https://github.com/fjall-rs/fjall/discussions/new?category=show-and-tell) -- [Open a PR](https://github.com/fjall-rs/fjall/compare), - - [See open issues to pick up here](https://github.com/search?q=org%3Afjall-rs+label%3A%22help+wanted%22+state%3Aopen+&type=issues) -- [Open an issue](https://github.com/fjall-rs/fjall/issues/new) (bug report, weirdness) - -## License - -All source code is licensed under MIT OR Apache-2.0. - -All contributions are to be licensed as MIT OR Apache-2.0. diff --git a/crates/fjall/commit.nu b/crates/fjall/commit.nu deleted file mode 100644 index a0a3001bd..000000000 --- a/crates/fjall/commit.nu +++ /dev/null @@ -1,64 +0,0 @@ -let machines = [ - # Fly.io performance - # "fly.performance.1x", - # "fly.performance.2x", - "fly.performance.4x", - # "fly.performance.8x", - # "fly.performance.16x", - - # EC2 T2 - # "aws.ec2.t2.nano", - # "aws.ec2.t2.micro", - # "aws.ec2.t2.small", - # "aws.ec2.t2.medium", - # "aws.ec2.t2.large", - # "aws.ec2.t2.xlarge", - # "aws.ec2.t2.2xlarge", - - # EC2 T3 - # "aws.ec2.t3.nano", - # "aws.ec2.t3.micro", - # "aws.ec2.t3.small", - "aws.ec2.t3.medium", - # "aws.ec2.t3.large", - # "aws.ec2.t3.xlarge", - # "aws.ec2.t3.2xlarge", - - # EC2 T3a - # "aws.ec2.t3a.nano", - # "aws.ec2.t3a.micro", - # "aws.ec2.t3a.small", - # "aws.ec2.t3a.medium", - # "aws.ec2.t3a.large", - # "aws.ec2.t3a.xlarge", - # "aws.ec2.t3a.2xlarge", - - # EC2 T4g - # "aws.ec2.t4g.nano", - # "aws.ec2.t4g.micro", - # "aws.ec2.t4g.small", - # "aws.ec2.t4g.medium", - # "aws.ec2.t4g.large", - # "aws.ec2.t4g.xlarge", - # "aws.ec2.t4g.2xlarge", - - # EC2 M4 - # "aws.ec2.m4.large", -] - -let table = $env.TABLE_NAME -let commit = $env.COMMIT - -print $"Queuing ($commit)" - -for machine in $machines { - let q_pk = $"q#($machine)" - - print $"Adding queue item for ($machine)" - let item = { - pk: { S: $q_pk }, - sk: { S: $commit }, - version: { S: "2" }, - } - aws dynamodb put-item --table-name $table --item ($item | to json) -} diff --git a/crates/fjall/compile_examples.mjs b/crates/fjall/compile_examples.mjs deleted file mode 100644 index 5812a12bc..000000000 --- a/crates/fjall/compile_examples.mjs +++ /dev/null @@ -1,60 +0,0 @@ -import { spawn } from "node:child_process"; -import { existsSync } from "node:fs"; -import { readdir } from "node:fs/promises"; -import { resolve } from "node:path"; - -const examplesFolder = "examples"; - -for (const exampleName of await readdir(examplesFolder)) { - const folder = resolve(examplesFolder, exampleName); - - { - console.error(`Testing ${exampleName}`); - - const proc = spawn("cargo test", { - cwd: folder, - shell: true, - }); - - proc.stdout.on("data", buf => console.log(String(buf))); - proc.stderr.on("data", buf => console.error(String(buf))); - - await new Promise((resolve, _) => { - proc.on("exit", () => { - if (proc.exitCode > 0) { - console.error(`${exampleName} FAILED`); - process.exit(1); - } - else { - resolve(); - } - }) - }); - } - - if (existsSync(resolve(folder, ".run"))) { - console.error(`Running ${exampleName}`); - - const proc = spawn("cargo run", { - cwd: folder, - shell: true, - }); - - proc.stdout.on("data", buf => console.log(String(buf))); - proc.stderr.on("data", buf => console.error(String(buf))); - - await new Promise((resolve, _) => { - proc.on("exit", () => { - if (proc.exitCode > 0) { - console.error(`${exampleName} FAILED`); - process.exit(1); - } - else { - resolve(); - } - }) - }); - } - - console.error(`${exampleName} OK`); -} diff --git a/crates/fjall/kawaii.png b/crates/fjall/kawaii.png deleted file mode 100644 index 7e6152a5df4d62988b2fea905b6006d2b42980b3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 92194 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 ZVS-2RybfLo9isq4N7F!~{H9&l{{faddLsY; diff --git a/crates/fjall/logo.png b/crates/fjall/logo.png deleted file mode 100644 index 8693656da19136596b6bbae8c36b14b212e85719..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 17908 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

1O|08GE6Fs`pv57SpaL2#F#3?|)slAEGe~kiT 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 MD`+4pz>% diff --git a/crates/fjall/renovate.json b/crates/fjall/renovate.json deleted file mode 100644 index 39a2b6e9a..000000000 --- a/crates/fjall/renovate.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "$schema": "https://docs.renovatebot.com/renovate-schema.json", - "extends": [ - "config:base" - ] -} diff --git a/crates/fjall/src/batch/item.rs b/crates/fjall/src/batch/item.rs index f0b64f91d..d4640295a 100644 --- a/crates/fjall/src/batch/item.rs +++ b/crates/fjall/src/batch/item.rs @@ -35,7 +35,6 @@ impl std::fmt::Debug for Item { ValueType::Value => "V", ValueType::Tombstone => "T", ValueType::WeakTombstone => "W", - ValueType::Indirection => "Vb", }, self.value ) diff --git a/crates/fjall/src/batch/mod.rs b/crates/fjall/src/batch/mod.rs index 9ee49757a..ae11723c0 100644 --- a/crates/fjall/src/batch/mod.rs +++ b/crates/fjall/src/batch/mod.rs @@ -148,7 +148,6 @@ impl WriteBatch { ValueType::Value => item.keyspace.tree.insert(item.key, item.value, batch_seqno), ValueType::Tombstone => item.keyspace.tree.remove(item.key, batch_seqno), ValueType::WeakTombstone => item.keyspace.tree.remove_weak(item.key, batch_seqno), - ValueType::Indirection => unreachable!(), }; batch_size += item_size; diff --git a/crates/fjall/src/builder.rs b/crates/fjall/src/builder.rs index 6fc7a7cfa..f47e70841 100644 --- a/crates/fjall/src/builder.rs +++ b/crates/fjall/src/builder.rs @@ -2,21 +2,19 @@ // This source code is licensed under both the Apache 2.0 and MIT License // (found in the LICENSE-* files in the repository) -use crate::{db_config::CompactionFilterAssigner, tx::single_writer::Openable, Config}; +use crate::{Config, Database, db_config::CompactionFilterAssigner}; use lsm_tree::{Cache, CompressionType, DescriptorTable}; -use std::{marker::PhantomData, path::Path, sync::Arc}; +use std::{path::Path, sync::Arc}; /// Database builder -pub struct Builder { +pub struct Builder { inner: Config, - _phantom: PhantomData, } -impl Builder { +impl Builder { pub(crate) fn new(path: &Path) -> Self { Self { inner: Config::new(path), - _phantom: PhantomData, } } @@ -31,8 +29,8 @@ impl Builder { /// # Errors /// /// Errors if an I/O error occurred, or if the database can not be opened. - pub fn open(self) -> crate::Result { - O::open(self.inner) + pub fn open(self) -> crate::Result { + Database::open(self.inner) } /// Sets the cache capacity in bytes. diff --git a/crates/fjall/src/compaction/mod.rs b/crates/fjall/src/compaction/mod.rs index 8b716bbf8..bac568c28 100644 --- a/crates/fjall/src/compaction/mod.rs +++ b/crates/fjall/src/compaction/mod.rs @@ -4,7 +4,7 @@ pub(crate) mod worker; -pub use lsm_tree::compaction::{Fifo, Leveled, Levelled}; +pub use lsm_tree::compaction::{Leveled, Levelled}; /// Compaction filter utilities pub mod filter { diff --git a/crates/fjall/src/compaction/worker.rs b/crates/fjall/src/compaction/worker.rs index 33b121cfa..92c66385a 100644 --- a/crates/fjall/src/compaction/worker.rs +++ b/crates/fjall/src/compaction/worker.rs @@ -2,7 +2,7 @@ // This source code is licensed under both the Apache 2.0 and MIT License // (found in the LICENSE-* files in the repository) -use crate::{snapshot_tracker::SnapshotTracker, stats::Stats, Keyspace}; +use crate::{Keyspace, snapshot_tracker::SnapshotTracker, stats::Stats}; use lsm_tree::AbstractTree; use std::time::Instant; @@ -23,7 +23,7 @@ pub fn run( keyspace.name, ); - let strategy = keyspace.config.compaction_strategy.clone(); + let strategy = std::sync::Arc::new(crate::compaction::Leveled::default()); stats.active_compaction_count.fetch_add(1, Relaxed); @@ -33,7 +33,7 @@ pub fn run( if let Err(e) = keyspace .tree - .compact(strategy.clone(), snapshot_tracker.get_seqno_safe_to_gc()) + .compact(strategy, snapshot_tracker.get_seqno_safe_to_gc()) { log::error!("Compaction failed: {e:?}"); stats.active_compaction_count.fetch_sub(1, Relaxed); diff --git a/crates/fjall/src/db.rs b/crates/fjall/src/db.rs index ecc1b8814..771abcdd7 100644 --- a/crates/fjall/src/db.rs +++ b/crates/fjall/src/db.rs @@ -3,12 +3,13 @@ // (found in the LICENSE-* files in the repository) use crate::{ + HashMap, Keyspace, KeyspaceCreateOptions, batch::WriteBatch, db_config::Config, - file::{fsync_directory, KEYSPACES_FOLDER, LOCK_FILE, VERSION_MARKER}, + file::{KEYSPACES_FOLDER, LOCK_FILE, VERSION_MARKER, fsync_directory}, flush::manager::FlushManager, - journal::{manager::JournalManager, writer::PersistMode, Journal}, - keyspace::{name::is_valid_keyspace_name, KeyspaceKey}, + journal::{Journal, manager::JournalManager, writer::PersistMode}, + keyspace::{KeyspaceKey, name::is_valid_keyspace_name}, locked_file::LockedFileGuard, meta_keyspace::MetaKeyspace, poison::{PoisonDart, PoisonSignal}, @@ -17,17 +18,15 @@ use crate::{ snapshot_tracker::SnapshotTracker, stats::Stats, supervisor::{Supervisor, SupervisorInner}, - tx::single_writer::Openable, version::FormatVersion, worker_pool::{WorkerMessage, WorkerPool}, write_buffer_manager::WriteBufferManager, - HashMap, Keyspace, KeyspaceCreateOptions, }; use lsm_tree::{AbstractTree, SequenceNumberCounter}; use std::{ fs::remove_dir_all, path::Path, - sync::{atomic::AtomicUsize, Arc, RwLock}, + sync::{Arc, RwLock, atomic::AtomicUsize}, }; pub type Keyspaces = HashMap; @@ -106,9 +105,6 @@ impl Drop for DatabaseInner { ); } } - - #[cfg(feature = "__internal_whitebox")] - crate::drop::decrement_drop_counter(); } } @@ -131,15 +127,6 @@ impl std::ops::Deref for Database { } } -impl Openable for Database { - fn open(config: Config) -> crate::Result - where - Self: Sized, - { - Self::open(config) - } -} - impl Database { /// Opens a cross-keyspace snapshot. /// @@ -152,7 +139,7 @@ impl Database { } /// Creates a new database builder to create or open a database at `path`. - pub fn builder(path: impl AsRef) -> crate::DatabaseBuilder { + pub fn builder(path: impl AsRef) -> crate::DatabaseBuilder { crate::DatabaseBuilder::new(path.as_ref()) } @@ -391,9 +378,6 @@ impl Database { let db = Self::create_or_recover(config)?; // db.start_background_threads()?; - #[cfg(feature = "__internal_whitebox")] - crate::drop::increment_drop_counter(); - Ok(db) } @@ -477,9 +461,6 @@ impl Database { self.meta_keyspace .create_keyspace(keyspace_id, &name, handle.clone(), keyspaces)?; - #[cfg(feature = "__internal_whitebox")] - crate::drop::increment_drop_counter(); - handle }) } @@ -549,12 +530,12 @@ impl Database { "It looks like you are trying to open a V2 database - the database needs a manual migration, a tool is available at https://github.com/fjall-rs/migrate-v2-v3." ); } - if version as u8 > 4 { + if version as u8 > 5 { log::error!( "It looks like you are trying to open a database from the future. Are you a time traveller?" ); } - if version != FormatVersion::V4 { + if version != FormatVersion::V5 { return Err(crate::Error::InvalidVersion(Some(version))); } } else { @@ -718,9 +699,6 @@ impl Database { lsm_tree::ValueType::WeakTombstone => { tree.remove_weak(item.key, batch.seqno); } - lsm_tree::ValueType::Indirection => { - unreachable!() - } } } @@ -837,7 +815,7 @@ impl Database { // NOTE: Lastly, fsync version marker, which contains the version let mut marker = std::fs::File::create_new(config.path.join(VERSION_MARKER))?; - FormatVersion::V4.write_file_header(&mut marker)?; + FormatVersion::V5.write_file_header(&mut marker)?; marker.sync_all()?; // IMPORTANT: fsync folders on Unix diff --git a/crates/fjall/src/db_test.rs b/crates/fjall/src/db_test.rs index 794111983..7429f9efb 100644 --- a/crates/fjall/src/db_test.rs +++ b/crates/fjall/src/db_test.rs @@ -1,4 +1,4 @@ -use crate::{Database, KeyspaceCreateOptions, KvSeparationOptions}; +use crate::{Database, KeyspaceCreateOptions}; use test_log::test; #[test_log::test] @@ -39,83 +39,6 @@ fn clear_recover_sealed() -> crate::Result<()> { Ok(()) } -// TODO: investigate: flaky on macOS??? -#[cfg(feature = "__internal_whitebox")] -#[test] -#[ignore = "restore"] -fn whitebox_db_drop() -> crate::Result<()> { - use crate::Database; - - { - let folder = tempfile::tempdir()?; - - assert_eq!(0, crate::drop::load_drop_counter()); - let db = Database::builder(&folder).open()?; - assert_eq!(5, crate::drop::load_drop_counter()); - - drop(db); - assert_eq!(0, crate::drop::load_drop_counter()); - } - - { - let folder = tempfile::tempdir()?; - - assert_eq!(0, crate::drop::load_drop_counter()); - let db = Database::builder(&folder).open()?; - assert_eq!(5, crate::drop::load_drop_counter()); - - let tree = db.keyspace("default", Default::default)?; - assert_eq!(6, crate::drop::load_drop_counter()); - - drop(tree); - drop(db); - assert_eq!(0, crate::drop::load_drop_counter()); - } - - { - let folder = tempfile::tempdir()?; - - assert_eq!(0, crate::drop::load_drop_counter()); - let db = Database::builder(&folder).open()?; - assert_eq!(5, crate::drop::load_drop_counter()); - - let _tree = db.keyspace("default", Default::default)?; - assert_eq!(6, crate::drop::load_drop_counter()); - - let _tree2 = db.keyspace("different", Default::default)?; - assert_eq!(7, crate::drop::load_drop_counter()); - } - - assert_eq!(0, crate::drop::load_drop_counter()); - - Ok(()) -} - -#[cfg(feature = "__internal_whitebox")] -#[test] -#[ignore = "restore"] -fn whitebox_db_drop_2() -> crate::Result<()> { - use crate::{Database, KeyspaceCreateOptions}; - - let folder = tempfile::tempdir()?; - - { - let db = Database::builder(&folder).open()?; - - let tree = db.keyspace("tree", KeyspaceCreateOptions::default)?; - let tree2 = db.keyspace("tree1", KeyspaceCreateOptions::default)?; - - tree.insert("a", "a")?; - tree2.insert("b", "b")?; - - tree.rotate_memtable_and_wait()?; - } - - assert_eq!(0, crate::drop::load_drop_counter()); - - Ok(()) -} - #[test] pub fn test_exotic_keyspace_names() -> crate::Result<()> { let folder = tempfile::tempdir()?; @@ -184,31 +107,6 @@ fn recover_sealed_order() -> crate::Result<()> { Ok(()) } -#[test] -#[expect(clippy::unwrap_used)] -fn recover_sealed_blob() -> crate::Result<()> { - let folder = tempfile::tempdir()?; - - for i in 0_u128..3 { - let db = Database::create_or_recover(Database::builder(folder.path()).into_config())?; - - let tree = db.keyspace("default", || { - KeyspaceCreateOptions::default() - .max_memtable_size(1_000) - .with_kv_separation(Some(KvSeparationOptions::default())) - })?; - - assert_eq!(i, tree.len()?.try_into().unwrap()); - - tree.insert(i.to_be_bytes(), i.to_be_bytes().repeat(1_024))?; - assert_eq!(i + 1, tree.len()?.try_into().unwrap()); - - tree.rotate_memtable_and_wait()?; - } - - Ok(()) -} - #[test] #[expect(clippy::unwrap_used)] fn recover_sealed_pair_1() -> crate::Result<()> { @@ -221,9 +119,7 @@ fn recover_sealed_pair_1() -> crate::Result<()> { KeyspaceCreateOptions::default().max_memtable_size(1_000) })?; let tree2 = db.keyspace("default2", || { - KeyspaceCreateOptions::default() - .max_memtable_size(1_000) - .with_kv_separation(Some(KvSeparationOptions::default())) + KeyspaceCreateOptions::default().max_memtable_size(1_000) })?; assert_eq!(i, tree.len()?.try_into().unwrap()); diff --git a/crates/fjall/src/drop.rs b/crates/fjall/src/drop.rs deleted file mode 100644 index d5473cf94..000000000 --- a/crates/fjall/src/drop.rs +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (c) 2024-present, fjall-rs -// This source code is licensed under both the Apache 2.0 and MIT License -// (found in the LICENSE-* files in the repository) - -use std::sync::atomic::Ordering::Relaxed; -use std::sync::{atomic::AtomicUsize, OnceLock}; - -static DROP_COUNTER: OnceLock = OnceLock::new(); - -pub fn increment_drop_counter() { - get_drop_counter().fetch_add(1, Relaxed); -} - -pub fn decrement_drop_counter() { - get_drop_counter().fetch_sub(1, Relaxed); -} - -pub fn get_drop_counter<'a>() -> &'a AtomicUsize { - DROP_COUNTER.get_or_init(AtomicUsize::default) -} - -#[must_use] -pub fn load_drop_counter() -> usize { - get_drop_counter().load(Relaxed) -} diff --git a/crates/fjall/src/error.rs b/crates/fjall/src/error.rs index b99a2c45e..39a6b1520 100644 --- a/crates/fjall/src/error.rs +++ b/crates/fjall/src/error.rs @@ -3,7 +3,7 @@ // (found in the LICENSE-* files in the repository) use crate::{ - journal::error::RecoveryError as JournalRecoveryError, version::FormatVersion, CompressionType, + CompressionType, journal::error::RecoveryError as JournalRecoveryError, version::FormatVersion, }; /// Errors that may occur in the storage engine diff --git a/crates/fjall/src/ingestion.rs b/crates/fjall/src/ingestion.rs index 6e658514a..a2b1a7bdd 100644 --- a/crates/fjall/src/ingestion.rs +++ b/crates/fjall/src/ingestion.rs @@ -2,17 +2,17 @@ // This source code is licensed under both the Apache 2.0 and MIT License // (found in the LICENSE-* files in the repository) -use crate::{worker_pool::WorkerMessage, Keyspace}; -use lsm_tree::{AnyIngestion, UserKey, UserValue}; +use crate::{Keyspace, worker_pool::WorkerMessage}; +use lsm_tree::{Ingestion as TreeIngestion, UserKey, UserValue}; pub struct Ingestion<'a> { keyspace: &'a Keyspace, - inner: AnyIngestion<'a>, + inner: TreeIngestion<'a>, } impl<'a> Ingestion<'a> { pub fn new(keyspace: &'a Keyspace) -> crate::Result { - let inner = keyspace.tree.ingestion()?; + let inner = TreeIngestion::new(&keyspace.tree)?; Ok(Self { keyspace, inner }) } @@ -20,37 +20,20 @@ impl<'a> Ingestion<'a> { &mut self, key: K, value: V, - ) -> crate::Result<()> { - self.inner.write(key, value).map_err(Into::into) - } - - #[doc(hidden)] - pub fn write_prevalidated, V: Into>( - &mut self, - key: K, - value: V, ) -> crate::Result<()> { self.inner - .write_prevalidated(key, value) + .write(key.into(), value.into()) .map_err(Into::into) } pub fn write_tombstone>(&mut self, key: K) -> crate::Result<()> { - self.inner.write_tombstone(key).map_err(Into::into) + self.inner.write_tombstone(key.into()).map_err(Into::into) } #[doc(hidden)] pub fn write_weak_tombstone>(&mut self, key: K) -> crate::Result<()> { - self.inner.write_weak_tombstone(key).map_err(Into::into) - } - - #[doc(hidden)] - pub fn write_prevalidated_weak_tombstone>( - &mut self, - key: K, - ) -> crate::Result<()> { self.inner - .write_prevalidated_weak_tombstone(key) + .write_weak_tombstone(key.into()) .map_err(Into::into) } @@ -72,7 +55,7 @@ impl<'a> Ingestion<'a> { // insert seqno=1 let _journal_lock = self.keyspace.supervisor.journal.get_writer(); - self.finish_inner() + self.finish_inner(false) } /// Finishes the ingestion without taking the global journal writer lock. @@ -81,12 +64,17 @@ impl<'a> Ingestion<'a> { /// the same database. Exclusive ingestions into independent keyspaces may /// still finish concurrently. pub fn finish_exclusive(self) -> crate::Result<()> { - self.finish_inner() + self.finish_inner(true) } - fn finish_inner(self) -> crate::Result<()> { - self.inner - .finish() + fn finish_inner(self, exclusive: bool) -> crate::Result<()> { + let result = if exclusive { + self.inner.finish_exclusive() + } else { + self.inner.finish() + }; + + result .inspect(|()| { self.keyspace .worker_messager diff --git a/crates/fjall/src/iter.rs b/crates/fjall/src/iter.rs index 7d61102b5..3312b287d 100644 --- a/crates/fjall/src/iter.rs +++ b/crates/fjall/src/iter.rs @@ -2,7 +2,7 @@ // This source code is licensed under both the Apache 2.0 and MIT License // (found in the LICENSE-* files in the repository) -use crate::{snapshot_nonce::SnapshotNonce, Guard}; +use crate::{Guard, snapshot_nonce::SnapshotNonce}; type InnerIter = Box + Send + 'static>; diff --git a/crates/fjall/src/journal/batch_reader.rs b/crates/fjall/src/journal/batch_reader.rs index 4b858d8f4..4e606ad2b 100644 --- a/crates/fjall/src/journal/batch_reader.rs +++ b/crates/fjall/src/journal/batch_reader.rs @@ -3,7 +3,7 @@ // (found in the LICENSE-* files in the repository) use super::reader::JournalReader; -use crate::{journal::entry::Entry, keyspace::InternalKeyspaceId, JournalRecoveryError}; +use crate::{JournalRecoveryError, journal::entry::Entry, keyspace::InternalKeyspaceId}; use lsm_tree::{SeqNo, UserKey, UserValue, ValueType}; use std::{fs::OpenOptions, hash::Hasher}; @@ -63,7 +63,9 @@ impl JournalBatchReader { fn on_close(&self) -> crate::Result<()> { if self.is_in_batch { - log::debug!("Invalid batch: missing terminator, but last batch, so probably incomplete, discarding to keep atomicity"); + log::debug!( + "Invalid batch: missing terminator, but last batch, so probably incomplete, discarding to keep atomicity" + ); // Discard batch self.truncate_to(self.last_valid_pos)?; @@ -125,7 +127,9 @@ impl Iterator for JournalBatchReader { self.checksum_builder = xxhash_rust::xxh3::Xxh3::new(); if got_checksum != expected_checksum { - log::error!("Invalid batch: checksum check failed, expected: {expected_checksum}, got: {got_checksum}"); + log::error!( + "Invalid batch: checksum check failed, expected: {expected_checksum}, got: {got_checksum}" + ); return Some(Err(JournalRecovery(JournalRecoveryError::ChecksumMismatch))); } diff --git a/crates/fjall/src/journal/entry.rs b/crates/fjall/src/journal/entry.rs index 4b7066c5b..ee45fe344 100644 --- a/crates/fjall/src/journal/entry.rs +++ b/crates/fjall/src/journal/entry.rs @@ -2,11 +2,11 @@ // This source code is licensed under both the Apache 2.0 and MIT License // (found in the LICENSE-* files in the repository) -use crate::{file::MAGIC_BYTES, keyspace::InternalKeyspaceId, Slice}; +use crate::{Slice, file::MAGIC_BYTES, keyspace::InternalKeyspaceId}; use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; use lsm_tree::{ - coding::{Decode, Encode}, CompressionType, SeqNo, UserKey, UserValue, ValueType, + coding::{Decode, Encode}, }; use std::io::{Read, Write}; diff --git a/crates/fjall/src/journal/manager.rs b/crates/fjall/src/journal/manager.rs index 3fad1b533..0dad2385c 100644 --- a/crates/fjall/src/journal/manager.rs +++ b/crates/fjall/src/journal/manager.rs @@ -49,17 +49,11 @@ pub struct JournalManager { impl Drop for JournalManager { fn drop(&mut self) { log::trace!("Dropping journal manager"); - - #[cfg(feature = "__internal_whitebox")] - crate::drop::decrement_drop_counter(); } } impl JournalManager { pub(crate) fn new() -> Self { - #[cfg(feature = "__internal_whitebox")] - crate::drop::increment_drop_counter(); - Self { items: Vec::with_capacity(10), disk_space_in_bytes: 0, diff --git a/crates/fjall/src/journal/mod.rs b/crates/fjall/src/journal/mod.rs index 7b656d079..7151538ef 100644 --- a/crates/fjall/src/journal/mod.rs +++ b/crates/fjall/src/journal/mod.rs @@ -18,7 +18,7 @@ use crate::file::fsync_directory; use batch_reader::JournalBatchReader; use lsm_tree::CompressionType; use reader::JournalReader; -use recovery::{recover_journals, RecoveryResult}; +use recovery::{RecoveryResult, recover_journals}; use std::{ path::{Path, PathBuf}, sync::{Mutex, MutexGuard}, @@ -53,9 +53,6 @@ impl Drop for Journal { log::error!("Flush error on drop: {e:?}"); } } - - #[cfg(feature = "__internal_whitebox")] - crate::drop::decrement_drop_counter(); } } @@ -92,9 +89,6 @@ impl Journal { // IMPORTANT: fsync folder on Unix fsync_directory(folder)?; - #[cfg(feature = "__internal_whitebox")] - crate::drop::increment_drop_counter(); - Ok(Self { writer: Mutex::new(writer), }) diff --git a/crates/fjall/src/journal/writer.rs b/crates/fjall/src/journal/writer.rs index 9cac11c52..bcff09cd6 100644 --- a/crates/fjall/src/journal/writer.rs +++ b/crates/fjall/src/journal/writer.rs @@ -2,7 +2,7 @@ // This source code is licensed under both the Apache 2.0 and MIT License // (found in the LICENSE-* files in the repository) -use super::entry::{serialize_marker_item, Entry}; +use super::entry::{Entry, serialize_marker_item}; use crate::{ batch::item::Item as BatchItem, file::fsync_directory, journal::recovery::JournalId, keyspace::InternalKeyspaceId, diff --git a/crates/fjall/src/keyspace/config/compression.rs b/crates/fjall/src/keyspace/config/compression.rs index c19149cca..3ec8d4b71 100644 --- a/crates/fjall/src/keyspace/config/compression.rs +++ b/crates/fjall/src/keyspace/config/compression.rs @@ -5,8 +5,8 @@ use crate::keyspace::config::{DecodeConfig, EncodeConfig}; use byteorder::{ReadBytesExt, WriteBytesExt}; use lsm_tree::{ - coding::{Decode, Encode}, CompressionType, + coding::{Decode, Encode}, }; impl EncodeConfig for crate::config::CompressionPolicy { diff --git a/crates/fjall/src/keyspace/mod.rs b/crates/fjall/src/keyspace/mod.rs index c2573d336..1d572d7b6 100644 --- a/crates/fjall/src/keyspace/mod.rs +++ b/crates/fjall/src/keyspace/mod.rs @@ -11,6 +11,7 @@ mod write_delay; mod test; use crate::{ + Database, Guard, Iter, file::{KEYSPACES_FOLDER, LSM_CURRENT_VERSION_MARKER}, flush::Task as FlushTask, ingestion::Ingestion, @@ -18,14 +19,13 @@ use crate::{ poison::PoisonSignal, supervisor::Supervisor, worker_pool::WorkerMessage, - Database, Guard, Iter, }; -use lsm_tree::{AbstractTree, AnyTree, SeqNo, UserKey, UserValue}; +use lsm_tree::{AbstractTree, SeqNo, Tree, UserKey, UserValue}; use options::CreateOptions; use std::{ ops::RangeBounds, path::Path, - sync::{atomic::AtomicBool, Arc, MutexGuard}, + sync::{Arc, MutexGuard, atomic::AtomicBool}, time::Duration, }; use write_delay::perform_write_stall; @@ -50,7 +50,6 @@ pub fn apply_to_base_config( .index_block_pinning_policy(our_config.index_block_pinning_policy.clone()) .data_block_hash_ratio_policy(our_config.data_block_hash_ratio_policy.clone()) .expect_point_read_hits(our_config.expect_point_read_hits) - .with_kv_separation(our_config.kv_separation_opts.clone()) .index_block_partitioning_policy(our_config.index_block_partitioning_policy.clone()) .filter_block_partitioning_policy(our_config.filter_block_partitioning_policy.clone()) .filter_policy(our_config.filter_policy.clone()) @@ -78,7 +77,7 @@ pub struct KeyspaceInner { /// LSM-tree wrapper #[doc(hidden)] - pub tree: AnyTree, + pub tree: Tree, pub(crate) supervisor: Supervisor, @@ -132,9 +131,6 @@ impl Drop for KeyspaceInner { } } } - - #[cfg(feature = "__internal_whitebox")] - crate::drop::decrement_drop_counter(); } } @@ -183,14 +179,6 @@ impl std::hash::Hash for Keyspace { } impl Keyspace { - #[inline] - fn standard_tree(&self) -> &lsm_tree::Tree { - let lsm_tree::AnyTree::Standard(tree) = &self.tree else { - panic!("standard keyspace operation used with a blob keyspace"); - }; - tree - } - #[doc(hidden)] #[must_use] pub fn id(&self) -> InternalKeyspaceId { @@ -271,14 +259,6 @@ impl Keyspace { Ok(()) } - /// Returns the number of blob bytes on disk that are not referenced. - /// - /// These will be reclaimed over time by blob garbage collection automatically. - #[must_use] - pub fn fragmented_blob_bytes(&self) -> u64 { - self.tree.stale_blob_bytes() - } - #[doc(hidden)] #[must_use] pub fn sealed_memtable_count(&self) -> usize { @@ -304,7 +284,7 @@ impl Keyspace { pub(crate) fn from_database( keyspace_id: InternalKeyspaceId, db: &Database, - tree: AnyTree, + tree: Tree, name: KeyspaceKey, config: CreateOptions, ) -> Self { @@ -362,17 +342,6 @@ impl Keyspace { }))) } - /// Returns the metrics struct of the underlying LSM-tree. - /// - /// # Note - /// - /// This function is experimental and metric names may change in future releases. - #[cfg(feature = "metrics")] - #[doc(hidden)] - pub fn metrics(&self) -> &lsm_tree::Metrics { - &**self.tree.metrics() - } - /// Returns the underlying LSM-tree's path. #[must_use] pub fn path(&self) -> &Path { @@ -432,7 +401,7 @@ impl Keyspace { ) -> impl DoubleEndedIterator> + Send + 'static { let nonce = self.supervisor.snapshot_tracker.open(); let range = ..; - self.standard_tree() + self.tree .create_range::<&[u8], _>(&range, nonce.instant, None) .map(move |item| { let _keep_snapshot_alive = &nonce; @@ -472,7 +441,7 @@ impl Keyspace { range: R, ) -> impl DoubleEndedIterator> + Send + 'static { let nonce = self.supervisor.snapshot_tracker.open(); - self.standard_tree() + self.tree .create_range(&range, nonce.instant, None) .map(move |item| { let _keep_snapshot_alive = &nonce; @@ -512,7 +481,7 @@ impl Keyspace { prefix: K, ) -> impl DoubleEndedIterator> + Send + 'static { let nonce = self.supervisor.snapshot_tracker.open(); - self.standard_tree() + self.tree .create_prefix(prefix, nonce.instant, None) .map(move |item| { let _keep_snapshot_alive = &nonce; @@ -677,7 +646,7 @@ impl Keyspace { &self, key: K, ) -> crate::Result> { - Ok(self.standard_tree().get(key, SeqNo::MAX)?) + Ok(self.tree.get(key, SeqNo::MAX)?) } /// Retrieves the size of an item from the keyspace. @@ -753,12 +722,6 @@ impl Keyspace { self.tree.last_key_value(SeqNo::MAX, None).map(Guard) } - /// Returns `true` if the underlying LSM-tree is key-value-separated. - #[must_use] - pub fn is_kv_separated(&self) -> bool { - matches!(self.tree, crate::AnyTree::Blob(_)) - } - // NOTE: Used in tests #[doc(hidden)] pub fn rotate_memtable_and_wait(&self) -> crate::Result<()> { @@ -908,13 +871,6 @@ impl Keyspace { self.tree.table_count() } - /// Number of blob files in the LSM-tree. - #[doc(hidden)] - #[must_use] - pub fn blob_file_count(&self) -> usize { - self.tree.blob_file_count() - } - /// Performs major compaction, blocking the caller until it's done. /// /// # Errors diff --git a/crates/fjall/src/keyspace/options.rs b/crates/fjall/src/keyspace/options.rs index 1b8455aa3..e0ceabc6c 100644 --- a/crates/fjall/src/keyspace/options.rs +++ b/crates/fjall/src/keyspace/options.rs @@ -8,13 +8,11 @@ use crate::{ FilterPolicyEntry, HashRatioPolicy, PartitioningPolicy, PinningPolicy, RestartIntervalPolicy, }, - keyspace::{config::DecodeConfig, InternalKeyspaceId}, - meta_keyspace::{encode_config_key, MetaKeyspace}, + keyspace::{InternalKeyspaceId, config::DecodeConfig}, + meta_keyspace::{MetaKeyspace, encode_config_key}, }; use byteorder::ReadBytesExt; -use lsm_tree::{ - compaction::Factory as CompactionFilterFactory, CompressionType, KvPair, KvSeparationOptions, -}; +use lsm_tree::{CompressionType, KvPair, compaction::Factory as CompactionFilterFactory}; use std::sync::Arc; /// Options to configure a keyspace @@ -72,12 +70,6 @@ pub struct CreateOptions { pub(crate) manual_journal_persist: bool, - #[doc(hidden)] - pub compaction_strategy: Arc, - - #[doc(hidden)] - pub kv_separation_opts: Option, - pub(crate) compaction_filter_factory: Option>, } @@ -120,12 +112,6 @@ impl Default for CreateOptions { index_block_compression_policy: CompressionPolicy::all(CompressionType::None), - compaction_strategy: Arc::new( - crate::compaction::Leveled::default() - ), - - kv_separation_opts: None, - compaction_filter_factory: None, } } @@ -153,8 +139,6 @@ impl CreateOptions { keyspace_id: InternalKeyspaceId, meta_keyspace: &MetaKeyspace, ) -> crate::Result { - let blob = meta_keyspace.get_kv_for_config(keyspace_id, "blob")?; - let data_block_compression_policy = meta_keyspace .get_kv_for_config(keyspace_id, "data_block_compression_policy")? .expect("should exist"); @@ -221,117 +205,6 @@ impl CreateOptions { .expect("should exist"); let filter_policy = FilterPolicy::decode(&filter_policy)?; - let blob_opts = blob.map(|_| { - use byteorder::LE; - use lsm_tree::coding::Decode; - - let blob_age_cutoff = meta_keyspace - .get_kv_for_config(keyspace_id, "blob_age_cutoff")? - .expect("blob_age_cutoff should be defined"); - let blob_age_cutoff = (&mut &blob_age_cutoff[..]).read_f32::()?; - - let blob_compression = meta_keyspace - .get_kv_for_config(keyspace_id, "blob_compression")? - .expect("blob_compression should be defined"); - let blob_compression = CompressionType::decode_from(&mut &blob_compression[..])?; - - let file_target_size = meta_keyspace - .get_kv_for_config(keyspace_id, "blob_file_target_size")? - .expect("blob_file_target_size should be defined"); - let file_target_size = (&mut &file_target_size[..]).read_u64::()?; - - let separation_threshold = meta_keyspace - .get_kv_for_config(keyspace_id, "blob_separation_threshold")? - .expect("blob_separation_threshold should be defined"); - let separation_threshold = (&mut &separation_threshold[..]).read_u32::()?; - - let staleness_threshold = meta_keyspace - .get_kv_for_config(keyspace_id, "blob_staleness_threshold")? - .expect("blob_staleness_threshold should be defined"); - let staleness_threshold = (&mut &staleness_threshold[..]).read_f32::()?; - - Ok::<_, crate::Error>( - KvSeparationOptions::default() - .compression(blob_compression) - .file_target_size(file_target_size) - .separation_threshold(separation_threshold) - .staleness_threshold(staleness_threshold) - .age_cutoff(blob_age_cutoff), - ) - }); - - let compaction_strategy_name = meta_keyspace - .get_kv_for_config(keyspace_id, "compaction_strategy")? - .expect("compaction_strategy should be defined"); - - let compaction_strategy_name = std::str::from_utf8(&compaction_strategy_name) - .expect("compaction_strategy should be UTF-8"); - - let compaction_strategy = match compaction_strategy_name { - lsm_tree::compaction::LEVELED_COMPACTION_NAME => { - use byteorder::LE; - - let l0_threshold = meta_keyspace - .get_kv_for_config(keyspace_id, "leveled_l0_threshold")? - .expect("leveled_l0_threshold should be defined"); - let l0_threshold = (&mut &l0_threshold[..]).read_u8()?; - - let target_size = meta_keyspace - .get_kv_for_config(keyspace_id, "leveled_target_size")? - .expect("leveled_target_size should be defined"); - let target_size = (&mut &target_size[..]).read_u64::()?; - - let level_ratio_policy_bytes = meta_keyspace - .get_kv_for_config(keyspace_id, "leveled_level_ratio_policy")? - .expect("leveled_level_ratio_policy should be defined"); - let level_ratio_policy_bytes = &mut &level_ratio_policy_bytes[..]; - - let level_ratio_policy_len = level_ratio_policy_bytes.read_u8()?; - - let mut ratios = vec![]; - - for _ in 0..level_ratio_policy_len { - ratios.push(level_ratio_policy_bytes.read_f32::()?); - } - - Arc::new( - crate::compaction::Leveled::default() - .with_l0_threshold(l0_threshold) - .with_table_target_size(target_size) - .with_level_ratio_policy(ratios), - ) as Arc - } - lsm_tree::compaction::FIFO_COMPACTION_NAME => { - use byteorder::LE; - - let fifo_limit = meta_keyspace - .get_kv_for_config(keyspace_id, "fifo_limit")? - .expect("fifo_limit should be defined"); - let fifo_limit = (&mut &fifo_limit[..]).read_u64::()?; - - let has_ttl = meta_keyspace - .get_kv_for_config(keyspace_id, "fifo_ttl")? - .expect("fifo_ttl should be defined") - == [1]; - - let ttl_seconds = if has_ttl { - let fifo_ttl_seconds = meta_keyspace - .get_kv_for_config(keyspace_id, "fifo_ttl_seconds")? - .expect("fifo_ttl_seconds should be defined"); - let fifo_ttl_seconds = (&mut &fifo_ttl_seconds[..]).read_u64::()?; - - Some(fifo_ttl_seconds) - } else { - None - }; - - Arc::new(crate::compaction::Fifo::new(fifo_limit, ttl_seconds)) - } - name => { - panic!("Invalid/unsupported compaction strategy: {name:?}"); - } - }; - let manual_journal_persist = meta_keyspace .get_kv_for_config(keyspace_id, "manual_journal_persist")? .expect("should exist") @@ -368,10 +241,6 @@ impl CreateOptions { max_memtable_size, - compaction_strategy, - - kv_separation_opts: blob_opts.transpose()?, - compaction_filter_factory: None, }) } @@ -380,11 +249,7 @@ impl CreateOptions { pub(crate) fn encode_kvs(&self, keyspace_id: InternalKeyspaceId) -> Vec { use crate::keyspace::config::EncodeConfig; - let mut kvs = vec![ - { - let key = encode_config_key(keyspace_id, "compaction_strategy"); - (key, self.compaction_strategy.get_name().into()) - }, + let kvs = vec![ policy!( keyspace_id, "data_block_compression_policy", @@ -466,68 +331,9 @@ impl CreateOptions { }, ]; - match self.compaction_strategy.get_name() { - "LeveledCompaction" | "FifoCompaction" => { - kvs.extend( - self.compaction_strategy - .get_config() - .into_iter() - .map(|(k, v)| { - // TODO: this is a bit stupid right now because we depend on behaviour in lsm-tree - // we should probably make lsm-tree just return a String for the key - let k = std::str::from_utf8(&k) - .expect("compaction strategy should return UTF-8 key"); - - (encode_config_key(keyspace_id, k), v) - }), - ); - } - name => { - panic!("Invalid/unsupported compaction strategy: {name:?}"); - } - } - - if let Some(blob_opts) = &self.kv_separation_opts { - kvs.extend([ - { - let key = encode_config_key(keyspace_id, "blob"); - (key, [1u8].into()) - }, - { - let key = encode_config_key(keyspace_id, "blob_age_cutoff"); - (key, blob_opts.age_cutoff.to_le_bytes().into()) - }, - { - use lsm_tree::coding::Encode; - - let key = encode_config_key(keyspace_id, "blob_compression"); - (key, blob_opts.compression.encode_into_vec().into()) - }, - { - let key = encode_config_key(keyspace_id, "blob_file_target_size"); - (key, blob_opts.file_target_size.to_le_bytes().into()) - }, - { - let key = encode_config_key(keyspace_id, "blob_separation_threshold"); - (key, blob_opts.separation_threshold.to_le_bytes().into()) - }, - { - let key = encode_config_key(keyspace_id, "blob_staleness_threshold"); - (key, blob_opts.staleness_threshold.to_le_bytes().into()) - }, - ]); - } - kvs } - /// Toggles key-value separation. - #[must_use] - pub fn with_kv_separation(mut self, opts: Option) -> Self { - self.kv_separation_opts = opts; - self - } - /// Sets the restart interval inside data blocks. /// /// A higher restart interval saves space while increasing lookup times @@ -632,18 +438,6 @@ impl CreateOptions { self } - /// Sets the compaction strategy. - /// - /// Default = Leveled - #[must_use] - pub fn compaction_strategy( - mut self, - compaction_strategy: Arc, - ) -> Self { - self.compaction_strategy = compaction_strategy; - self - } - /// If `false`, writes will flush data to the operating system. /// /// Default = false @@ -704,23 +498,12 @@ mod tests { c.data_block_compression_policy, CompressionPolicy::disabled(), ); - assert_eq!(c.kv_separation_opts, None); - - c = c.with_kv_separation(KvSeparationOptions::default()); - assert_eq!( - c.kv_separation_opts.as_ref().unwrap().compression, - CompressionType::None, - ); c = c.data_block_compression_policy(CompressionPolicy::disabled()); assert_eq!( c.data_block_compression_policy, CompressionPolicy::disabled(), ); - assert_eq!( - c.kv_separation_opts.unwrap().compression, - CompressionType::None, - ); } #[test] @@ -734,16 +517,11 @@ mod tests { c.data_block_compression_policy, CompressionPolicy::new([Uncompressed, Uncompressed, Lz4]), ); - assert_eq!(c.kv_separation_opts, None); - - c = c.with_kv_separation(Some(KvSeparationOptions::default())); - assert_eq!(c.kv_separation_opts.as_ref().unwrap().compression, Lz4); c = c.data_block_compression_policy(CompressionPolicy::disabled()); assert_eq!( c.data_block_compression_policy, CompressionPolicy::disabled(), ); - assert_eq!(c.kv_separation_opts.as_ref().unwrap().compression, Lz4); } } diff --git a/crates/fjall/src/lib.rs b/crates/fjall/src/lib.rs index c241516c4..125fde69c 100644 --- a/crates/fjall/src/lib.rs +++ b/crates/fjall/src/lib.rs @@ -11,7 +11,6 @@ //! - Keyspaces (a.k.a. column families) with cross-keyspace atomic semantics //! - Built-in compression (default = `LZ4`) //! - Serializable transactions (optional) -//! - Key-value separation for large blob use cases (optional) //! - Automatic background maintenance //! //! It is not: @@ -98,10 +97,6 @@ pub mod compaction; mod db_config; -#[cfg(feature = "__internal_whitebox")] -#[doc(hidden)] -pub mod drop; - mod db; #[cfg(test)] @@ -126,7 +121,6 @@ mod snapshot_nonce; mod snapshot_tracker; mod stats; mod supervisor; -mod tx; mod version; mod worker_pool; mod write_buffer_manager; @@ -152,27 +146,16 @@ pub use { guard::Guard, iter::Iter, journal::{error::RecoveryError as JournalRecoveryError, writer::PersistMode}, - keyspace::{options::CreateOptions as KeyspaceCreateOptions, Keyspace}, + keyspace::{Keyspace, options::CreateOptions as KeyspaceCreateOptions}, readable::Readable, snapshot::Snapshot, version::FormatVersion, }; -pub use tx::single_writer::{ - SingleWriterTxKeyspace, TxDatabase as SingleWriterTxDatabase, - WriteTransaction as SingleWriterWriteTx, -}; - -pub use tx::optimistic::{ - Conflict, OptimisticTxDatabase, OptimisticTxKeyspace, WriteTransaction as OptimisticWriteTx, -}; - #[doc(hidden)] -pub use lsm_tree::{AbstractTree, AnyTree, Error as LsmError, TreeType}; +pub use lsm_tree::{AbstractTree, Error as LsmError}; -pub use lsm_tree::{ - CompressionType, KvPair, KvSeparationOptions, SeqNo, Slice, UserKey, UserValue, -}; +pub use lsm_tree::{CompressionType, KvPair, SeqNo, Slice, UserKey, UserValue}; /// Utility functions pub mod util { diff --git a/crates/fjall/src/locked_file.rs b/crates/fjall/src/locked_file.rs index 34bcafafe..fa018f8b3 100644 --- a/crates/fjall/src/locked_file.rs +++ b/crates/fjall/src/locked_file.rs @@ -61,7 +61,9 @@ impl LockedFileGuard { if let Err(e) = file.try_lock() { match e { std::fs::TryLockError::Error(e) => { - log::error!("Failed to acquire database lock - if this is expected, you can try opening again (maybe wait a little)"); + log::error!( + "Failed to acquire database lock - if this is expected, you can try opening again (maybe wait a little)" + ); return Err(crate::Error::Io(e)); } std::fs::TryLockError::WouldBlock => { diff --git a/crates/fjall/src/meta_keyspace.rs b/crates/fjall/src/meta_keyspace.rs index 0a3e1cb2e..6bb30e366 100644 --- a/crates/fjall/src/meta_keyspace.rs +++ b/crates/fjall/src/meta_keyspace.rs @@ -2,13 +2,13 @@ // This source code is licensed under both the Apache 2.0 and MIT License // (found in the LICENSE-* files in the repository) -use crate::{db::Keyspaces, keyspace::InternalKeyspaceId, Keyspace}; +use crate::{Keyspace, db::Keyspaces, keyspace::InternalKeyspaceId}; use byteview::StrView; -use lsm_tree::{AbstractTree, AnyTree, SeqNo, SequenceNumberCounter, UserValue}; +use lsm_tree::{AbstractTree, SeqNo, SequenceNumberCounter, Tree, UserValue}; use std::sync::{Arc, RwLock, RwLockWriteGuard}; pub fn encode_config_key(keyspace_id: InternalKeyspaceId, name: &str) -> crate::UserKey { - use byteorder::{WriteBytesExt, BE}; + use byteorder::{BE, WriteBytesExt}; use std::io::Write; #[expect(unsafe_code)] @@ -46,7 +46,7 @@ pub struct MetaKeyspace { /// /// We never commit data through the WAL but write tables directly using /// lsm-tree's ingestion API. - inner: AnyTree, + inner: Tree, /// Dictionary of all keyspaces #[doc(hidden)] @@ -58,7 +58,7 @@ pub struct MetaKeyspace { impl MetaKeyspace { pub(crate) fn new( - inner: AnyTree, + inner: Tree, keyspaces: Arc>, seqno_generator: SequenceNumberCounter, visible_seqno: SequenceNumberCounter, @@ -224,7 +224,7 @@ impl MetaKeyspace { #[cfg(test)] mod tests { - use crate::{Database, Guard, KeyspaceCreateOptions, Readable, SingleWriterTxDatabase}; + use crate::{Database, Guard, KeyspaceCreateOptions}; use test_log::test; const ITEM_COUNT: usize = 10; @@ -374,83 +374,6 @@ mod tests { Ok(()) } - #[test] - fn tx_keyspace_delete() -> crate::Result<()> { - let folder = tempfile::tempdir()?; - - let path; - - { - let db = SingleWriterTxDatabase::builder(&folder).open()?; - - let tree = db.keyspace("default", KeyspaceCreateOptions::default)?; - path = tree.path(); - - assert!(path.try_exists()?); - - for x in 0..ITEM_COUNT as u64 { - let key = x.to_be_bytes(); - let value = nanoid::nanoid!(); - tree.insert(key, value.as_bytes())?; - } - - for x in 0..ITEM_COUNT as u64 { - let key: [u8; 8] = (x + ITEM_COUNT as u64).to_be_bytes(); - let value = nanoid::nanoid!(); - tree.insert(key, value.as_bytes())?; - } - - assert_eq!(db.read_tx().len(&tree)?, ITEM_COUNT * 2); - assert_eq!( - db.read_tx().iter(&tree).flat_map(Guard::key).count(), - ITEM_COUNT * 2, - ); - assert_eq!( - db.read_tx().iter(&tree).rev().flat_map(Guard::key).count(), - ITEM_COUNT * 2, - ); - } - - for _ in 0..3 { - let db = SingleWriterTxDatabase::builder(&folder).open()?; - - let tree = db.keyspace("default", KeyspaceCreateOptions::default)?; - - assert_eq!(db.read_tx().len(&tree)?, ITEM_COUNT * 2); - assert_eq!( - db.read_tx().iter(&tree).flat_map(Guard::key).count(), - ITEM_COUNT * 2, - ); - assert_eq!( - db.read_tx().iter(&tree).rev().flat_map(Guard::key).count(), - ITEM_COUNT * 2, - ); - - assert!(path.try_exists()?); - } - - { - let db = SingleWriterTxDatabase::builder(&folder).open()?; - - { - let tree = db.keyspace("default", KeyspaceCreateOptions::default)?; - - assert!(path.try_exists()?); - - db.inner().delete_keyspace(tree.inner().clone())?; - } - - assert!(!path.try_exists()?); - } - - { - let _db = Database::builder(&folder).open()?; - assert!(!path.try_exists()?); - } - - Ok(()) - } - #[test] fn keyspace_delete_and_reopening_behavior() -> crate::Result<()> { let keyspace_name = "default"; @@ -473,9 +396,10 @@ mod tests { db.delete_keyspace(keyspace)?; assert!(!keyspace_exists(1)?); - assert!(db - .keyspace("default", KeyspaceCreateOptions::default) - .is_ok()); + assert!( + db.keyspace("default", KeyspaceCreateOptions::default) + .is_ok() + ); assert!(keyspace_exists(2)?); Ok(()) diff --git a/crates/fjall/src/poison.rs b/crates/fjall/src/poison.rs index 5744b6c63..7000815a4 100644 --- a/crates/fjall/src/poison.rs +++ b/crates/fjall/src/poison.rs @@ -2,7 +2,7 @@ // This source code is licensed under both the Apache 2.0 and MIT License // (found in the LICENSE-* files in the repository) -use std::sync::{atomic::AtomicBool, Arc}; +use std::sync::{Arc, atomic::AtomicBool}; #[derive(Clone, Debug, Default)] pub struct PoisonSignal(Arc); diff --git a/crates/fjall/src/recovery.rs b/crates/fjall/src/recovery.rs index 629f87661..b7a49e8e9 100644 --- a/crates/fjall/src/recovery.rs +++ b/crates/fjall/src/recovery.rs @@ -3,15 +3,15 @@ // (found in the LICENSE-* files in the repository) use crate::{ + Database, HashMap, Keyspace, file::{KEYSPACES_FOLDER, LSM_CURRENT_VERSION_MARKER}, journal::{ batch_reader::JournalBatchReader, manager::EvictionWatermark, reader::JournalReader, }, keyspace::{ - apply_to_base_config, options::CreateOptions as KeyspaceCreateOptions, InternalKeyspaceId, + InternalKeyspaceId, apply_to_base_config, options::CreateOptions as KeyspaceCreateOptions, }, meta_keyspace::MetaKeyspace, - Database, HashMap, Keyspace, }; use lsm_tree::AbstractTree; use std::path::PathBuf; @@ -177,9 +177,6 @@ pub fn recover_sealed_memtables( lsm_tree::ValueType::WeakTombstone => { tree.remove_weak(item.key, batch.seqno); } - lsm_tree::ValueType::Indirection => { - unreachable!() - } } } diff --git a/crates/fjall/src/snapshot.rs b/crates/fjall/src/snapshot.rs index 920bb328d..dd594bc80 100644 --- a/crates/fjall/src/snapshot.rs +++ b/crates/fjall/src/snapshot.rs @@ -2,7 +2,7 @@ // This source code is licensed under both the Apache 2.0 and MIT License // (found in the LICENSE-* files in the repository) -use crate::{snapshot_nonce::SnapshotNonce, Guard, Iter, Keyspace, Readable}; +use crate::{Guard, Iter, Keyspace, Readable, snapshot_nonce::SnapshotNonce}; use lsm_tree::{AbstractTree, SeqNo, UserValue}; use std::ops::RangeBounds; diff --git a/crates/fjall/src/snapshot_nonce.rs b/crates/fjall/src/snapshot_nonce.rs index b8e47ad0f..9a7263b2d 100644 --- a/crates/fjall/src/snapshot_nonce.rs +++ b/crates/fjall/src/snapshot_nonce.rs @@ -2,7 +2,7 @@ // This source code is licensed under both the Apache 2.0 and MIT License // (found in the LICENSE-* files in the repository) -use crate::{snapshot_tracker::SnapshotTracker, SeqNo}; +use crate::{SeqNo, snapshot_tracker::SnapshotTracker}; /// Holds a snapshot instant and automatically frees it from the snapshot tracker when dropped pub struct SnapshotNonce { diff --git a/crates/fjall/src/snapshot_tracker.rs b/crates/fjall/src/snapshot_tracker.rs index 3d68fec2c..c9aa8f6f3 100644 --- a/crates/fjall/src/snapshot_tracker.rs +++ b/crates/fjall/src/snapshot_tracker.rs @@ -2,10 +2,10 @@ // This source code is licensed under both the Apache 2.0 and MIT License // (found in the LICENSE-* files in the repository) -use crate::{snapshot_nonce::SnapshotNonce, SeqNo}; +use crate::{SeqNo, snapshot_nonce::SnapshotNonce}; use dashmap::DashMap; use lsm_tree::SequenceNumberCounter; -use std::sync::{atomic::AtomicU64, Arc, RwLock}; +use std::sync::{Arc, RwLock, atomic::AtomicU64}; /// Keeps track of open snapshots pub struct SnapshotTrackerInner { diff --git a/crates/fjall/src/supervisor.rs b/crates/fjall/src/supervisor.rs index d29f530a0..277d1b6fe 100644 --- a/crates/fjall/src/supervisor.rs +++ b/crates/fjall/src/supervisor.rs @@ -7,7 +7,7 @@ use lsm_tree::SequenceNumberCounter; use crate::{ db::Keyspaces, flush::manager::FlushManager, - journal::{manager::JournalManager, Journal}, + journal::{Journal, manager::JournalManager}, snapshot_tracker::SnapshotTracker, write_buffer_manager::WriteBufferManager, }; diff --git a/crates/fjall/src/tx/mod.rs b/crates/fjall/src/tx/mod.rs deleted file mode 100644 index 4a0c61dfa..000000000 --- a/crates/fjall/src/tx/mod.rs +++ /dev/null @@ -1,7 +0,0 @@ -// Copyright (c) 2024-present, fjall-rs -// This source code is licensed under both the Apache 2.0 and MIT License -// (found in the LICENSE-* files in the repository) - -pub mod optimistic; -pub mod single_writer; -pub mod write_tx; diff --git a/crates/fjall/src/tx/optimistic/conflict_manager.rs b/crates/fjall/src/tx/optimistic/conflict_manager.rs deleted file mode 100644 index 992d52fce..000000000 --- a/crates/fjall/src/tx/optimistic/conflict_manager.rs +++ /dev/null @@ -1,197 +0,0 @@ -// Copyright (c) 2024-present, fjall-rs -// This source code is licensed under both the Apache 2.0 and MIT License -// (found in the LICENSE-* files in the repository) - -use crate::keyspace::InternalKeyspaceId; -use core::ops::Bound; -use lsm_tree::Slice; -use std::{ - collections::{BTreeMap, BTreeSet}, - ops::RangeBounds, - sync::Mutex, -}; - -#[derive(Clone, Debug)] -enum Read { - Single(Slice), - Range { - start: Bound, - end: Bound, - }, - All, -} - -#[derive(Default, Debug)] -pub struct ConflictManager { - reads: Mutex>>, - conflict_keys: Mutex>>, -} - -impl ConflictManager { - fn push_read(&self, keyspace_id: InternalKeyspaceId, read: Read) { - #[expect(clippy::expect_used)] - let mut lock = self.reads.lock().expect("lock is poisoned"); - - if let Some(tbl) = lock.get_mut(&keyspace_id) { - tbl.push(read); - } else { - lock.entry(keyspace_id).or_default().push(read); - } - } - - pub fn mark_read(&self, keyspace_id: InternalKeyspaceId, key: Slice) { - self.push_read(keyspace_id, Read::Single(key)); - } - - pub fn mark_conflict(&self, keyspace_id: InternalKeyspaceId, key: Slice) { - #[expect(clippy::expect_used)] - let mut lock = self.conflict_keys.lock().expect("lock is poisoned"); - - if let Some(tbl) = lock.get_mut(&keyspace_id) { - tbl.insert(key); - } else { - lock.entry(keyspace_id).or_default().insert(key); - } - } - - pub fn mark_range(&self, keyspace_id: InternalKeyspaceId, range: impl RangeBounds) { - let start = match range.start_bound() { - Bound::Included(k) => Bound::Included(k.clone()), - Bound::Excluded(k) => Bound::Excluded(k.clone()), - Bound::Unbounded => Bound::Unbounded, - }; - - let end = match range.end_bound() { - Bound::Included(k) => Bound::Included(k.clone()), - Bound::Excluded(k) => Bound::Excluded(k.clone()), - Bound::Unbounded => Bound::Unbounded, - }; - - let read = if start == Bound::Unbounded && end == Bound::Unbounded { - Read::All - } else { - Read::Range { start, end } - }; - - self.push_read(keyspace_id, read); - } - - #[expect(clippy::too_many_lines, clippy::significant_drop_tightening)] - pub fn has_conflict(&self, other: &Self) -> bool { - #[expect(clippy::expect_used)] - let reads_lock = self.reads.lock().expect("lock is poisoned"); - - #[expect(clippy::expect_used)] - let conflict_keys_lock = other.conflict_keys.lock().expect("lock is poisoned"); - - if reads_lock.is_empty() { - return false; - } - - for (keyspace_name, keys) in &*reads_lock { - if let Some(other_conflict_keys) = conflict_keys_lock.get(keyspace_name) { - for ro in keys { - match ro { - Read::Single(k) => { - if other_conflict_keys.contains(k) { - return true; - } - } - Read::Range { start, end } => match (start, end) { - (Bound::Included(start), Bound::Included(end)) => { - if other_conflict_keys - .range::(( - Bound::Included(start), - Bound::Included(end), - )) - .next() - .is_some() - { - return true; - } - } - (Bound::Included(start), Bound::Excluded(end)) => { - if other_conflict_keys - .range::(( - Bound::Included(start), - Bound::Excluded(end), - )) - .next() - .is_some() - { - return true; - } - } - (Bound::Included(start), Bound::Unbounded) => { - if other_conflict_keys - .range::((Bound::Included(start), Bound::Unbounded)) - .next() - .is_some() - { - return true; - } - } - (Bound::Excluded(start), Bound::Included(end)) => { - if other_conflict_keys - .range::(( - Bound::Excluded(start), - Bound::Included(end), - )) - .next() - .is_some() - { - return true; - } - } - (Bound::Excluded(start), Bound::Excluded(end)) => { - if other_conflict_keys - .range::(( - Bound::Excluded(start), - Bound::Excluded(end), - )) - .next() - .is_some() - { - return true; - } - } - (Bound::Excluded(start), Bound::Unbounded) => { - if other_conflict_keys - .range::((Bound::Excluded(start), Bound::Unbounded)) - .next() - .is_some() - { - return true; - } - } - (Bound::Unbounded, Bound::Included(end)) => { - let range = ..=end; - for write in other_conflict_keys { - if range.contains(&write) { - return true; - } - } - } - (Bound::Unbounded, Bound::Excluded(end)) => { - let range = ..end; - for write in other_conflict_keys { - if range.contains(&write) { - return true; - } - } - } - (Bound::Unbounded, Bound::Unbounded) => unreachable!(), - }, - Read::All => { - if !other_conflict_keys.is_empty() { - return true; - } - } - } - } - } - } - - false - } -} diff --git a/crates/fjall/src/tx/optimistic/keyspace.rs b/crates/fjall/src/tx/optimistic/keyspace.rs deleted file mode 100644 index 3e9d003a3..000000000 --- a/crates/fjall/src/tx/optimistic/keyspace.rs +++ /dev/null @@ -1,502 +0,0 @@ -// Copyright (c) 2024-present, fjall-rs -// This source code is licensed under both the Apache 2.0 and MIT License -// (found in the LICENSE-* files in the repository) - -use crate::{tx::optimistic::OptimisticTxDatabase, Keyspace}; -use crate::{Guard, Readable}; -use lsm_tree::{UserKey, UserValue}; -use std::path::PathBuf; - -/// Handle to a keyspace of a transactional database -#[derive(Clone)] -pub struct OptimisticTxKeyspace { - pub(crate) inner: Keyspace, - pub(crate) db: OptimisticTxDatabase, -} - -impl AsRef for OptimisticTxKeyspace { - fn as_ref(&self) -> &Keyspace { - self.inner() - } -} - -impl OptimisticTxKeyspace { - /// Returns the underlying LSM-tree's path. - #[must_use] - pub fn path(&self) -> PathBuf { - self.inner.path().into() - } - - /// Approximates the number of items in the keyspace. - /// - /// For update- or delete-heavy workloads, this value will - /// diverge from the real value, but is a O(1) operation. - /// - /// For insert-only workloads (e.g. logs, time series) - /// this value is reliable. - /// - /// # Examples - /// - /// ``` - /// # use fjall::{OptimisticTxDatabase, KeyspaceCreateOptions, Readable}; - /// # - /// # let folder = tempfile::tempdir()?; - /// # let db = OptimisticTxDatabase::builder(folder).open()?; - /// # let tree = db.keyspace("default", KeyspaceCreateOptions::default)?; - /// assert_eq!(tree.approximate_len(), 0); - /// - /// tree.insert("1", "abc")?; - /// assert_eq!(tree.approximate_len(), 1); - /// - /// tree.remove("1")?; - /// // Oops! approximate_len will not be reliable here - /// assert_eq!(tree.approximate_len(), 2); - /// # - /// # Ok::<(), fjall::Error>(()) - /// ``` - #[must_use] - pub fn approximate_len(&self) -> usize { - self.inner.approximate_len() - } - - /// Removes an item and returns its value if it existed. - /// - /// The operation will run wrapped in a transaction. - /// - /// ``` - /// # use fjall::{OptimisticTxDatabase, KeyspaceCreateOptions, Readable}; - /// # use std::sync::Arc; - /// # - /// # let folder = tempfile::tempdir()?; - /// # let db = OptimisticTxDatabase::builder(folder).open()?; - /// # let tree = db.keyspace("default", KeyspaceCreateOptions::default)?; - /// tree.insert("a", "abc")?; - /// - /// let taken = tree.take("a")?.unwrap(); - /// assert_eq!(b"abc", &*taken); - /// - /// let item = tree.get("a")?; - /// assert!(item.is_none()); - /// # - /// # Ok::<(), fjall::Error>(()) - /// ``` - /// - /// # Errors - /// - /// Will return `Err` if an IO error occurs. - pub fn take>(&self, key: K) -> crate::Result> { - self.fetch_update(key, |_| None) - } - - /// Atomically updates an item and returns the previous value. - /// - /// Returning `None` removes the item if it existed before. - /// - /// The operation will run wrapped in a transaction. - /// - /// # Note - /// - /// The provided closure can be called multiple times as this function - /// automatically retries on conflict. Since this is an `FnMut`, make sure - /// it is idempotent and will not cause side-effects. - /// - /// # Examples - /// - /// ``` - /// # use fjall::{OptimisticTxDatabase, Slice, KeyspaceCreateOptions}; - /// # use std::sync::Arc; - /// # - /// # let folder = tempfile::tempdir()?; - /// # let db = OptimisticTxDatabase::builder(folder).open()?; - /// # let tree = db.keyspace("default", KeyspaceCreateOptions::default)?; - /// tree.insert("a", "abc")?; - /// - /// let prev = tree.fetch_update("a", |_| Some(Slice::from(*b"def")))?.unwrap(); - /// assert_eq!(b"abc", &*prev); - /// - /// let item = tree.get("a")?; - /// assert_eq!(Some("def".as_bytes().into()), item); - /// # - /// # Ok::<(), fjall::Error>(()) - /// ``` - /// - /// ``` - /// # use fjall::{OptimisticTxDatabase, KeyspaceCreateOptions, Readable}; - /// # use std::sync::Arc; - /// # - /// # let folder = tempfile::tempdir()?; - /// # let db = OptimisticTxDatabase::builder(folder).open()?; - /// # let tree = db.keyspace("default", KeyspaceCreateOptions::default)?; - /// tree.insert("a", "abc")?; - /// - /// let prev = tree.fetch_update("a", |_| None)?.unwrap(); - /// assert_eq!(b"abc", &*prev); - /// - /// let item = tree.get("a")?; - /// assert!(item.is_none()); - /// # - /// # Ok::<(), fjall::Error>(()) - /// ``` - /// - /// # Errors - /// - /// Will return `Err` if an IO error occurs. - pub fn fetch_update, F: FnMut(Option<&UserValue>) -> Option>( - &self, - key: K, - mut f: F, - ) -> crate::Result> { - let key = key.into(); - - loop { - let mut tx = self.db.write_tx()?; - let prev = tx.fetch_update(self.inner(), key.clone(), &mut f)?; - if tx.commit()?.is_ok() { - return Ok(prev); - } - } - } - - /// Atomically updates an item and returns the new value. - /// - /// Returning `None` removes the item if it existed before. - /// - /// The operation will run wrapped in a transaction. - /// - /// # Note - /// - /// The provided closure can be called multiple times as this function - /// automatically retries on conflict. Since this is an `FnMut`, make sure - /// it is idempotent and will not cause side-effects. - /// - /// # Examples - /// - /// ``` - /// # use fjall::{OptimisticTxDatabase, Slice, KeyspaceCreateOptions}; - /// # use std::sync::Arc; - /// # - /// # let folder = tempfile::tempdir()?; - /// # let db = OptimisticTxDatabase::builder(folder).open()?; - /// # let tree = db.keyspace("default", KeyspaceCreateOptions::default)?; - /// tree.insert("a", "abc")?; - /// - /// let updated = tree.update_fetch("a", |_| Some(Slice::from(*b"def")))?.unwrap(); - /// assert_eq!(b"def", &*updated); - /// - /// let item = tree.get("a")?; - /// assert_eq!(Some("def".as_bytes().into()), item); - /// # - /// # Ok::<(), fjall::Error>(()) - /// ``` - /// - /// ``` - /// # use fjall::{OptimisticTxDatabase, KeyspaceCreateOptions, Readable}; - /// # use std::sync::Arc; - /// # - /// # let folder = tempfile::tempdir()?; - /// # let db = OptimisticTxDatabase::builder(folder).open()?; - /// # let tree = db.keyspace("default", KeyspaceCreateOptions::default)?; - /// tree.insert("a", "abc")?; - /// - /// let updated = tree.update_fetch("a", |_| None)?; - /// assert!(updated.is_none()); - /// - /// let item = tree.get("a")?; - /// assert!(item.is_none()); - /// # - /// # Ok::<(), fjall::Error>(()) - /// ``` - /// - /// # Errors - /// - /// Will return `Err` if an IO error occurs. - pub fn update_fetch, F: FnMut(Option<&UserValue>) -> Option>( - &self, - key: K, - mut f: F, - ) -> crate::Result> { - let key = key.into(); - - loop { - let mut tx = self.db.write_tx()?; - let updated = tx.update_fetch(self.inner(), key.clone(), &mut f)?; - if tx.commit()?.is_ok() { - return Ok(updated); - } - } - } - - /// Inserts a key-value pair into the keyspace. - /// - /// Keys may be up to 65536 bytes long, values up to 2^32 bytes. - /// Shorter keys and values result in better performance. - /// - /// If the key already exists, the item will be overwritten. - /// - /// The operation will run wrapped in a transaction. - /// - /// # Examples - /// - /// ``` - /// # use fjall::{OptimisticTxDatabase, KeyspaceCreateOptions, Readable}; - /// # - /// # let folder = tempfile::tempdir()?; - /// # let db = OptimisticTxDatabase::builder(folder).open()?; - /// # let tree = db.keyspace("default", KeyspaceCreateOptions::default)?; - /// tree.insert("a", "abc")?; - /// - /// assert!(!db.read_tx().is_empty(&tree)?); - /// # - /// # Ok::<(), fjall::Error>(()) - /// ``` - /// - /// # Errors - /// - /// Will return `Err` if an IO error occurs. - pub fn insert, V: Into>( - &self, - key: K, - value: V, - ) -> crate::Result<()> { - let mut tx = self.db.write_tx()?; - tx.insert(self.inner(), key, value); - - #[expect( - clippy::expect_used, - clippy::missing_panics_doc, - reason = "blind insert should not conflict ever" - )] - tx.commit()?.expect("blind insert should not conflict ever"); - - Ok(()) - } - - /// Removes an item from the keyspace. - /// - /// The key may be up to 65536 bytes long. - /// Shorter keys result in better performance. - /// - /// The operation will run wrapped in a transaction. - /// - /// # Examples - /// - /// ``` - /// # use fjall::{OptimisticTxDatabase, KeyspaceCreateOptions, Readable}; - /// # - /// # let folder = tempfile::tempdir()?; - /// # let db = OptimisticTxDatabase::builder(folder).open()?; - /// # let tree = db.keyspace("default", KeyspaceCreateOptions::default)?; - /// tree.insert("a", "abc")?; - /// assert!(!db.read_tx().is_empty(&tree)?); - /// - /// tree.remove("a")?; - /// assert!(db.read_tx().is_empty(&tree)?); - /// # - /// # Ok::<(), fjall::Error>(()) - /// ``` - /// - /// # Errors - /// - /// Will return `Err` if an IO error occurs. - pub fn remove>(&self, key: K) -> crate::Result<()> { - let mut tx = self.db.write_tx()?; - tx.remove(self.inner(), key); - - #[expect( - clippy::expect_used, - clippy::missing_panics_doc, - reason = "blind remove should not conflict ever" - )] - tx.commit()?.expect("blind remove should not conflict ever"); - - Ok(()) - } - - /// Removes an item from the keyspace, leaving behind a weak tombstone. - /// - /// The tombstone marker of this delete operation will vanish when it - /// collides with its corresponding insertion. - /// This may cause older versions of the value to be resurrected, so it should - /// only be used and preferred in scenarios where a key is only ever written once. - /// - /// The key may be up to 65536 bytes long. - /// Shorter keys result in better performance. - /// - /// The operation will run wrapped in a transaction. - /// - /// # Experimental - /// - /// This function is currently experimental. - /// - /// # Examples - /// - /// ``` - /// # use fjall::{OptimisticTxDatabase, KeyspaceCreateOptions, Readable}; - /// # - /// # let folder = tempfile::tempdir()?; - /// # let db = OptimisticTxDatabase::builder(folder).open()?; - /// # let tree = db.keyspace("default", KeyspaceCreateOptions::default)?; - /// tree.insert("a", "abc")?; - /// assert!(!db.read_tx().is_empty(&tree)?); - /// - /// tree.remove_weak("a")?; - /// assert!(db.read_tx().is_empty(&tree)?); - /// # - /// # Ok::<(), fjall::Error>(()) - /// ``` - /// - /// # Errors - /// - /// Will return `Err` if an IO error occurs. - #[doc(hidden)] - pub fn remove_weak>(&self, key: K) -> crate::Result<()> { - let mut tx = self.db.write_tx()?; - tx.remove_weak(self.inner(), key); - - #[expect(clippy::expect_used)] - tx.commit()?.expect("blind remove should not conflict ever"); - - Ok(()) - } - - /// Retrieves an item from the keyspace. - /// - /// The operation will run wrapped in a read snapshot. - /// - /// # Examples - /// - /// ``` - /// # use fjall::{OptimisticTxDatabase, KeyspaceCreateOptions, Readable}; - /// # - /// # let folder = tempfile::tempdir()?; - /// # let db = OptimisticTxDatabase::builder(folder).open()?; - /// # let tree = db.keyspace("default", KeyspaceCreateOptions::default)?; - /// tree.insert("a", "my_value")?; - /// - /// let item = tree.get("a")?; - /// assert_eq!(Some("my_value".as_bytes().into()), item); - /// # - /// # Ok::<(), fjall::Error>(()) - /// ``` - /// - /// # Errors - /// - /// Will return `Err` if an IO error occurs. - pub fn get>(&self, key: K) -> crate::Result> { - self.inner.get(key) - } - - /// Retrieves the size of an item from the keyspace. - /// - /// The operation will run wrapped in a read snapshot. - /// - /// # Examples - /// - /// ``` - /// # use fjall::{OptimisticTxDatabase, KeyspaceCreateOptions, Readable}; - /// # - /// # let folder = tempfile::tempdir()?; - /// # let db = OptimisticTxDatabase::builder(folder).open()?; - /// # let tree = db.keyspace("default", KeyspaceCreateOptions::default)?; - /// tree.insert("a", "my_value")?; - /// - /// let len = tree.size_of("a")?.unwrap_or_default(); - /// assert_eq!("my_value".len() as u32, len); - /// # - /// # Ok::<(), fjall::Error>(()) - /// ``` - /// - /// # Errors - /// - /// Will return `Err` if an IO error occurs. - pub fn size_of>(&self, key: K) -> crate::Result> { - self.inner.size_of(key) - } - - /// Returns the first key-value pair in the keyspace. - /// The key in this pair is the minimum key in the keyspace. - /// - /// The operation will run wrapped in a read snapshot. - /// - /// # Examples - /// - /// ``` - /// # use fjall::{OptimisticTxDatabase, KeyspaceCreateOptions, Readable}; - /// # - /// # let folder = tempfile::tempdir()?; - /// # let db = OptimisticTxDatabase::builder(folder).open()?; - /// # let tree = db.keyspace("default", KeyspaceCreateOptions::default)?; - /// tree.insert("a", "my_value")?; - /// tree.insert("b", "my_value")?; - /// - /// assert_eq!(b"a", &*tree.first_key_value().unwrap().key()?); - /// # - /// # Ok::<(), fjall::Error>(()) - /// ``` - #[must_use] - pub fn first_key_value(&self) -> Option { - let read_tx = self.db.read_tx(); - read_tx.first_key_value(self) - } - - /// Returns the last key-value pair in the keyspace. - /// The key in this pair is the maximum key in the keyspace. - /// - /// The operation will run wrapped in a read snapshot. - /// - /// # Examples - /// - /// ``` - /// # use fjall::{OptimisticTxDatabase, KeyspaceCreateOptions, Readable}; - /// # - /// # let folder = tempfile::tempdir()?; - /// # let db = OptimisticTxDatabase::builder(folder).open()?; - /// # let tree = db.keyspace("default", KeyspaceCreateOptions::default)?; - /// tree.insert("a", "my_value")?; - /// tree.insert("b", "my_value")?; - /// - /// assert_eq!(b"b", &*tree.last_key_value().unwrap().key()?); - /// # - /// # Ok::<(), fjall::Error>(()) - /// ``` - #[must_use] - pub fn last_key_value(&self) -> Option { - let read_tx = self.db.read_tx(); - read_tx.last_key_value(self) - } - - /// Returns `true` if the keyspace contains the specified key. - /// - /// The operation will run wrapped in a read snapshot. - /// - /// # Examples - /// - /// ``` - /// # use fjall::{OptimisticTxDatabase, KeyspaceCreateOptions, Readable}; - /// # - /// # let folder = tempfile::tempdir()?; - /// # let db = OptimisticTxDatabase::builder(folder).open()?; - /// # let tree = db.keyspace("default", KeyspaceCreateOptions::default)?; - /// tree.insert("a", "my_value")?; - /// - /// assert!(tree.contains_key("a")?); - /// assert!(!tree.contains_key("b")?); - /// # - /// # Ok::<(), fjall::Error>(()) - /// ``` - /// - /// # Errors - /// - /// Will return `Err` if an IO error occurs. - pub fn contains_key>(&self, key: K) -> crate::Result { - self.inner.contains_key(key) - } - - /// Allows access to the inner keyspace handle, allowing to - /// escape from the transactional context. - #[doc(hidden)] - #[must_use] - pub fn inner(&self) -> &Keyspace { - &self.inner - } -} diff --git a/crates/fjall/src/tx/optimistic/mod.rs b/crates/fjall/src/tx/optimistic/mod.rs deleted file mode 100644 index 1dca25162..000000000 --- a/crates/fjall/src/tx/optimistic/mod.rs +++ /dev/null @@ -1,184 +0,0 @@ -// Copyright (c) 2024-present, fjall-rs -// This source code is licensed under both the Apache 2.0 and MIT License -// (found in the LICENSE-* files in the repository) - -mod conflict_manager; -mod keyspace; -mod oracle; -mod write_tx; - -use crate::{ - keyspace::KeyspaceKey, - tx::{optimistic::oracle::Oracle, single_writer::Openable}, - Config, Database, KeyspaceCreateOptions, PersistMode, Snapshot, -}; -use std::{ - path::Path, - sync::{Arc, Mutex}, -}; - -pub use keyspace::OptimisticTxKeyspace; -pub use write_tx::{Conflict, WriteTransaction}; - -/// Transactional database -#[derive(Clone)] -pub struct OptimisticTxDatabase { - pub(crate) inner: Database, - pub(super) oracle: Arc, -} - -impl Openable for OptimisticTxDatabase { - fn open(config: Config) -> crate::Result - where - Self: Sized, - { - let inner = Database::create_or_recover(config)?; - // inner.start_background_threads()?; - - Ok(Self { - oracle: Arc::new(Oracle { - write_serialize_lock: Mutex::default(), - snapshot_tracker: inner.supervisor.snapshot_tracker.clone(), - }), - inner, - }) - } -} - -impl OptimisticTxDatabase { - /// Creates a new database builder to create or open a database at `path`. - pub fn builder(path: impl AsRef) -> crate::DatabaseBuilder { - crate::DatabaseBuilder::new(path.as_ref()) - } - - #[doc(hidden)] - #[must_use] - pub fn inner(&self) -> &Database { - &self.inner - } - - /// Starts a new writeable transaction. - /// - /// # Errors - /// - /// Will return `Err` if creation failed. - pub fn write_tx(&self) -> crate::Result { - let snapshot = { - // acquire a lock here to prevent getting a stale snapshot seqno - // this will drain at least part of the commit queue, but ordering - // is platform-dependent since we use std::sync::Mutex - let _guard = self.oracle.write_serialize_lock()?; - - self.inner.supervisor.snapshot_tracker.open() - }; - - let mut write_tx = - WriteTransaction::new(self.inner().clone(), snapshot, self.oracle.clone()); - - if !self.inner.config.manual_journal_persist { - write_tx = write_tx.durability(Some(PersistMode::Buffer)); - } - - Ok(write_tx) - } - - /// Starts a new read-only transaction (a.k.a. [`Snapshot`]). - #[must_use] - pub fn read_tx(&self) -> Snapshot { - self.inner.snapshot() - } - - /// Flushes the active journal. The durability depends on the [`PersistMode`] - /// used. - /// - /// Persisting only affects durability, NOT consistency! Even without flushing - /// data is crash-safe. - /// - /// # Examples - /// - /// ``` - /// # use fjall::{PersistMode, OptimisticTxDatabase, KeyspaceCreateOptions}; - /// # let folder = tempfile::tempdir()?; - /// let db = OptimisticTxDatabase::builder(folder).open()?; - /// let items = db.keyspace("my_items", KeyspaceCreateOptions::default)?; - /// - /// items.insert("a", "hello")?; - /// - /// db.persist(PersistMode::SyncAll)?; - /// # - /// # Ok::<_, fjall::Error>(()) - /// ``` - /// - /// # Errors - /// - /// Returns error, if an IO error occurred. - pub fn persist(&self, mode: PersistMode) -> crate::Result<()> { - self.inner.persist(mode) - } - - /// Creates or opens a keyspace. - /// - /// If the keyspace does not yet exist, it will be created configured with `create_options`. - /// Otherwise simply a handle to the existing keyspace will be returned. - /// - /// Keyspace names can be up to 255 characters long and can not be empty. - /// - /// # Errors - /// - /// Returns error, if an IO error occurred. - /// - /// # Panics - /// - /// Panics if the keyspace name is invalid. - pub fn keyspace( - &self, - name: &str, - create_options: impl FnOnce() -> KeyspaceCreateOptions, - ) -> crate::Result { - let keyspace = self.inner.keyspace(name, create_options)?; - - Ok(OptimisticTxKeyspace { - inner: keyspace, - db: self.clone(), - }) - } - - /// Returns the number of keyspaces. - #[must_use] - pub fn keyspace_count(&self) -> usize { - self.inner.keyspace_count() - } - - /// Gets a list of all keyspace names in the database. - #[must_use] - pub fn list_keyspace_names(&self) -> Vec { - self.inner.list_keyspace_names() - } - - /// Returns `true` if the keyspace with the given name exists. - #[must_use] - pub fn keyspace_exists(&self, name: &str) -> bool { - self.inner.keyspace_exists(name) - } - - /// Returns the current write buffer size (active + sealed memtables). - #[must_use] - pub fn write_buffer_size(&self) -> u64 { - self.inner.write_buffer_size() - } - - /// Returns the number of journal fragments on disk. - #[must_use] - pub fn journal_count(&self) -> usize { - self.inner.journal_count() - } - - /// Returns the disk space usage of the entire database. - /// - /// # Errors - /// - /// Returns error, if an IO error occurred. - pub fn disk_space(&self) -> crate::Result { - self.inner.disk_space() - } -} diff --git a/crates/fjall/src/tx/optimistic/oracle.rs b/crates/fjall/src/tx/optimistic/oracle.rs deleted file mode 100644 index 5b423a6c1..000000000 --- a/crates/fjall/src/tx/optimistic/oracle.rs +++ /dev/null @@ -1,121 +0,0 @@ -// Copyright (c) 2024-present, fjall-rs -// This source code is licensed under both the Apache 2.0 and MIT License -// (found in the LICENSE-* files in the repository) - -use super::conflict_manager::ConflictManager; -use crate::snapshot_tracker::SnapshotTracker; -use crate::SeqNo; -use std::collections::BTreeMap; -use std::sync::{Mutex, MutexGuard}; - -pub enum CommitOutcome { - Ok, - Aborted(E), - Conflicted, -} - -pub struct Oracle { - pub(super) write_serialize_lock: Mutex>, - pub(super) snapshot_tracker: SnapshotTracker, -} - -impl Oracle { - pub(super) fn with_commit Result<(), E>>( - &self, - instant: SeqNo, - conflict_checker: ConflictManager, - f: F, - ) -> crate::Result> { - let mut committed_txns = self - .write_serialize_lock - .lock() - .map_err(|_| crate::Error::Poisoned)?; - - // If the committed_txn.ts is less than `SeqNo` that implies that the - // committed_txn finished before the current transaction started. - // We don't need to check for conflict in that case. - // This change assumes linearizability. Lack of linearizability could - // cause the read ts of a new txn to be lower than the commit ts of - // a txn before it. - let conflicted = - committed_txns - .range((instant + 1)..) - .any(|(_ts, other_conflict_checker)| { - conflict_checker.has_conflict(other_conflict_checker) - }); - - self.snapshot_tracker.close_raw(instant); - - // TODO: This can be expensive and should probably be done in a background worker, or a after a memtable rotation - let safe_to_gc = self.snapshot_tracker.get_seqno_safe_to_gc(); - committed_txns.retain(|ts, _| *ts > safe_to_gc); - - if conflicted { - return Ok(CommitOutcome::Conflicted); - } - - if let Err(e) = f() { - return Ok(CommitOutcome::Aborted(e)); - } - - committed_txns.insert(self.snapshot_tracker.get(), conflict_checker); - - Ok(CommitOutcome::Ok) - } - - pub(super) fn write_serialize_lock( - &self, - ) -> crate::Result>> { - self.write_serialize_lock - .lock() - .map_err(|_| crate::Error::Poisoned) - } -} - -#[cfg(test)] -mod tests { - use crate::{KeyspaceCreateOptions, OptimisticTxDatabase, OptimisticTxKeyspace, Readable}; - - #[expect(clippy::significant_drop_tightening)] - fn run_tx( - db: &OptimisticTxDatabase, - tree: &OptimisticTxKeyspace, - ) -> Result<(), Box> { - let mut tx1 = db.write_tx()?; - let mut tx2 = db.write_tx()?; - tx1.insert(tree.inner(), "hello", "world"); - - tx1.commit()??; - assert!(tree.contains_key("hello")?); - - _ = tx2.get(tree.inner(), "hello")?; - - tx2.insert(tree.inner(), "hello", "world2"); - assert!(tx2.commit()?.is_err()); // intended to conflict - - Ok(()) - } - - #[expect(clippy::unwrap_used)] - #[test] - fn oracle_committed_txns_does_not_leak() -> crate::Result<()> { - let tmpdir = tempfile::tempdir()?; - let db = OptimisticTxDatabase::builder(tmpdir.path()).open()?; - - let part = db.keyspace("foo", KeyspaceCreateOptions::default)?; - - for _ in 0..10_000 { - run_tx(&db, &part).unwrap(); - } - - assert!(dbg!(db.oracle.write_serialize_lock.lock().unwrap().len()) < 10_000); - - for _ in 0..10_000 { - run_tx(&db, &part).unwrap(); - } - - assert!(dbg!(db.oracle.write_serialize_lock.lock().unwrap().len()) < 10_000); - - Ok(()) - } -} diff --git a/crates/fjall/src/tx/optimistic/write_tx.rs b/crates/fjall/src/tx/optimistic/write_tx.rs deleted file mode 100644 index e4cfc2dfc..000000000 --- a/crates/fjall/src/tx/optimistic/write_tx.rs +++ /dev/null @@ -1,891 +0,0 @@ -// Copyright (c) 2024-present, fjall-rs -// This source code is licensed under both the Apache 2.0 and MIT License -// (found in the LICENSE-* files in the repository) - -use crate::{ - snapshot_nonce::SnapshotNonce, - tx::{ - optimistic::{ - conflict_manager::ConflictManager, - oracle::{CommitOutcome, Oracle}, - }, - write_tx::BaseTransaction, - }, - Database, Guard, Iter, Keyspace, PersistMode, Readable, -}; -use lsm_tree::{Slice, UserKey, UserValue}; -use std::{ - fmt, - ops::{Bound, RangeBounds, RangeFull}, - sync::Arc, -}; - -/// Transaction conflict -/// -/// SSI transactions can conflict which require them to be rerun. -#[derive(Debug)] -pub struct Conflict; - -impl std::error::Error for Conflict {} - -impl fmt::Display for Conflict { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - "Transaction conflict".fmt(f) - } -} - -/// A cross-keyspace transaction using optimistic concurrency control -/// -/// Use [`WriteTransaction::commit`] to commit changes to the keyspace(s). -/// -/// Transactions keep a consistent view of the database at the time, -/// meaning old data will not be dropped until it is not referenced by any active transaction. -/// -/// For that reason, you should try to keep transactions short-lived, and make sure they -/// are not held somewhere forever. -/// -/// # Caution -/// -/// The transaction may fail and have to be rerun if it conflicts. -#[clippy::has_significant_drop] -pub struct WriteTransaction { - inner: BaseTransaction, - cm: ConflictManager, - oracle: Arc, -} - -impl Readable for WriteTransaction { - fn get>( - &self, - keyspace: impl AsRef, - key: K, - ) -> crate::Result> { - let keyspace = keyspace.as_ref(); - - let res = self.inner.get(keyspace, key.as_ref())?; - - self.cm.mark_read(keyspace.id, key.as_ref().into()); - - Ok(res) - } - - fn contains_key>( - &self, - keyspace: impl AsRef, - key: K, - ) -> crate::Result { - let keyspace = keyspace.as_ref(); - - let contains = self.inner.contains_key(keyspace, key.as_ref())?; - - self.cm.mark_read(keyspace.id, key.as_ref().into()); - - Ok(contains) - } - - fn first_key_value(&self, keyspace: impl AsRef) -> Option { - self.iter(&keyspace).next() - } - - fn last_key_value(&self, keyspace: impl AsRef) -> Option { - self.iter(&keyspace).next_back() - } - - fn size_of>( - &self, - keyspace: impl AsRef, - key: K, - ) -> crate::Result> { - let keyspace = keyspace.as_ref(); - self.inner.size_of(keyspace, key) - } - - fn iter(&self, keyspace: impl AsRef) -> Iter { - self.cm.mark_range(keyspace.as_ref().id, RangeFull); - self.inner.iter(keyspace) - } - - fn range, R: RangeBounds>( - &self, - keyspace: impl AsRef, - range: R, - ) -> Iter { - let start: Bound = range.start_bound().map(|k| k.as_ref().into()); - let end: Bound = range.end_bound().map(|k| k.as_ref().into()); - - self.cm.mark_range(keyspace.as_ref().id, (start, end)); - - self.inner.range(keyspace, range) - } - - fn prefix>(&self, keyspace: impl AsRef, prefix: K) -> Iter { - self.range(keyspace, lsm_tree::range::prefix_to_range(prefix.as_ref())) - } -} - -impl WriteTransaction { - pub(crate) fn new(db: Database, nonce: SnapshotNonce, oracle: Arc) -> Self { - Self { - inner: BaseTransaction::new(db, nonce), - cm: ConflictManager::default(), - oracle, - } - } - - /// Sets the durability level. - #[must_use] - pub fn durability(mut self, mode: Option) -> Self { - self.inner = self.inner.durability(mode); - self - } - - /// Removes an item and returns its value if it existed. - /// - /// ``` - /// # use fjall::{OptimisticTxDatabase, KeyspaceCreateOptions, Readable}; - /// # use std::sync::Arc; - /// # - /// # let folder = tempfile::tempdir()?; - /// # let db = OptimisticTxDatabase::builder(folder).open()?; - /// # let tree = db.keyspace("default", KeyspaceCreateOptions::default)?; - /// tree.insert("a", "abc")?; - /// - /// let mut tx = db.write_tx()?; - /// - /// let taken = tx.take(&tree, "a")?.unwrap(); - /// assert_eq!(b"abc", &*taken); - /// tx.commit()?; - /// - /// let item = tree.get("a")?; - /// assert!(item.is_none()); - /// # - /// # Ok::<(), fjall::Error>(()) - /// ``` - /// - /// # Errors - /// - /// Will return `Err` if an IO error occurs. - pub fn take>( - &mut self, - keyspace: impl AsRef, - key: K, - ) -> crate::Result> { - self.fetch_update(keyspace, key, |_| None) - } - - /// Atomically updates an item and returns the new value. - /// - /// Returning `None` removes the item if it existed before. - /// - /// # Examples - /// - /// ``` - /// # use fjall::{OptimisticTxDatabase, KeyspaceCreateOptions, Readable, Slice}; - /// # - /// # let folder = tempfile::tempdir()?; - /// # let db = OptimisticTxDatabase::builder(folder).open()?; - /// # let tree = db.keyspace("default", KeyspaceCreateOptions::default)?; - /// tree.insert("a", "abc")?; - /// - /// let mut tx = db.write_tx()?; - /// - /// let updated = tx.update_fetch(&tree, "a", |_| Some(Slice::from(*b"def")))?.unwrap(); - /// assert_eq!(b"def", &*updated); - /// tx.commit()?; - /// - /// let item = tree.get("a")?; - /// assert_eq!(Some("def".as_bytes().into()), item); - /// # - /// # Ok::<(), fjall::Error>(()) - /// ``` - /// - /// ``` - /// # use fjall::{OptimisticTxDatabase, KeyspaceCreateOptions, Readable}; - /// # use std::sync::Arc; - /// # - /// # let folder = tempfile::tempdir()?; - /// # let db = OptimisticTxDatabase::builder(folder).open()?; - /// # let tree = db.keyspace("default", KeyspaceCreateOptions::default)?; - /// tree.insert("a", "abc")?; - /// - /// let mut tx = db.write_tx()?; - /// - /// let updated = tx.update_fetch(&tree, "a", |_| None)?; - /// assert!(updated.is_none()); - /// tx.commit()?; - /// - /// let item = tree.get("a")?; - /// assert!(item.is_none()); - /// # - /// # Ok::<(), fjall::Error>(()) - /// ``` - /// - /// # Errors - /// - /// Will return `Err` if an IO error occurs. - pub fn update_fetch, F: FnOnce(Option<&UserValue>) -> Option>( - &mut self, - keyspace: impl AsRef, - key: K, - f: F, - ) -> crate::Result> { - let keyspace = keyspace.as_ref(); - let key: UserKey = key.into(); - - let updated = self.inner.update_fetch(keyspace, key.clone(), f)?; - - self.cm.mark_read(keyspace.id, key.clone()); - self.cm.mark_conflict(keyspace.id, key); - - Ok(updated) - } - - /// Atomically updates an item and returns the previous value. - /// - /// Returning `None` removes the item if it existed before. - /// - /// # Examples - /// - /// ``` - /// # use fjall::{OptimisticTxDatabase, KeyspaceCreateOptions, Readable, Slice}; - /// # - /// # let folder = tempfile::tempdir()?; - /// # let db = OptimisticTxDatabase::builder(folder).open()?; - /// # let tree = db.keyspace("default", KeyspaceCreateOptions::default)?; - /// tree.insert("a", "abc")?; - /// - /// let mut tx = db.write_tx()?; - /// - /// let prev = tx.fetch_update(&tree, "a", |_| Some(Slice::from(*b"def")))?.unwrap(); - /// assert_eq!(b"abc", &*prev); - /// tx.commit()?; - /// - /// let item = tree.get("a")?; - /// assert_eq!(Some("def".as_bytes().into()), item); - /// # - /// # Ok::<(), fjall::Error>(()) - /// ``` - /// - /// ``` - /// # use fjall::{OptimisticTxDatabase, KeyspaceCreateOptions, Readable}; - /// # use std::sync::Arc; - /// # - /// # let folder = tempfile::tempdir()?; - /// # let db = OptimisticTxDatabase::builder(folder).open()?; - /// # let tree = db.keyspace("default", KeyspaceCreateOptions::default)?; - /// tree.insert("a", "abc")?; - /// - /// let mut tx = db.write_tx()?; - /// - /// let prev = tx.fetch_update(&tree, "a", |_| None)?.unwrap(); - /// assert_eq!(b"abc", &*prev); - /// tx.commit()?; - /// - /// let item = tree.get("a")?; - /// assert!(item.is_none()); - /// # - /// # Ok::<(), fjall::Error>(()) - /// ``` - /// - /// # Errors - /// - /// Will return `Err` if an IO error occurs. - pub fn fetch_update, F: FnOnce(Option<&UserValue>) -> Option>( - &mut self, - keyspace: impl AsRef, - key: K, - f: F, - ) -> crate::Result> { - let keyspace = keyspace.as_ref(); - let key = key.into(); - - let prev = self.inner.fetch_update(keyspace, key.clone(), f)?; - - self.cm.mark_read(keyspace.id, key.clone()); - self.cm.mark_conflict(keyspace.id, key); - - Ok(prev) - } - - /// Inserts a key-value pair into the keyspace. - /// - /// Keys may be up to 65536 bytes long, values up to 2^32 bytes. - /// Shorter keys and values result in better performance. - /// - /// If the key already exists, the item will be overwritten. - /// - /// # Examples - /// - /// ``` - /// # use fjall::{OptimisticTxDatabase, KeyspaceCreateOptions, Readable}; - /// # - /// # let folder = tempfile::tempdir()?; - /// # let db = OptimisticTxDatabase::builder(folder).open()?; - /// # let tree = db.keyspace("default", KeyspaceCreateOptions::default)?; - /// tree.insert("a", "previous_value")?; - /// assert_eq!(b"previous_value", &*tree.get("a")?.unwrap()); - /// - /// let mut tx = db.write_tx()?; - /// tx.insert(&tree, "a", "new_value"); - /// - /// drop(tx); - /// - /// // Write was not committed - /// assert_eq!(b"previous_value", &*tree.get("a")?.unwrap()); - /// # - /// # Ok::<(), fjall::Error>(()) - /// ``` - pub fn insert, V: Into>( - &mut self, - keyspace: impl AsRef, - key: K, - value: V, - ) { - let keyspace = keyspace.as_ref(); - let key: UserKey = key.into(); - - self.inner.insert(keyspace, key.clone(), value); - self.cm.mark_conflict(keyspace.id, key); - } - - /// Removes an item from the keyspace. - /// - /// The key may be up to 65536 bytes long. - /// Shorter keys result in better performance. - /// - /// # Examples - /// - /// ``` - /// # use fjall::{OptimisticTxDatabase, KeyspaceCreateOptions, Readable}; - /// # - /// # let folder = tempfile::tempdir()?; - /// # let db = OptimisticTxDatabase::builder(folder).open()?; - /// # let tree = db.keyspace("default", KeyspaceCreateOptions::default)?; - /// tree.insert("a", "previous_value")?; - /// assert_eq!(b"previous_value", &*tree.get("a")?.unwrap()); - /// - /// let mut tx = db.write_tx()?; - /// tx.remove(&tree, "a"); - /// - /// // Read-your-own-write - /// let item = tx.get(&tree, "a")?; - /// assert_eq!(None, item); - /// - /// drop(tx); - /// - /// // Deletion was not committed - /// assert_eq!(b"previous_value", &*tree.get("a")?.unwrap()); - /// # - /// # Ok::<(), fjall::Error>(()) - /// ``` - pub fn remove>(&mut self, keyspace: impl AsRef, key: K) { - let keyspace = keyspace.as_ref(); - let key: UserKey = key.into(); - - self.inner.remove(keyspace, key.clone()); - self.cm.mark_conflict(keyspace.id, key); - } - - /// Removes an item from the keyspace, leaving behind a weak tombstone. - /// - /// The tombstone marker of this delete operation will vanish when it - /// collides with its corresponding insertion. - /// This may cause older versions of the value to be resurrected, so it should - /// only be used and preferred in scenarios where a key is only ever written once. - /// - /// # Experimental - /// - /// This function is currently experimental. - /// - /// # Examples - /// - /// ``` - /// # use fjall::{OptimisticTxDatabase, KeyspaceCreateOptions, Readable}; - /// # - /// # let folder = tempfile::tempdir()?; - /// # let db = OptimisticTxDatabase::builder(folder).open()?; - /// # let tree = db.keyspace("default", KeyspaceCreateOptions::default)?; - /// tree.insert("a", "previous_value")?; - /// assert_eq!(b"previous_value", &*tree.get("a")?.unwrap()); - /// - /// let mut tx = db.write_tx()?; - /// tx.remove_weak(&tree, "a"); - /// - /// // Read-your-own-write - /// let item = tx.get(&tree, "a")?; - /// assert_eq!(None, item); - /// - /// drop(tx); - /// - /// // Deletion was not committed - /// assert_eq!(b"previous_value", &*tree.get("a")?.unwrap()); - /// # - /// # Ok::<(), fjall::Error>(()) - /// ``` - #[doc(hidden)] - pub fn remove_weak>(&mut self, keyspace: impl AsRef, key: K) { - let keyspace = keyspace.as_ref(); - let key: UserKey = key.into(); - - self.inner.remove_weak(keyspace, key.clone()); - self.cm.mark_conflict(keyspace.id, key); - } - - /// Commits the transaction. - /// - /// # Errors - /// - /// Will return `Err` if an IO error occurs. - pub fn commit(self) -> crate::Result> { - // NOTE: We have no write set, so we are basically - // a read-only transaction, so nothing to do here - if self.inner.memtables.is_empty() { - return Ok(Ok(())); - } - - let oracle = self.oracle.clone(); - - match oracle.with_commit(self.inner.nonce.instant, self.cm, move || { - self.inner.commit() - })? { - CommitOutcome::Ok => Ok(Ok(())), - CommitOutcome::Aborted(e) => Err(e), - CommitOutcome::Conflicted => Ok(Err(Conflict)), - } - } - - /// More explicit alternative to dropping the transaction - /// to roll it back. - pub fn rollback(self) { - self.inner.rollback(); - } -} - -#[cfg(test)] -mod tests { - use crate::{ - Conflict, KeyspaceCreateOptions, OptimisticTxDatabase, OptimisticTxKeyspace, Readable, - }; - use tempfile::TempDir; - use test_log::test; - - struct TestEnv { - db: OptimisticTxDatabase, - tree: OptimisticTxKeyspace, - - #[expect(unused)] - tmpdir: TempDir, - } - - impl TestEnv { - fn seed_hermitage_data(&self) -> crate::Result<()> { - self.tree.insert([1u8], [10u8])?; - self.tree.insert([2u8], [20u8])?; - Ok(()) - } - } - - fn setup() -> Result> { - let tmpdir = tempfile::tempdir()?; - let db = OptimisticTxDatabase::builder(tmpdir.path()).open()?; - - let tree = db.keyspace("foo", KeyspaceCreateOptions::default)?; - - Ok(TestEnv { db, tree, tmpdir }) - } - - // Adapted from https://github.com/al8n/skipdb/issues/10 - #[test] - #[expect(clippy::unwrap_used)] - fn tx_ssi_arthur_1() -> Result<(), Box> { - let env = setup()?; - - let mut tx = env.db.write_tx()?; - tx.insert(env.tree.inner(), "a1", 10u64.to_be_bytes()); - tx.insert(env.tree.inner(), "b1", 100u64.to_be_bytes()); - tx.insert(env.tree.inner(), "b2", 200u64.to_be_bytes()); - tx.commit()??; - - let mut tx1 = env.db.write_tx()?; - let val = tx1 - .range(&env.tree, "a".."b") - .map(|kv| { - let v = kv.value().unwrap(); - - let mut buf = [0u8; 8]; - buf.copy_from_slice(&v); - u64::from_be_bytes(buf) - }) - .sum::(); - tx1.insert(env.tree.inner(), "b3", 10u64.to_be_bytes()); - assert_eq!(10, val); - - let mut tx2 = env.db.write_tx()?; - let val = tx2 - .range(&env.tree, "b".."c") - .map(|kv| { - let v = kv.value().unwrap(); - - let mut buf = [0u8; 8]; - buf.copy_from_slice(&v); - u64::from_be_bytes(buf) - }) - .sum::(); - tx2.insert(env.tree.inner(), "a3", 300u64.to_be_bytes()); - assert_eq!(300, val); - tx2.commit()??; - assert!(matches!(tx1.commit()?, Err(Conflict))); - - let tx3 = env.db.write_tx()?; - let val = tx3 - .iter(&env.tree) - .filter_map(|kv| { - let (k, v) = kv.into_inner().unwrap(); - - if k.starts_with(b"a") { - let mut buf = [0u8; 8]; - buf.copy_from_slice(&v); - Some(u64::from_be_bytes(buf)) - } else { - None - } - }) - .sum::(); - assert_eq!(310, val); - - Ok(()) - } - - // Adapted from https://github.com/al8n/skipdb/issues/10 - #[test] - #[expect(clippy::unwrap_used)] - fn tx_ssi_arthur_2() -> Result<(), Box> { - let env = setup()?; - - let mut tx = env.db.write_tx()?; - tx.insert(env.tree.inner(), "b1", 100u64.to_be_bytes()); - tx.insert(env.tree.inner(), "b2", 200u64.to_be_bytes()); - tx.commit()??; - - let mut tx1 = env.db.write_tx()?; - let val = tx1 - .range(&env.tree, "a".."b") - .map(|kv| { - let v = kv.value().unwrap(); - - let mut buf = [0u8; 8]; - buf.copy_from_slice(&v); - u64::from_be_bytes(buf) - }) - .sum::(); - tx1.insert(env.tree.inner(), "b3", 0u64.to_be_bytes()); - assert_eq!(0, val); - - let mut tx2 = env.db.write_tx()?; - let val = tx2 - .range(&env.tree, "b".."c") - .map(|kv| { - let v = kv.value().unwrap(); - - let mut buf = [0u8; 8]; - buf.copy_from_slice(&v); - u64::from_be_bytes(buf) - }) - .sum::(); - tx2.insert(env.tree.inner(), "a3", 300u64.to_be_bytes()); - assert_eq!(300, val); - tx2.commit()??; - assert!(matches!(tx1.commit()?, Err(Conflict))); - - let tx3 = env.db.write_tx()?; - let val = tx3 - .iter(&env.tree) - .filter_map(|kv| { - let (k, v) = kv.into_inner().unwrap(); - - if k.starts_with(b"a") { - let mut buf = [0u8; 8]; - buf.copy_from_slice(&v); - Some(u64::from_be_bytes(buf)) - } else { - None - } - }) - .sum::(); - assert_eq!(300, val); - - Ok(()) - } - - #[test] - fn tx_ssi_basic() -> Result<(), Box> { - let env = setup()?; - - let mut tx1 = env.db.write_tx()?; - let mut tx2 = env.db.write_tx()?; - - tx1.insert(env.tree.inner(), "hello", "world"); - - tx1.commit()??; - assert!(env.tree.contains_key("hello")?); - - assert_eq!(tx2.get(env.tree.inner(), "hello")?, None); - - tx2.insert(env.tree.inner(), "hello", "world2"); - assert!(matches!(tx2.commit()?, Err(Conflict))); - - let mut tx1 = env.db.write_tx()?; - let mut tx2 = env.db.write_tx()?; - - tx1.iter(&env.tree).next(); - tx2.insert(env.tree.inner(), "hello", "world2"); - - tx1.insert(env.tree.inner(), "hello2", "world1"); - tx1.commit()??; - - tx2.commit()??; - - Ok(()) - } - - #[test] - #[expect(clippy::unwrap_used)] - fn tx_ssi_ww() -> Result<(), Box> { - // https://en.wikipedia.org/wiki/Write%E2%80%93write_conflict - let env = setup()?; - - let mut tx1 = env.db.write_tx()?; - let mut tx2 = env.db.write_tx()?; - - tx1.insert(env.tree.inner(), "a", "a"); - tx2.insert(env.tree.inner(), "b", "c"); - tx1.insert(env.tree.inner(), "b", "b"); - tx1.commit()??; - - tx2.insert(env.tree.inner(), "a", "c"); - - tx2.commit()??; - assert_eq!(b"c", &*env.tree.get("a")?.unwrap()); - assert_eq!(b"c", &*env.tree.get("b")?.unwrap()); - - Ok(()) - } - - #[test] - #[expect(clippy::unwrap_used)] - fn tx_ssi_swap() -> Result<(), Box> { - let env = setup()?; - - env.tree.insert("x", "x")?; - env.tree.insert("y", "y")?; - - let mut tx1 = env.db.write_tx()?; - let mut tx2 = env.db.write_tx()?; - - { - let x = tx1.get(env.tree.inner(), "x")?.unwrap(); - tx1.insert(env.tree.inner(), "y", x); - } - - { - let y = tx2.get(env.tree.inner(), "y")?.unwrap(); - tx2.insert(env.tree.inner(), "x", y); - } - - tx1.commit()??; - assert!(matches!(tx2.commit()?, Err(Conflict))); - - Ok(()) - } - - #[test] - fn tx_ssi_write_cycles() -> Result<(), Box> { - let env = setup()?; - env.seed_hermitage_data()?; - - let mut t1 = env.db.write_tx()?; - let mut t2 = env.db.write_tx()?; - - t1.insert(env.tree.inner(), [1u8], [11u8]); - t2.insert(env.tree.inner(), [1u8], [12u8]); - t1.insert(env.tree.inner(), [2u8], [21u8]); - t1.commit()??; - - assert_eq!(env.tree.get([1u8])?, Some([11u8].into())); - - t2.insert(env.tree.inner(), [2u8], [22u8]); - t2.commit()??; - - assert_eq!(env.tree.get([1u8])?, Some([12u8].into())); - assert_eq!(env.tree.get([2u8])?, Some([22u8].into())); - - Ok(()) - } - - #[test] - fn tx_ssi_aborted_reads() -> Result<(), Box> { - let env = setup()?; - env.seed_hermitage_data()?; - - let mut t1 = env.db.write_tx()?; - let t2 = env.db.write_tx()?; - - t1.insert(env.tree.inner(), [1u8], [101u8]); - - assert_eq!(t2.get(env.tree.inner(), [1u8])?, Some([10u8].into())); - - t1.rollback(); - - assert_eq!(t2.get(env.tree.inner(), [1u8])?, Some([10u8].into())); - - t2.commit()??; - - Ok(()) - } - - #[expect(clippy::unwrap_used)] - #[test] - fn tx_ssi_anti_dependency_cycles() -> Result<(), Box> { - let env = setup()?; - env.seed_hermitage_data()?; - - let mut t1 = env.db.write_tx()?; - { - let mut iter = t1.iter(&env.tree); - assert_eq!( - iter.next().unwrap().into_inner()?, - ([1u8].into(), [10u8].into()), - ); - assert_eq!( - iter.next().unwrap().into_inner()?, - ([2u8].into(), [20u8].into()), - ); - assert!(iter.next().is_none()); - } - - let mut t2 = env.db.write_tx()?; - let new = t2.update_fetch(&env.tree, [2u8], |v| { - v.and_then(|v| v.first().copied()).map(|v| [v + 5].into()) - })?; - assert_eq!(new, Some([25u8].into())); - t2.commit()??; - - let t3 = env.db.write_tx()?; - { - let mut iter = t3.iter(&env.tree); - assert_eq!( - iter.next().unwrap().into_inner()?, - ([1u8].into(), [10u8].into()), - ); - assert_eq!( - iter.next().unwrap().into_inner()?, - ([2u8].into(), [25u8].into()), - ); // changed here - assert!(iter.next().is_none()); - } - - t3.commit()??; - - t1.insert(env.tree.inner(), [1u8], [0u8]); - - assert!(matches!(t1.commit()?, Err(Conflict))); - - Ok(()) - } - - #[test] - fn tx_ssi_update_fetch_update() -> Result<(), Box> { - let env = setup()?; - - let mut t1 = env.db.write_tx()?; - let mut t2 = env.db.write_tx()?; - - let new = t1.update_fetch(env.tree.inner(), "hello", |_| Some("world".into()))?; - assert_eq!(new, Some("world".into())); - let old = t2.fetch_update(env.tree.inner(), "hello", |_| Some("world2".into()))?; - assert_eq!(old, None); - - t1.commit()??; - assert!(matches!(t2.commit()?, Err(Conflict))); - - assert_eq!(env.tree.get("hello")?, Some("world".into())); - - let mut t1 = env.db.write_tx()?; - let mut t2 = env.db.write_tx()?; - - let old = t1.fetch_update(env.tree.inner(), "hello", |_| Some("world3".into()))?; - assert_eq!(old, Some("world".into())); - let new = t2.update_fetch(env.tree.inner(), "hello2", |_| Some("world2".into()))?; - assert_eq!(new, Some("world2".into())); - - t1.commit()??; - t2.commit()??; - - assert_eq!(env.tree.get("hello")?, Some("world3".into())); - assert_eq!(env.tree.get("hello2")?, Some("world2".into())); - - Ok(()) - } - - #[test] - fn tx_ssi_range() -> Result<(), Box> { - let env = setup()?; - - let mut t1 = env.db.write_tx()?; - let mut t2 = env.db.write_tx()?; - - _ = t1.range(&env.tree, "h"..="hello"); - t1.insert(env.tree.inner(), "foo", "bar"); - - // insert a key INSIDE the range read by t1 - t2.insert(env.tree.inner(), "hello", "world"); - - t2.commit()??; - assert!(matches!(t1.commit()?, Err(Conflict))); - - let mut t1 = env.db.write_tx()?; - let mut t2 = env.db.write_tx()?; - - _ = t1.range(&env.tree, "h"..="hello"); - t1.insert(env.tree.inner(), "foo", "bar"); - - // insert a key OUTSIDE the range read by t1 - t2.insert(env.tree.inner(), "hello2", "world"); - - t2.commit()??; - t1.commit()??; - - Ok(()) - } - - #[test] - fn tx_ssi_prefix() -> Result<(), Box> { - let env = setup()?; - - let mut t1 = env.db.write_tx()?; - let mut t2 = env.db.write_tx()?; - - _ = t1.prefix(&env.tree, "hello"); - t1.insert(env.tree.inner(), "foo", "bar"); - - // insert a key MATCHING the prefix read by t1 - t2.insert(env.tree.inner(), "hello", "world"); - - t2.commit()??; - assert!(matches!(t1.commit()?, Err(Conflict))); - - let mut t1 = env.db.write_tx()?; - let mut t2 = env.db.write_tx()?; - - _ = t1.prefix(&env.tree, "hello"); - t1.insert(env.tree.inner(), "foo", "bar"); - - // insert a key NOT MATCHING the range read by t1 - t2.insert(env.tree.inner(), "foobar", "world"); - - t2.commit()??; - t1.commit()??; - - Ok(()) - } -} diff --git a/crates/fjall/src/tx/single_writer/keyspace.rs b/crates/fjall/src/tx/single_writer/keyspace.rs deleted file mode 100644 index ab0ac3179..000000000 --- a/crates/fjall/src/tx/single_writer/keyspace.rs +++ /dev/null @@ -1,469 +0,0 @@ -// Copyright (c) 2024-present, fjall-rs -// This source code is licensed under both the Apache 2.0 and MIT License -// (found in the LICENSE-* files in the repository) - -use crate::{Guard, Keyspace, Readable, SingleWriterTxDatabase}; -use lsm_tree::{UserKey, UserValue}; -use std::path::PathBuf; - -/// Handle to a keyspace of a transactional database -#[derive(Clone)] -pub struct SingleWriterTxKeyspace { - pub(crate) inner: Keyspace, - pub(crate) db: SingleWriterTxDatabase, -} - -impl AsRef for SingleWriterTxKeyspace { - fn as_ref(&self) -> &Keyspace { - self.inner() - } -} - -impl SingleWriterTxKeyspace { - /// Returns the underlying LSM-tree's path. - #[must_use] - pub fn path(&self) -> PathBuf { - self.inner.path().into() - } - - /// Approximates the number of items in the keyspace. - /// - /// For update- or delete-heavy workloads, this value will - /// diverge from the real value, but is a O(1) operation. - /// - /// For insert-only workloads (e.g. logs, time series) - /// this value is reliable. - /// - /// # Examples - /// - /// ``` - /// # use fjall::{SingleWriterTxDatabase, KeyspaceCreateOptions, Readable}; - /// # - /// # let folder = tempfile::tempdir()?; - /// # let db = SingleWriterTxDatabase::builder(folder).open()?; - /// # let tree = db.keyspace("default", KeyspaceCreateOptions::default)?; - /// assert_eq!(tree.approximate_len(), 0); - /// - /// tree.insert("1", "abc")?; - /// assert_eq!(tree.approximate_len(), 1); - /// - /// tree.remove("1")?; - /// // Oops! approximate_len will not be reliable here - /// assert_eq!(tree.approximate_len(), 2); - /// # - /// # Ok::<(), fjall::Error>(()) - /// ``` - #[must_use] - pub fn approximate_len(&self) -> usize { - self.inner.approximate_len() - } - - /// Removes an item and returns its value if it existed. - /// - /// The operation will run wrapped in a transaction. - /// - /// ``` - /// # use fjall::{SingleWriterTxDatabase, KeyspaceCreateOptions, Readable}; - /// # use std::sync::Arc; - /// # - /// # let folder = tempfile::tempdir()?; - /// # let db = SingleWriterTxDatabase::builder(folder).open()?; - /// # let tree = db.keyspace("default", KeyspaceCreateOptions::default)?; - /// tree.insert("a", "abc")?; - /// - /// let taken = tree.take("a")?.unwrap(); - /// assert_eq!(b"abc", &*taken); - /// - /// let item = tree.get("a")?; - /// assert!(item.is_none()); - /// # - /// # Ok::<(), fjall::Error>(()) - /// ``` - /// - /// # Errors - /// - /// Will return `Err` if an IO error occurs. - pub fn take>(&self, key: K) -> crate::Result> { - self.fetch_update(key, |_| None) - } - - /// Atomically updates an item and returns the previous value. - /// - /// Returning `None` removes the item if it existed before. - /// - /// The operation will run wrapped in a transaction. - /// - /// # Examples - /// - /// ``` - /// # use fjall::{SingleWriterTxDatabase, Slice, KeyspaceCreateOptions, Readable}; - /// # use std::sync::Arc; - /// # - /// # let folder = tempfile::tempdir()?; - /// # let db = SingleWriterTxDatabase::builder(folder).open()?; - /// # let tree = db.keyspace("default", KeyspaceCreateOptions::default)?; - /// tree.insert("a", "abc")?; - /// - /// let prev = tree.fetch_update("a", |_| Some(Slice::from(*b"def")))?.unwrap(); - /// assert_eq!(b"abc", &*prev); - /// - /// let item = tree.get("a")?; - /// assert_eq!(Some("def".as_bytes().into()), item); - /// # - /// # Ok::<(), fjall::Error>(()) - /// ``` - /// - /// ``` - /// # use fjall::{SingleWriterTxDatabase, KeyspaceCreateOptions}; - /// # use std::sync::Arc; - /// # - /// # let folder = tempfile::tempdir()?; - /// # let db = SingleWriterTxDatabase::builder(folder).open()?; - /// # let tree = db.keyspace("default", KeyspaceCreateOptions::default)?; - /// tree.insert("a", "abc")?; - /// - /// let prev = tree.fetch_update("a", |_| None)?.unwrap(); - /// assert_eq!(b"abc", &*prev); - /// - /// let item = tree.get("a")?; - /// assert!(item.is_none()); - /// # - /// # Ok::<(), fjall::Error>(()) - /// ``` - /// - /// # Errors - /// - /// Will return `Err` if an IO error occurs. - pub fn fetch_update, F: FnOnce(Option<&UserValue>) -> Option>( - &self, - key: K, - f: F, - ) -> crate::Result> { - let key: UserKey = key.into(); - - let mut tx = self.db.write_tx(); - - let prev = tx.fetch_update(self, key, f)?; - tx.commit()?; - - Ok(prev) - } - - /// Atomically updates an item and returns the new value. - /// - /// Returning `None` removes the item if it existed before. - /// - /// The operation will run wrapped in a transaction. - /// - /// # Examples - /// - /// ``` - /// # use fjall::{SingleWriterTxDatabase, Slice, KeyspaceCreateOptions, Readable}; - /// # use std::sync::Arc; - /// # - /// # let folder = tempfile::tempdir()?; - /// # let db = SingleWriterTxDatabase::builder(folder).open()?; - /// # let tree = db.keyspace("default", KeyspaceCreateOptions::default)?; - /// tree.insert("a", "abc")?; - /// - /// let updated = tree.update_fetch("a", |_| Some(Slice::from(*b"def")))?.unwrap(); - /// assert_eq!(b"def", &*updated); - /// - /// let item = tree.get("a")?; - /// assert_eq!(Some("def".as_bytes().into()), item); - /// # - /// # Ok::<(), fjall::Error>(()) - /// ``` - /// - /// ``` - /// # use fjall::{SingleWriterTxDatabase, KeyspaceCreateOptions, Readable}; - /// # use std::sync::Arc; - /// # - /// # let folder = tempfile::tempdir()?; - /// # let db = SingleWriterTxDatabase::builder(folder).open()?; - /// # let tree = db.keyspace("default", KeyspaceCreateOptions::default)?; - /// tree.insert("a", "abc")?; - /// - /// let updated = tree.update_fetch("a", |_| None)?; - /// assert!(updated.is_none()); - /// - /// let item = tree.get("a")?; - /// assert!(item.is_none()); - /// # - /// # Ok::<(), fjall::Error>(()) - /// ``` - /// - /// # Errors - /// - /// Will return `Err` if an IO error occurs. - pub fn update_fetch, F: FnOnce(Option<&UserValue>) -> Option>( - &self, - key: K, - f: F, - ) -> crate::Result> { - let key = key.into(); - - let mut tx = self.db.write_tx(); - let updated = tx.update_fetch(self, key, f)?; - tx.commit()?; - - Ok(updated) - } - - /// Inserts a key-value pair into the keyspace. - /// - /// Keys may be up to 65536 bytes long, values up to 2^32 bytes. - /// Shorter keys and values result in better performance. - /// - /// If the key already exists, the item will be overwritten. - /// - /// The operation will run wrapped in a transaction. - /// - /// # Examples - /// - /// ``` - /// # use fjall::{SingleWriterTxDatabase, KeyspaceCreateOptions, Readable}; - /// # - /// # let folder = tempfile::tempdir()?; - /// # let db = SingleWriterTxDatabase::builder(folder).open()?; - /// # let tree = db.keyspace("default", KeyspaceCreateOptions::default)?; - /// tree.insert("a", "abc")?; - /// - /// assert!(!db.read_tx().is_empty(&tree)?); - /// # - /// # Ok::<(), fjall::Error>(()) - /// ``` - /// - /// # Errors - /// - /// Will return `Err` if an IO error occurs. - pub fn insert, V: Into>( - &self, - key: K, - value: V, - ) -> crate::Result<()> { - let mut tx = self.db.write_tx(); - tx.insert(self, key, value); - tx.commit()?; - Ok(()) - } - - /// Removes an item from the keyspace. - /// - /// The key may be up to 65536 bytes long. - /// Shorter keys result in better performance. - /// - /// The operation will run wrapped in a transaction. - /// - /// # Examples - /// - /// ``` - /// # use fjall::{SingleWriterTxDatabase, KeyspaceCreateOptions, Readable}; - /// # - /// # let folder = tempfile::tempdir()?; - /// # let db = SingleWriterTxDatabase::builder(folder).open()?; - /// # let tree = db.keyspace("default", KeyspaceCreateOptions::default)?; - /// tree.insert("a", "abc")?; - /// assert!(!db.read_tx().is_empty(&tree)?); - /// - /// tree.remove("a")?; - /// assert!(db.read_tx().is_empty(&tree)?); - /// # - /// # Ok::<(), fjall::Error>(()) - /// ``` - /// - /// # Errors - /// - /// Will return `Err` if an IO error occurs. - pub fn remove>(&self, key: K) -> crate::Result<()> { - let mut tx = self.db.write_tx(); - tx.remove(self, key); - tx.commit()?; - Ok(()) - } - - /// Removes an item from the keyspace, leaving behind a weak tombstone. - /// - /// The tombstone marker of this delete operation will vanish when it - /// collides with its corresponding insertion. - /// This may cause older versions of the value to be resurrected, so it should - /// only be used and preferred in scenarios where a key is only ever written once. - /// - /// The key may be up to 65536 bytes long. - /// Shorter keys result in better performance. - /// - /// The operation will run wrapped in a transaction. - /// - /// # Experimental - /// - /// This function is currently experimental. - /// - /// # Examples - /// - /// ``` - /// # use fjall::{SingleWriterTxDatabase, KeyspaceCreateOptions, Readable}; - /// # - /// # let folder = tempfile::tempdir()?; - /// # let db = SingleWriterTxDatabase::builder(folder).open()?; - /// # let tree = db.keyspace("default", KeyspaceCreateOptions::default)?; - /// tree.insert("a", "abc")?; - /// assert!(!db.read_tx().is_empty(&tree)?); - /// - /// tree.remove_weak("a")?; - /// assert!(db.read_tx().is_empty(&tree)?); - /// # - /// # Ok::<(), fjall::Error>(()) - /// ``` - /// - /// # Errors - /// - /// Will return `Err` if an IO error occurs. - #[doc(hidden)] - pub fn remove_weak>(&self, key: K) -> crate::Result<()> { - let mut tx = self.db.write_tx(); - tx.remove_weak(self, key); - tx.commit()?; - Ok(()) - } - - /// Retrieves an item from the keyspace. - /// - /// The operation will run wrapped in a read snapshot. - /// - /// # Examples - /// - /// ``` - /// # use fjall::{SingleWriterTxDatabase, KeyspaceCreateOptions, Readable}; - /// # - /// # let folder = tempfile::tempdir()?; - /// # let db = SingleWriterTxDatabase::builder(folder).open()?; - /// # let tree = db.keyspace("default", KeyspaceCreateOptions::default)?; - /// tree.insert("a", "my_value")?; - /// - /// let item = tree.get("a")?; - /// assert_eq!(Some("my_value".as_bytes().into()), item); - /// # - /// # Ok::<(), fjall::Error>(()) - /// ``` - /// - /// # Errors - /// - /// Will return `Err` if an IO error occurs. - pub fn get>(&self, key: K) -> crate::Result> { - self.inner.get(key) - } - - /// Retrieves the size of an item from the keyspace. - /// - /// The operation will run wrapped in a read snapshot. - /// - /// # Examples - /// - /// ``` - /// # use fjall::{SingleWriterTxDatabase, KeyspaceCreateOptions, Readable}; - /// # - /// # let folder = tempfile::tempdir()?; - /// # let db = SingleWriterTxDatabase::builder(folder).open()?; - /// # let tree = db.keyspace("default", KeyspaceCreateOptions::default)?; - /// tree.insert("a", "my_value")?; - /// - /// let len = tree.size_of("a")?.unwrap_or_default(); - /// assert_eq!("my_value".len() as u32, len); - /// # - /// # Ok::<(), fjall::Error>(()) - /// ``` - /// - /// # Errors - /// - /// Will return `Err` if an IO error occurs. - pub fn size_of>(&self, key: K) -> crate::Result> { - self.inner.size_of(key) - } - - /// Returns the first key-value pair in the keyspace. - /// The key in this pair is the minimum key in the keyspace. - /// - /// The operation will run wrapped in a read snapshot. - /// - /// # Examples - /// - /// ``` - /// # use fjall::{SingleWriterTxDatabase, KeyspaceCreateOptions, Readable}; - /// # - /// # let folder = tempfile::tempdir()?; - /// # let db = SingleWriterTxDatabase::builder(folder).open()?; - /// # let tree = db.keyspace("default", KeyspaceCreateOptions::default)?; - /// tree.insert("a", "my_value")?; - /// tree.insert("b", "my_value")?; - /// - /// assert_eq!(b"a", &*tree.first_key_value().unwrap().key()?); - /// # - /// # Ok::<(), fjall::Error>(()) - /// ``` - #[must_use] - pub fn first_key_value(&self) -> Option { - let read_tx = self.db.read_tx(); - read_tx.first_key_value(self) - } - - /// Returns the last key-value pair in the keyspace. - /// The key in this pair is the maximum key in the keyspace. - /// - /// The operation will run wrapped in a read snapshot. - /// - /// # Examples - /// - /// ``` - /// # use fjall::{SingleWriterTxDatabase, KeyspaceCreateOptions, Readable}; - /// # - /// # let folder = tempfile::tempdir()?; - /// # let db = SingleWriterTxDatabase::builder(folder).open()?; - /// # let tree = db.keyspace("default", KeyspaceCreateOptions::default)?; - /// tree.insert("a", "my_value")?; - /// tree.insert("b", "my_value")?; - /// - /// assert_eq!(b"b", &*tree.last_key_value().unwrap().key()?); - /// # - /// # Ok::<(), fjall::Error>(()) - /// ``` - #[must_use] - pub fn last_key_value(&self) -> Option { - let read_tx = self.db.read_tx(); - read_tx.last_key_value(self) - } - - /// Returns `true` if the keyspace contains the specified key. - /// - /// The operation will run wrapped in a read snapshot. - /// - /// # Examples - /// - /// ``` - /// # use fjall::{SingleWriterTxDatabase, KeyspaceCreateOptions, Readable}; - /// # - /// # let folder = tempfile::tempdir()?; - /// # let db = SingleWriterTxDatabase::builder(folder).open()?; - /// # let tree = db.keyspace("default", KeyspaceCreateOptions::default)?; - /// tree.insert("a", "my_value")?; - /// - /// assert!(tree.contains_key("a")?); - /// assert!(!tree.contains_key("b")?); - /// # - /// # Ok::<(), fjall::Error>(()) - /// ``` - /// - /// # Errors - /// - /// Will return `Err` if an IO error occurs. - pub fn contains_key>(&self, key: K) -> crate::Result { - self.inner.contains_key(key) - } - - /// Allows access to the inner keyspace handle, allowing to - /// escape from the transactional context. - #[doc(hidden)] - #[must_use] - pub fn inner(&self) -> &Keyspace { - &self.inner - } -} diff --git a/crates/fjall/src/tx/single_writer/mod.rs b/crates/fjall/src/tx/single_writer/mod.rs deleted file mode 100644 index 4aa57234f..000000000 --- a/crates/fjall/src/tx/single_writer/mod.rs +++ /dev/null @@ -1,189 +0,0 @@ -// Copyright (c) 2024-present, fjall-rs -// This source code is licensed under both the Apache 2.0 and MIT License -// (found in the LICENSE-* files in the repository) - -mod keyspace; -mod write_tx; - -use crate::{ - keyspace::KeyspaceKey, Config, Database, KeyspaceCreateOptions, PersistMode, Snapshot, -}; -use std::{ - path::Path, - sync::{Arc, Mutex}, -}; - -pub use keyspace::SingleWriterTxKeyspace; -pub use write_tx::WriteTransaction; - -pub trait Openable { - fn open(config: Config) -> crate::Result - where - Self: Sized; -} - -/// Single-writer transactional database -#[derive(Clone)] -pub struct TxDatabase { - pub(crate) inner: Database, - single_writer_lock: Arc>, -} - -impl Openable for TxDatabase { - fn open(config: Config) -> crate::Result - where - Self: Sized, - { - Ok(Self { - inner: crate::Database::open(config)?, - single_writer_lock: Arc::default(), - }) - } -} - -impl TxDatabase { - /// Creates a new database builder to create or open a database at `path`. - pub fn builder(path: impl AsRef) -> crate::DatabaseBuilder { - crate::DatabaseBuilder::new(path.as_ref()) - } - - #[doc(hidden)] - #[must_use] - pub fn inner(&self) -> &Database { - &self.inner - } - - /// Starts a new writeable transaction. - #[must_use] - #[expect(clippy::missing_panics_doc)] - pub fn write_tx(&self) -> WriteTransaction<'_> { - #[expect(clippy::expect_used)] - let guard = self.single_writer_lock.lock().expect("poisoned tx lock"); - - let mut write_tx = WriteTransaction::new( - self.clone(), - self.inner.supervisor.snapshot_tracker.open(), - guard, - ); - - if !self.inner.config.manual_journal_persist { - write_tx = write_tx.durability(Some(PersistMode::Buffer)); - } - - write_tx - } - - /// Starts a new read-only transaction (a.k.a. [`Snapshot`]). - #[must_use] - pub fn read_tx(&self) -> Snapshot { - self.inner.snapshot() - } - - /// Flushes the active journal. The durability depends on the [`PersistMode`] - /// used. - /// - /// Persisting only affects durability, NOT consistency! Even without flushing - /// data is crash-safe. - /// - /// # Examples - /// - /// ``` - /// # use fjall::{PersistMode, SingleWriterTxDatabase, KeyspaceCreateOptions}; - /// # let folder = tempfile::tempdir()?; - /// let db = SingleWriterTxDatabase::builder(folder).open()?; - /// let items = db.keyspace("my_items", KeyspaceCreateOptions::default)?; - /// - /// items.insert("a", "hello")?; - /// - /// db.persist(PersistMode::SyncAll)?; - /// # - /// # Ok::<_, fjall::Error>(()) - /// ``` - /// - /// # Errors - /// - /// Returns error, if an IO error occurred. - pub fn persist(&self, mode: PersistMode) -> crate::Result<()> { - self.inner.persist(mode) - } - - /// Creates or opens a keyspace. - /// - /// If the keyspace does not yet exist, it will be created configured with `create_options`. - /// Otherwise simply a handle to the existing keyspace will be returned. - /// - /// Keyspace names can be up to 255 characters long and can not be empty. - /// - /// # Errors - /// - /// Returns error, if an IO error occurred. - /// - /// # Panics - /// - /// Panics if the keyspace name is invalid. - pub fn keyspace( - &self, - name: &str, - create_options: impl FnOnce() -> KeyspaceCreateOptions, - ) -> crate::Result { - let keyspace = self.inner.keyspace(name, create_options)?; - - Ok(SingleWriterTxKeyspace { - inner: keyspace, - db: self.clone(), - }) - } - - /// Returns the number of keyspaces. - #[must_use] - pub fn keyspace_count(&self) -> usize { - self.inner.keyspace_count() - } - - /// Gets a list of all keyspace names in the database. - #[must_use] - pub fn list_keyspace_names(&self) -> Vec { - self.inner.list_keyspace_names() - } - - /// Returns `true` if the keyspace with the given name exists. - #[must_use] - pub fn keyspace_exists(&self, name: &str) -> bool { - self.inner.keyspace_exists(name) - } - - /// Returns the current write buffer size (active + sealed memtables). - #[must_use] - pub fn write_buffer_size(&self) -> u64 { - self.inner.write_buffer_size() - } - - /// Returns the number of journal fragments on disk. - #[must_use] - pub fn journal_count(&self) -> usize { - self.inner.journal_count() - } - - /// Returns the disk space usage of the entire database. - /// - /// # Errors - /// - /// Returns error, if an IO error occurred. - pub fn disk_space(&self) -> crate::Result { - self.inner.disk_space() - } - - /// Opens a database in the given directory. - /// - /// # Errors - /// - /// Returns error, if an IO error occurred. - pub fn open(config: Config) -> crate::Result { - let inner = Database::create_or_recover(config)?; - - Ok(Self { - inner, - single_writer_lock: Arc::default(), - }) - } -} diff --git a/crates/fjall/src/tx/single_writer/write_tx.rs b/crates/fjall/src/tx/single_writer/write_tx.rs deleted file mode 100644 index 8e702cc65..000000000 --- a/crates/fjall/src/tx/single_writer/write_tx.rs +++ /dev/null @@ -1,375 +0,0 @@ -// Copyright (c) 2024-present, fjall-rs -// This source code is licensed under both the Apache 2.0 and MIT License -// (found in the LICENSE-* files in the repository) - -use crate::{ - snapshot_nonce::SnapshotNonce, - tx::{single_writer::keyspace::SingleWriterTxKeyspace, write_tx::BaseTransaction}, - Guard, Iter, Keyspace, PersistMode, Readable, SingleWriterTxDatabase, -}; -use lsm_tree::{UserKey, UserValue}; -use std::{ops::RangeBounds, sync::MutexGuard}; - -/// A single-writer (serialized) cross-keyspace transaction -/// -/// Use [`WriteTransaction::commit`] to commit changes to the keyspace(s). -/// -/// Transactions keep a consistent view of the database at the time, -/// meaning old data will not be dropped until it is not referenced by any active transaction. -/// -/// For that reason, you should try to keep transactions short-lived, and make sure they -/// are not held somewhere forever. -#[clippy::has_significant_drop] -pub struct WriteTransaction<'a> { - _guard: MutexGuard<'a, ()>, - inner: BaseTransaction, -} - -impl Readable for WriteTransaction<'_> { - fn get>( - &self, - keyspace: impl AsRef, - key: K, - ) -> crate::Result> { - self.inner.get(keyspace, key) - } - - fn contains_key>( - &self, - keyspace: impl AsRef, - key: K, - ) -> crate::Result { - self.inner.contains_key(keyspace, key) - } - - fn first_key_value(&self, keyspace: impl AsRef) -> Option { - self.inner.first_key_value(keyspace) - } - - fn last_key_value(&self, keyspace: impl AsRef) -> Option { - self.inner.last_key_value(keyspace) - } - - fn size_of>( - &self, - keyspace: impl AsRef, - key: K, - ) -> crate::Result> { - self.inner.size_of(keyspace, key) - } - - fn iter(&self, keyspace: impl AsRef) -> Iter { - self.inner.iter(keyspace) - } - - fn range, R: RangeBounds>( - &self, - keyspace: impl AsRef, - range: R, - ) -> Iter { - self.inner.range(keyspace, range) - } - - fn prefix>(&self, keyspace: impl AsRef, prefix: K) -> Iter { - self.inner.prefix(keyspace, prefix) - } -} - -impl<'tx> WriteTransaction<'tx> { - pub(crate) fn new( - db: SingleWriterTxDatabase, - nonce: SnapshotNonce, - guard: MutexGuard<'tx, ()>, - ) -> Self { - Self { - _guard: guard, - inner: BaseTransaction::new(db.inner, nonce), - } - } - - /// Sets the durability level. - #[must_use] - pub fn durability(mut self, mode: Option) -> Self { - self.inner = self.inner.durability(mode); - self - } - - /// Removes an item and returns its value if it existed. - /// - /// ``` - /// # use fjall::{SingleWriterTxDatabase, KeyspaceCreateOptions, Readable}; - /// # use std::sync::Arc; - /// # - /// # let folder = tempfile::tempdir()?; - /// # let db = SingleWriterTxDatabase::builder(folder).open()?; - /// # let tree = db.keyspace("default", KeyspaceCreateOptions::default)?; - /// tree.insert("a", "abc")?; - /// - /// let mut tx = db.write_tx(); - /// - /// let taken = tx.take(&tree, "a")?.unwrap(); - /// assert_eq!(b"abc", &*taken); - /// tx.commit()?; - /// - /// let item = tree.get("a")?; - /// assert!(item.is_none()); - /// # - /// # Ok::<(), fjall::Error>(()) - /// ``` - /// - /// # Errors - /// - /// Will return `Err` if an IO error occurs. - pub fn take>( - &mut self, - keyspace: &SingleWriterTxKeyspace, - key: K, - ) -> crate::Result> { - self.inner.take(keyspace.inner(), key) - } - - /// Atomically updates an item and returns the new value. - /// - /// Returning `None` removes the item if it existed before. - /// - /// # Examples - /// - /// ``` - /// # use fjall::{SingleWriterTxDatabase, KeyspaceCreateOptions, Slice, Readable}; - /// # - /// # let folder = tempfile::tempdir()?; - /// # let db = SingleWriterTxDatabase::builder(folder).open()?; - /// # let tree = db.keyspace("default", KeyspaceCreateOptions::default)?; - /// tree.insert("a", "abc")?; - /// - /// let mut tx = db.write_tx(); - /// - /// let updated = tx.update_fetch(&tree, "a", |_| Some(Slice::from(*b"def")))?.unwrap(); - /// assert_eq!(b"def", &*updated); - /// tx.commit()?; - /// - /// let item = tree.get("a")?; - /// assert_eq!(Some("def".as_bytes().into()), item); - /// # - /// # Ok::<(), fjall::Error>(()) - /// ``` - /// - /// ``` - /// # use fjall::{SingleWriterTxDatabase, KeyspaceCreateOptions, Readable}; - /// # use std::sync::Arc; - /// # - /// # let folder = tempfile::tempdir()?; - /// # let db = SingleWriterTxDatabase::builder(folder).open()?; - /// # let tree = db.keyspace("default", KeyspaceCreateOptions::default)?; - /// tree.insert("a", "abc")?; - /// - /// let mut tx = db.write_tx(); - /// - /// let updated = tx.update_fetch(&tree, "a", |_| None)?; - /// assert!(updated.is_none()); - /// tx.commit()?; - /// - /// let item = tree.get("a")?; - /// assert!(item.is_none()); - /// # - /// # Ok::<(), fjall::Error>(()) - /// ``` - /// - /// # Errors - /// - /// Will return `Err` if an IO error occurs. - pub fn update_fetch, F: FnOnce(Option<&UserValue>) -> Option>( - &mut self, - keyspace: &SingleWriterTxKeyspace, - key: K, - f: F, - ) -> crate::Result> { - self.inner.update_fetch(keyspace.inner(), key, f) - } - - /// Atomically updates an item and returns the previous value. - /// - /// Returning `None` removes the item if it existed before. - /// - /// # Examples - /// - /// ``` - /// # use fjall::{SingleWriterTxDatabase, KeyspaceCreateOptions, Slice, Readable}; - /// # - /// # let folder = tempfile::tempdir()?; - /// # let db = SingleWriterTxDatabase::builder(folder).open()?; - /// # let tree = db.keyspace("default", KeyspaceCreateOptions::default)?; - /// tree.insert("a", "abc")?; - /// - /// let mut tx = db.write_tx(); - /// - /// let prev = tx.fetch_update(&tree, "a", |_| Some(Slice::from(*b"def")))?.unwrap(); - /// assert_eq!(b"abc", &*prev); - /// tx.commit()?; - /// - /// let item = tree.get("a")?; - /// assert_eq!(Some("def".as_bytes().into()), item); - /// # - /// # Ok::<(), fjall::Error>(()) - /// ``` - /// - /// ``` - /// # use fjall::{SingleWriterTxDatabase, KeyspaceCreateOptions, Readable}; - /// # use std::sync::Arc; - /// # - /// # let folder = tempfile::tempdir()?; - /// # let db = SingleWriterTxDatabase::builder(folder).open()?; - /// # let tree = db.keyspace("default", KeyspaceCreateOptions::default)?; - /// tree.insert("a", "abc")?; - /// - /// let mut tx = db.write_tx(); - /// - /// let prev = tx.fetch_update(&tree, "a", |_| None)?.unwrap(); - /// assert_eq!(b"abc", &*prev); - /// tx.commit()?; - /// - /// let item = tree.get("a")?; - /// assert!(item.is_none()); - /// # - /// # Ok::<(), fjall::Error>(()) - /// ``` - /// - /// # Errors - /// - /// Will return `Err` if an IO error occurs. - pub fn fetch_update, F: FnOnce(Option<&UserValue>) -> Option>( - &mut self, - keyspace: &SingleWriterTxKeyspace, - key: K, - f: F, - ) -> crate::Result> { - self.inner.fetch_update(keyspace.inner(), key, f) - } - - // Inserts a key-value pair into the keyspace. - /// - /// Keys may be up to 65536 bytes long, values up to 2^32 bytes. - /// Shorter keys and values result in better performance. - /// - /// If the key already exists, the item will be overwritten. - /// - /// # Examples - /// - /// ``` - /// # use fjall::{SingleWriterTxDatabase, KeyspaceCreateOptions, Readable}; - /// # - /// # let folder = tempfile::tempdir()?; - /// # let db = SingleWriterTxDatabase::builder(folder).open()?; - /// # let tree = db.keyspace("default", KeyspaceCreateOptions::default)?; - /// tree.insert("a", "previous_value")?; - /// assert_eq!(b"previous_value", &*tree.get("a")?.unwrap()); - /// - /// let mut tx = db.write_tx(); - /// tx.insert(&tree, "a", "new_value"); - /// - /// drop(tx); - /// - /// // Write was not committed - /// assert_eq!(b"previous_value", &*tree.get("a")?.unwrap()); - /// # - /// # Ok::<(), fjall::Error>(()) - /// ``` - pub fn insert, V: Into>( - &mut self, - keyspace: &SingleWriterTxKeyspace, - key: K, - value: V, - ) { - self.inner.insert(keyspace.inner(), key, value); - } - - /// Removes an item from the keyspace. - /// - /// The key may be up to 65536 bytes long. - /// Shorter keys result in better performance. - /// - /// # Examples - /// - /// ``` - /// # use fjall::{SingleWriterTxDatabase, KeyspaceCreateOptions, Readable}; - /// # - /// # let folder = tempfile::tempdir()?; - /// # let db = SingleWriterTxDatabase::builder(folder).open()?; - /// # let tree = db.keyspace("default", KeyspaceCreateOptions::default)?; - /// tree.insert("a", "previous_value")?; - /// assert_eq!(b"previous_value", &*tree.get("a")?.unwrap()); - /// - /// let mut tx = db.write_tx(); - /// tx.remove(&tree, "a"); - /// - /// // Read-your-own-write - /// let item = tx.get(&tree, "a")?; - /// assert_eq!(None, item); - /// - /// drop(tx); - /// - /// // Deletion was not committed - /// assert_eq!(b"previous_value", &*tree.get("a")?.unwrap()); - /// # - /// # Ok::<(), fjall::Error>(()) - /// ``` - pub fn remove>(&mut self, keyspace: &SingleWriterTxKeyspace, key: K) { - self.inner.remove(keyspace.inner(), key); - } - - /// Removes an item from the keyspace, leaving behind a weak tombstone. - /// - /// The tombstone marker of this delete operation will vanish when it - /// collides with its corresponding insertion. - /// This may cause older versions of the value to be resurrected, so it should - /// only be used and preferred in scenarios where a key is only ever written once. - /// - /// # Experimental - /// - /// This function is currently experimental. - /// - /// # Examples - /// - /// ``` - /// # use fjall::{SingleWriterTxDatabase, KeyspaceCreateOptions, Readable}; - /// # - /// # let folder = tempfile::tempdir()?; - /// # let db = SingleWriterTxDatabase::builder(folder).open()?; - /// # let tree = db.keyspace("default", KeyspaceCreateOptions::default)?; - /// tree.insert("a", "previous_value")?; - /// assert_eq!(b"previous_value", &*tree.get("a")?.unwrap()); - /// - /// let mut tx = db.write_tx(); - /// tx.remove_weak(&tree, "a"); - /// - /// // Read-your-own-write - /// let item = tx.get(&tree, "a")?; - /// assert_eq!(None, item); - /// - /// drop(tx); - /// - /// // Deletion was not committed - /// assert_eq!(b"previous_value", &*tree.get("a")?.unwrap()); - /// # - /// # Ok::<(), fjall::Error>(()) - /// ``` - #[doc(hidden)] - pub fn remove_weak>(&mut self, keyspace: &SingleWriterTxKeyspace, key: K) { - self.inner.remove_weak(keyspace.inner(), key); - } - - /// Commits the transaction. - /// - /// # Errors - /// - /// Will return `Err` if an IO error occurs. - pub fn commit(self) -> crate::Result<()> { - self.inner.commit() - } - - /// More explicit alternative to dropping the transaction - /// to roll it back. - pub fn rollback(self) { - self.inner.rollback(); - } -} diff --git a/crates/fjall/src/tx/write_tx.rs b/crates/fjall/src/tx/write_tx.rs deleted file mode 100644 index e339989e9..000000000 --- a/crates/fjall/src/tx/write_tx.rs +++ /dev/null @@ -1,404 +0,0 @@ -// Copyright (c) 2024-present, fjall-rs -// This source code is licensed under both the Apache 2.0 and MIT License -// (found in the LICENSE-* files in the repository) - -use crate::{ - batch::item::Item, snapshot_nonce::SnapshotNonce, Database, Guard, HashMap, Iter, Keyspace, - OwnedWriteBatch, PersistMode, Readable, -}; -use lsm_tree::{AbstractTree, InternalValue, Memtable, SeqNo, UserKey, UserValue}; -use std::{ops::RangeBounds, sync::Arc}; - -fn ignore_tombstone_value(item: InternalValue) -> Option { - if item.is_tombstone() { - None - } else { - Some(item) - } -} - -pub(super) struct BaseTransaction { - /// Database to work with - pub(super) db: Database, - - /// Ephemeral transaction changes - /// - /// Used for RYOW (read-your-own-writes) - pub(crate) memtables: HashMap>, - - /// The snapshot, for repeatable reads - pub(crate) nonce: SnapshotNonce, - - /// Durability level used, see [`PersistMode`]. - pub(crate) durability: Option, - - /// Current sequence number - /// - /// The sequence number starts at 0b1000...0000 and is incremented with each write. - /// - /// This ensures that writes within the transaction are always newer than any existing data. - pub(crate) seqno: SeqNo, -} - -impl Readable for BaseTransaction { - fn get>( - &self, - keyspace: impl AsRef, - key: K, - ) -> crate::Result> { - let keyspace = keyspace.as_ref(); - let key = key.as_ref(); - - if let Some(memtable) = self.memtables.get(keyspace) { - if let Some(item) = memtable.get(key, SeqNo::MAX) { - return Ok(ignore_tombstone_value(item).map(|x| x.value)); - } - } - - let res = keyspace.tree.get(key, self.nonce.instant)?; - - Ok(res) - } - - fn contains_key>( - &self, - keyspace: impl AsRef, - key: K, - ) -> crate::Result { - let keyspace = keyspace.as_ref(); - let key = key.as_ref(); - - if let Some(memtable) = self.memtables.get(keyspace) { - if let Some(item) = memtable.get(key, SeqNo::MAX) { - return Ok(!item.key.is_tombstone()); - } - } - - let contains = keyspace.tree.contains_key(key, self.nonce.instant)?; - - Ok(contains) - } - - fn first_key_value(&self, keyspace: impl AsRef) -> Option { - self.iter(keyspace.as_ref()).next() - } - - fn last_key_value(&self, keyspace: impl AsRef) -> Option { - self.iter(keyspace.as_ref()).next_back() - } - - fn size_of>( - &self, - keyspace: impl AsRef, - key: K, - ) -> crate::Result> { - let keyspace = keyspace.as_ref(); - let key = key.as_ref(); - - if let Some(memtable) = self.memtables.get(keyspace) { - if let Some(item) = memtable.get(key, SeqNo::MAX) { - // NOTE: Values are limited to u32 in lsm-tree - #[expect(clippy::cast_possible_truncation)] - return Ok(ignore_tombstone_value(item).map(|x| x.value.len() as u32)); - } - } - - let res = keyspace.tree.size_of(key, self.nonce.instant)?; - - Ok(res) - } - - fn iter(&self, keyspace: impl AsRef) -> Iter { - let keyspace = keyspace.as_ref(); - - let iter = keyspace.tree.iter( - self.nonce.instant, - self.memtables - .get(keyspace) - .cloned() - .map(|mt| (mt, self.seqno)), - ); - - Iter::new(self.nonce.clone(), iter) - } - - fn range, R: RangeBounds>( - &self, - keyspace: impl AsRef, - range: R, - ) -> Iter { - let keyspace = keyspace.as_ref(); - - let iter = keyspace.tree.range( - range, - self.nonce.instant, - self.memtables - .get(keyspace) - .cloned() - .map(|mt| (mt, self.seqno)), - ); - - Iter::new(self.nonce.clone(), iter) - } - - fn prefix>(&self, keyspace: impl AsRef, prefix: K) -> Iter { - let keyspace = keyspace.as_ref(); - - let iter = keyspace.tree.prefix( - prefix, - self.nonce.instant, - self.memtables - .get(keyspace) - .cloned() - .map(|mt| (mt, self.seqno)), - ); - - Iter::new(self.nonce.clone(), iter) - } -} - -impl BaseTransaction { - pub(crate) fn new(db: Database, nonce: SnapshotNonce) -> Self { - Self { - db, - memtables: HashMap::default(), - nonce, - durability: None, - seqno: 0x8000_0000_0000_0000, - } - } - - /// Sets the durability level. - #[must_use] - pub(super) fn durability(mut self, mode: Option) -> Self { - self.durability = mode; - self - } - - /// Removes an item and returns its value if it existed. - /// - /// # Errors - /// - /// Will return `Err` if an IO error occurs. - pub(super) fn take>( - &mut self, - keyspace: &Keyspace, - key: K, - ) -> crate::Result> { - self.fetch_update(keyspace, key, |_| None) - } - - /// Atomically updates an item and returns the new value. - /// - /// Returning `None` removes the item if it existed before. - /// - /// # Errors - /// - /// Will return `Err` if an IO error occurs. - pub(super) fn update_fetch< - K: Into, - F: FnOnce(Option<&UserValue>) -> Option, - >( - &mut self, - keyspace: &Keyspace, - key: K, - f: F, - ) -> crate::Result> { - let key = key.into(); - let prev = self.get(keyspace, &key)?; - let updated = f(prev.as_ref()); - - if let Some(value) = updated.clone() { - // NOTE: Skip insert if the value hasn't changed - if prev.as_ref() != Some(&value) { - self.insert(keyspace, key, value); - } - } else if prev.is_some() { - self.remove(keyspace, key); - } - - Ok(updated) - } - - /// Atomically updates an item and returns the previous value. - /// - /// Returning `None` removes the item if it existed before. - /// - /// # Errors - /// - /// Will return `Err` if an IO error occurs. - pub(super) fn fetch_update< - K: Into, - F: FnOnce(Option<&UserValue>) -> Option, - >( - &mut self, - keyspace: &Keyspace, - key: K, - f: F, - ) -> crate::Result> { - let key = key.into(); - let prev = self.get(keyspace, &key)?; - let updated = f(prev.as_ref()); - - if let Some(value) = updated { - // NOTE: Skip insert if the value hasn't changed - if prev.as_ref() != Some(&value) { - self.insert(keyspace, key, value); - } - } else if prev.is_some() { - self.remove(keyspace, key); - } - - Ok(prev) - } - - /// Inserts a key-value pair into the keyspace. - /// - /// Keys may be up to 65536 bytes long, values up to 2^32 bytes. - /// Shorter keys and values result in better performance. - /// - /// If the key already exists, the item will be overwritten. - pub(super) fn insert, V: Into>( - &mut self, - keyspace: &Keyspace, - key: K, - value: V, - ) { - self.memtables - .entry(keyspace.clone()) - .or_insert_with(|| Arc::new(Memtable::new(0))) - .insert(lsm_tree::InternalValue::from_components( - key, - value, - self.seqno, - lsm_tree::ValueType::Value, - )); - - self.seqno += 1; - } - - /// Removes an item from the keyspace. - /// - /// The key may be up to 65536 bytes long. - /// Shorter keys result in better performance. - pub(super) fn remove>(&mut self, keyspace: &Keyspace, key: K) { - self.memtables - .entry(keyspace.clone()) - .or_insert_with(|| Arc::new(Memtable::new(0))) - .insert(lsm_tree::InternalValue::new_tombstone(key, self.seqno)); - - self.seqno += 1; - } - - /// Removes an item from the keyspace, leaving behind a weak tombstone. - /// - /// The tombstone marker of this delete operation will vanish when it - /// collides with its corresponding insertion. - /// This may cause older versions of the value to be resurrected, so it should - /// only be used and preferred in scenarios where a key is only ever written once. - /// - /// The key may be up to 65536 bytes long. - /// Shorter keys result in better performance. - pub(super) fn remove_weak>(&mut self, keyspace: &Keyspace, key: K) { - self.memtables - .entry(keyspace.clone()) - .or_insert_with(|| Arc::new(Memtable::new(0))) - .insert(lsm_tree::InternalValue::new_weak_tombstone(key, self.seqno)); - - self.seqno += 1; - } - - /// Commits the transaction. - /// - /// # Errors - /// - /// Will return `Err` if an IO error occurs. - pub(super) fn commit(self) -> crate::Result<()> { - // skip all the logic if no keys were written to - if self.memtables.is_empty() { - return Ok(()); - } - - // TODO: instead of using batch, write batch::commit as a generic function that takes - // a impl Iterator - // that way, we don't have to move the memtable(s) into the batch first to commit - let mut batch = OwnedWriteBatch::new(self.db).durability(self.durability); - - for (keyspace, memtable) in self.memtables { - let mut prev_key: Option = None; - - for item in memtable.iter() { - if let Some(prev_key) = &prev_key { - if item.key.user_key == prev_key { - continue; - } - } - - batch.data.push(Item::new( - keyspace.clone(), - item.key.user_key.clone(), - item.value.clone(), - item.key.value_type, - )); - - prev_key = Some(item.key.user_key.clone()); - } - } - - batch.commit()?; - - Ok(()) - } - - /// More explicit alternative to dropping the transaction - /// to roll it back. - #[expect(clippy::unused_self)] - pub(super) fn rollback(self) {} -} - -#[cfg(test)] -mod tests { - use crate::{ - tx::{single_writer::SingleWriterTxKeyspace, write_tx::BaseTransaction}, - KeyspaceCreateOptions, SingleWriterTxDatabase, - }; - use tempfile::TempDir; - - struct TestEnv { - db: SingleWriterTxDatabase, - tree: SingleWriterTxKeyspace, - - #[expect(unused)] - tmpdir: TempDir, - } - - fn setup() -> Result> { - let tmpdir: TempDir = tempfile::tempdir()?; - let db = SingleWriterTxDatabase::builder(tmpdir.path()).open()?; - - let tree = db.keyspace("foo", KeyspaceCreateOptions::default)?; - - Ok(TestEnv { db, tree, tmpdir }) - } - - #[test] - fn update_fetch() -> Result<(), Box> { - let env = setup()?; - - env.tree.insert([2u8], [20u8])?; - - let mut tx = BaseTransaction::new( - env.db.inner.clone(), - env.db.inner().supervisor.snapshot_tracker.open(), - ); - - let new = tx.update_fetch(env.tree.inner(), [2u8], |v| { - v.and_then(|v| v.first().copied()).map(|v| [v + 5].into()) - })?; - - assert_eq!(new, Some([25u8].into())); - tx.commit()?; - - Ok(()) - } -} diff --git a/crates/fjall/src/version.rs b/crates/fjall/src/version.rs index ac2113b03..af946348b 100644 --- a/crates/fjall/src/version.rs +++ b/crates/fjall/src/version.rs @@ -18,6 +18,9 @@ pub enum FormatVersion { /// Fixed-width key/value lengths in LSM table data blocks V4, + + /// 32-bit LSM tree and table IDs + V5, } impl std::fmt::Display for FormatVersion { @@ -33,6 +36,7 @@ impl From for u8 { FormatVersion::V2 => 2, FormatVersion::V3 => 3, FormatVersion::V4 => 4, + FormatVersion::V5 => 5, } } } @@ -46,6 +50,7 @@ impl TryFrom for FormatVersion { 2 => Ok(Self::V2), 3 => Ok(Self::V3), 4 => Ok(Self::V4), + 5 => Ok(Self::V5), _ => Err(()), } } @@ -110,13 +115,13 @@ mod tests { } #[test] - pub fn version_serialize_and_deserialize_4() -> crate::Result<()> { + pub fn version_serialize_and_deserialize_5() -> crate::Result<()> { let mut bytes = vec![]; - FormatVersion::V4.write_file_header(&mut bytes)?; - assert_eq!(bytes, &[b'F', b'J', b'L', 4]); + FormatVersion::V5.write_file_header(&mut bytes)?; + assert_eq!(bytes, &[b'F', b'J', b'L', 5]); assert_eq!( FormatVersion::parse_file_header(&bytes), - Some(FormatVersion::V4), + Some(FormatVersion::V5), ); Ok(()) } diff --git a/crates/fjall/src/worker_pool.rs b/crates/fjall/src/worker_pool.rs index a8eecb220..dcf836d8a 100644 --- a/crates/fjall/src/worker_pool.rs +++ b/crates/fjall/src/worker_pool.rs @@ -3,13 +3,13 @@ // (found in the LICENSE-* files in the repository) use crate::{ - compaction::worker::run as run_compaction, flush::worker::run as run_flush, poison::PoisonDart, - stats::Stats, supervisor::Supervisor, Keyspace, + Keyspace, compaction::worker::run as run_compaction, flush::worker::run as run_flush, + poison::PoisonDart, stats::Stats, supervisor::Supervisor, }; use lsm_tree::MemtableId; use std::{ borrow::Cow, - sync::{atomic::AtomicUsize, Arc, Mutex}, + sync::{Arc, Mutex, atomic::AtomicUsize}, thread::JoinHandle, }; diff --git a/crates/fjall/src/write_buffer_manager.rs b/crates/fjall/src/write_buffer_manager.rs index d06d9065a..3c6a2a569 100644 --- a/crates/fjall/src/write_buffer_manager.rs +++ b/crates/fjall/src/write_buffer_manager.rs @@ -2,7 +2,7 @@ // This source code is licensed under both the Apache 2.0 and MIT License // (found in the LICENSE-* files in the repository) -use std::sync::{atomic::AtomicU64, Arc}; +use std::sync::{Arc, atomic::AtomicU64}; /// Keeps track of the size of the database's write buffer #[derive(Clone, Default, Debug)] diff --git a/crates/fjall/test_fixture/v1_keyspace/partitions/a/config b/crates/fjall/test_fixture/v1_keyspace/partitions/a/config deleted file mode 100644 index 61e5bd120bd4eff9fc05bda9d16a304c1be34a9d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16 XcmZ?s^6_zYb2nsQWME(rU|c$=tIj675%n){E;HYq&v)Ov;hC$I%1Y&Ci9Oi4 z^a%RF%oYQL{6&kQNK%7OKaSoO}Sr=2#Iprx;$F_=4`lwxjqAkLC^p&Plcp&{5xCUtwyICV9YVD_51rAblJ-2tTx#ObQ2_>`;Ykr*hTAYip4YHBu^NT?EaW~OBkj;X z1`Wfw3Ll0^ZjqcSOZz3=ky|?A9^KGW+@?unh&I=}#zV2R07ug!#Jj}Lxmq{EE5%|Q z#|4^OM>SEf)OIoE+H#kgi{gIuVr%>1`hrwwGpZxe-m^*y>vkTuU_umdFaq>ex_e66DbIF$UG_eu(!{?0ssDeK?0I9o GQu+m3qJv8S diff --git a/crates/fjall/test_fixture/v1_keyspace/partitions/a/version b/crates/fjall/test_fixture/v1_keyspace/partitions/a/version deleted file mode 100644 index 2073e5dd0ed44d0784b77c9a924a0958da4139c7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5 McmZ?s@?l^E00VFU-T(jq diff --git a/crates/fjall/test_fixture/v1_keyspace/partitions/b/config b/crates/fjall/test_fixture/v1_keyspace/partitions/b/config deleted file mode 100644 index 61e5bd120bd4eff9fc05bda9d16a304c1be34a9d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16 XcmZ?s^6_zYb2nsQWME(rU|JLVOarxq29GkoUY0tzJqmGOmSq!y*B7c1oDE9B>;$}t3R17%Wd87BCbmMD~f zB;8;Z%Wt~f0n`EmqWnPC2M~`DCIr+8SIyaKG7m1!z<{Z`5~@1b$;}X^FaXLB44EqH zZtA~#O3YV~fx*5mFeM1P*d$hbdOGPWR0j(h4Koo&S3(&t85jgWOh&jtzz_oiZcxB6 cra*Zx^~<6Bt56z7{eUrs3k?eAkRTsJ09!&iy#N3J diff --git a/crates/fjall/test_fixture/v1_keyspace/partitions/b/version b/crates/fjall/test_fixture/v1_keyspace/partitions/b/version deleted file mode 100644 index 2073e5dd0ed44d0784b77c9a924a0958da4139c7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5 McmZ?s@?l^E00VFU-T(jq diff --git a/crates/fjall/test_fixture/v1_keyspace/partitions/c/config b/crates/fjall/test_fixture/v1_keyspace/partitions/c/config deleted file mode 100644 index 61e5bd120bd4eff9fc05bda9d16a304c1be34a9d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16 XcmZ?s^6_zYb2nsQWME(rU|c$=tIj675%n){E;HYq&v)Ov;hC$I%1Y&Ci9Oi4 z^a%RF%oYQL{6&kQNK%7OKaSoO}Sr=2#Iprx;$F_=4`lwxjqAkLC^p&Plcp&{5xCUtwyICV9YVD_51rAblJ-2tTx#ObQ2_>`;Ykr*hTAYip4YHBu^NT?EaW~OBkj;X z1`Wfw3Ll0^ZjqcSOZz3=ky|?A9^KGW+@?unh&I=}#zV2R07ug!#Jj}Lxmq{EE5%|Q z#|4^OM>SEf)OIoE+H#kgi{gIuVr%>1`hrwwGpZxe-m^*y>vkTuU_umdFaq>ex_e66DbIF$UG_eu(!{?0ssDeK?0I9o GQu+m3qJv8S diff --git a/crates/fjall/test_fixture/v1_keyspace_corrupt_journal/partitions/a/version b/crates/fjall/test_fixture/v1_keyspace_corrupt_journal/partitions/a/version deleted file mode 100644 index 2073e5dd0ed44d0784b77c9a924a0958da4139c7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5 McmZ?s@?l^E00VFU-T(jq diff --git a/crates/fjall/test_fixture/v1_keyspace_corrupt_journal/partitions/b/config b/crates/fjall/test_fixture/v1_keyspace_corrupt_journal/partitions/b/config deleted file mode 100644 index 61e5bd120bd4eff9fc05bda9d16a304c1be34a9d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16 XcmZ?s^6_zYb2nsQWME(rU|JLVOarxq29GkoUY0tzJqmGOmSq!y*B7c1oDE9B>;$}t3R17%Wd87BCbmMD~f zB;8;Z%Wt~f0n`EmqWnPC2M~`DCIr+8SIyaKG7m1!z<{Z`5~@1b$;}X^FaXLB44EqH zZtA~#O3YV~fx*5mFeM1P*d$hbdOGPWR0j(h4Koo&S3(&t85jgWOh&jtzz_oiZcxB6 cra*Zx^~<6Bt56z7{eUrs3k?eAkRTsJ09!&iy#N3J diff --git a/crates/fjall/test_fixture/v1_keyspace_corrupt_journal/partitions/b/version b/crates/fjall/test_fixture/v1_keyspace_corrupt_journal/partitions/b/version deleted file mode 100644 index 2073e5dd0ed44d0784b77c9a924a0958da4139c7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5 McmZ?s@?l^E00VFU-T(jq diff --git a/crates/fjall/test_fixture/v1_keyspace_corrupt_journal/partitions/c/config b/crates/fjall/test_fixture/v1_keyspace_corrupt_journal/partitions/c/config deleted file mode 100644 index 61e5bd120bd4eff9fc05bda9d16a304c1be34a9d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16 XcmZ?s^6_zYb2nsQWME(rU|WP%A{BgsfIl8huH$w)Gij3gt;NWcBRp`0iu z%87EKoG2&CiE^TxC@0E^zkfD|tSl?b%CfSoEGx^(va+l!E6d8V@~qGD(--sweL-K) z7xV>vL0`}p^aXuEU(gpcBh5%N(u_1C%}6uSj5MR^W)v^Q^_SbV%d7I~EsLeoTZ)rl z`Wu5kPl9oE@?0Eu!`+y5W|C*av*FnoogZc|*bDZ8y)b>UAQ{L&1~QO=gd`*(2}wvo z5|WUFBqSjTNk~Exl8}TXBq0e&NJ0{lkc1>8Aqh!HLK2dYgd`*(2}wvo5|WUFBqSjT zNk~Exl8}TXBq0e&NJ0{lkc1>8Aqh!HLK2dYgd`*(2}wvo5|WUFBqSjTNk~Exl8}TX sBq0e&NJ0{lkc1>8Aqh!HLK2dYgd`*(3ICgfaU8upRySUEho`H40!(|$rvLx| diff --git a/crates/fjall/test_fixture/v2_keyspace/partitions/default1/config b/crates/fjall/test_fixture/v2_keyspace/partitions/default1/config deleted file mode 100644 index 1fe458313565a33761516e13c30a898c07a3c7d7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 30 bcmZ?s@?m0UU}OLR0Wb!#xfobDSU@5G9L@nE diff --git a/crates/fjall/test_fixture/v2_keyspace/partitions/default1/levels b/crates/fjall/test_fixture/v2_keyspace/partitions/default1/levels deleted file mode 100644 index 23ecc55259e97ef90d339c668eb4de3ffd005aff..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 41 TcmeYX_GMycU|?VbV!QwVCU^k% diff --git a/crates/fjall/test_fixture/v2_keyspace/partitions/default1/manifest b/crates/fjall/test_fixture/v2_keyspace/partitions/default1/manifest deleted file mode 100644 index 969bbb4ca73b53322008098eee075a57d6f70e73..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7 OcmeYX_GMyVU}AMbL_{r8R4*}-guaDJIYhnNm=(#(_F-}}wM zVQDV2pfhtM`ow(1(^SGKHHmwSHC!?lcB)iA4U>F#Z%aJ2E13(=$FW0?qd1xAygWh;A5>&MMY*GxCoZ{C4904%rt1n{{~ zR~Y&oPoWd}qJtsFScY7Av?-xqfM&|+x&WptQ&FA_V}FNxZ*kOy#%OArM@|}aOSPd!I;MN-eSM6 zhGk$b6=kmZKr~iOuY{?`1uD;082b?|oj|5>33YQ~l!V0&0+hrG6~Wz_S|Gm)HncJ` nEyJuGGz`_6kEfxVS diff --git a/crates/fjall/test_fixture/v2_keyspace/partitions/default2/levels b/crates/fjall/test_fixture/v2_keyspace/partitions/default2/levels deleted file mode 100644 index 23ecc55259e97ef90d339c668eb4de3ffd005aff..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 41 TcmeYX_GMycU|?VbV!QwVCU^k% diff --git a/crates/fjall/test_fixture/v2_keyspace/partitions/default2/manifest b/crates/fjall/test_fixture/v2_keyspace/partitions/default2/manifest deleted file mode 100644 index 445fd09222e5aa0f191025b0b5bba2d4bb9a6b3d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7 OcmeYX_GMyZU2LN3~C?fy> diff --git a/crates/fjall/test_fixture/v2_keyspace/version b/crates/fjall/test_fixture/v2_keyspace/version deleted file mode 100644 index 29e6c1aa3..000000000 --- a/crates/fjall/test_fixture/v2_keyspace/version +++ /dev/null @@ -1 +0,0 @@ -FJL \ No newline at end of file diff --git a/crates/fjall/test_fixture/v2_keyspace_corrupt_journal/journals/2 b/crates/fjall/test_fixture/v2_keyspace_corrupt_journal/journals/2 deleted file mode 100644 index 908e21c951aa6d901390db0db101fdd79dd213c5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16777216 zcmeF)yGkoj6oBEK42YK}P*8Ru*hXt>5fMwpa?q^~#01Plu(A+rYy=yh!6y(4!BQ-2 z?0pp{iWtXBW9cWH<^)3cnECfwo}<$UAvCH#gRjvLnqhQ1?d1J-IUgFS`n##kZa;2y z_fyQ<#bJs?Czg9DE~mWAj@Q>Vo))ifmsVDrgZFrUpRwBe>{PGK998d=7oE8>ZXI-c zy`r-ldu3jx*e*J$mrdV|TwQKIpVi-MeE7W)>r>aWA+R+zal7_Z>zL^gs{vKo35?ucL{YsEL}WiJCb4{eI_ybHTabTyQQp7n}>9 z|1I3v=xlU0Ivbsh&PHdWv$6hv<;(}>1M`9Tzyu!-I{aK58Z>@-W|C{ewc*-$dw!UE!M)&Ka4&p)vY;|3 zgEAi;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?v_l61 z00000g8Z!qxezj7z<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r Q3>YwAz<>b*1`MnR0?}N{hX4Qo diff --git a/crates/fjall/test_fixture/v2_keyspace_corrupt_journal/partitions/default1/config b/crates/fjall/test_fixture/v2_keyspace_corrupt_journal/partitions/default1/config deleted file mode 100644 index 1fe458313565a33761516e13c30a898c07a3c7d7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 30 bcmZ?s@?m0UU}OLR0Wb!#xfobDSU@5G9L@nE diff --git a/crates/fjall/test_fixture/v2_keyspace_corrupt_journal/partitions/default1/levels b/crates/fjall/test_fixture/v2_keyspace_corrupt_journal/partitions/default1/levels deleted file mode 100644 index 23ecc55259e97ef90d339c668eb4de3ffd005aff..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 41 TcmeYX_GMycU|?VbV!QwVCU^k% diff --git a/crates/fjall/test_fixture/v2_keyspace_corrupt_journal/partitions/default1/manifest b/crates/fjall/test_fixture/v2_keyspace_corrupt_journal/partitions/default1/manifest deleted file mode 100644 index 969bbb4ca73b53322008098eee075a57d6f70e73..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7 OcmeYX_GMyVU}AMbL_{r8R4*}-guaDJIYhnNm=(#(_F-}}wM zVQDV2pfhtM`ow(1(^SGKHHmwSHC!?lcB)iA4U>F#Z%aJ2E13(=$FW0?qd1xAygWh;A5>&MMY*GxCoZ{C4904%rt1n{{~ zR~Y&oPoWd}qJtsFScY7Av?-xqfM&|+x&WptQ&FA_V}FNxZ*kOy#%OArM@|}aOSPd!I;MN-eSM6 zhGk$b6=kmZKr~iOuY{?`1uD;082b?|oj|5>33YQ~l!V0&0+hrG6~Wz_S|Gm)HncJ` nEyJuGGz`_6kEfxVS diff --git a/crates/fjall/test_fixture/v2_keyspace_corrupt_journal/partitions/default2/levels b/crates/fjall/test_fixture/v2_keyspace_corrupt_journal/partitions/default2/levels deleted file mode 100644 index 23ecc55259e97ef90d339c668eb4de3ffd005aff..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 41 TcmeYX_GMycU|?VbV!QwVCU^k% diff --git a/crates/fjall/test_fixture/v2_keyspace_corrupt_journal/partitions/default2/manifest b/crates/fjall/test_fixture/v2_keyspace_corrupt_journal/partitions/default2/manifest deleted file mode 100644 index 445fd09222e5aa0f191025b0b5bba2d4bb9a6b3d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7 OcmeYX_GMyZU2LN3~C?fy> diff --git a/crates/fjall/test_fixture/v2_keyspace_corrupt_journal/version b/crates/fjall/test_fixture/v2_keyspace_corrupt_journal/version deleted file mode 100644 index 29e6c1aa3..000000000 --- a/crates/fjall/test_fixture/v2_keyspace_corrupt_journal/version +++ /dev/null @@ -1 +0,0 @@ -FJL \ No newline at end of file diff --git a/crates/fjall/test_fixture/v2_sealed_journal_shenanigans/journals/0.sealed b/crates/fjall/test_fixture/v2_sealed_journal_shenanigans/journals/0.sealed deleted file mode 100644 index 4a0a05eadc06aebb20dc3409b564d248b99689a0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 46 tcmZQ%U|?VbVklr@U{6U+ODxSPVPH%IiZLcKmmTq%5|QCC$<51$2>^L02uJ_` diff --git a/crates/fjall/test_fixture/v2_sealed_journal_shenanigans/journals/1.sealed b/crates/fjall/test_fixture/v2_sealed_journal_shenanigans/journals/1.sealed deleted file mode 100644 index 88a33b123a0057b1b62e24b31e71b82f478cfe7b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 46 ucmZQ%U|?VbVh{k*ObqNPscDI&IVB8?Ng$yl=A3 fjall::Result<()> { Ok(()) } -#[test] -fn blob_batch_simple() -> fjall::Result<()> { - let folder = tempfile::tempdir()?; - - let db = Database::builder(&folder).open()?; - let tree = db.keyspace("default", || { - KeyspaceCreateOptions::default().with_kv_separation(Some(KvSeparationOptions::default())) - })?; - - let blob = "oxygen".repeat(128_000); - - let mut batch = db.batch(); - - assert_eq!(tree.len()?, 0); - batch.insert(&tree, "1", &blob); - batch.insert(&tree, "3", "abc"); - batch.insert(&tree, "5", "abc"); - assert_eq!(tree.len()?, 0); - - batch.commit()?; - assert_eq!(tree.len()?, 3); - - assert_eq!(&*tree.get("1")?.unwrap(), blob.as_bytes()); - - Ok(()) -} - #[test] fn batch_multi_keys() -> fjall::Result<()> { let folder = tempfile::tempdir()?; diff --git a/crates/fjall/tests/blob_kv_simple.rs b/crates/fjall/tests/blob_kv_simple.rs deleted file mode 100644 index f83a7ea84..000000000 --- a/crates/fjall/tests/blob_kv_simple.rs +++ /dev/null @@ -1,63 +0,0 @@ -use fjall::{Database, KeyspaceCreateOptions, KvSeparationOptions}; -use lsm_tree::AbstractTree; -use test_log::test; - -#[test] -fn blob_kv_simple() -> fjall::Result<()> { - let folder = tempfile::tempdir()?; - - let db = Database::builder(&folder).open()?; - let tree = db.keyspace("default", || { - KeyspaceCreateOptions::default().with_kv_separation(Some(KvSeparationOptions::default())) - })?; - - assert_eq!(tree.len()?, 0); - tree.insert("1", "oxygen".repeat(1_000_000))?; - tree.insert("3", "abc")?; - tree.insert("5", "abc")?; - assert_eq!(tree.len()?, 3); - - tree.rotate_memtable_and_wait()?; - - if let fjall::AnyTree::Blob(tree) = &tree.tree { - assert!(tree.index.disk_space() < 1_000); - - // NOTE: The data is compressed quite well, so it's way less than 1M - assert!(tree.disk_space() > 5_000); - - assert_eq!(1, tree.blob_file_count()); - } else { - unreachable!(); - } - - Ok(()) -} - -#[test] -fn blob_kv_simple_recovery() -> fjall::Result<()> { - let folder = tempfile::tempdir()?; - - { - let db = Database::builder(&folder).open()?; - let tree = db.keyspace("default", || { - KeyspaceCreateOptions::default() - .with_kv_separation(Some(KvSeparationOptions::default())) - })?; - - assert_eq!(tree.len()?, 0); - tree.insert("1", "oxygen".repeat(1_000_000))?; - tree.insert("3", "abc")?; - tree.insert("5", "abc")?; - assert_eq!(tree.len()?, 3); - - tree.rotate_memtable_and_wait()?; - } - - { - let db = Database::builder(&folder).open()?; - let tree = db.keyspace("default", KeyspaceCreateOptions::default)?; - assert_eq!(tree.len()?, 3); - } - - Ok(()) -} diff --git a/crates/fjall/tests/clear_dirty_read.rs b/crates/fjall/tests/clear_dirty_read.rs deleted file mode 100644 index 4f58ae390..000000000 --- a/crates/fjall/tests/clear_dirty_read.rs +++ /dev/null @@ -1,35 +0,0 @@ -use fjall::Readable; - -#[test_log::test] -fn clear_dirty_read() -> fjall::Result<()> { - use fjall::{KeyspaceCreateOptions, SingleWriterTxDatabase}; - - let folder = tempfile::tempdir()?; - - let db = SingleWriterTxDatabase::builder(&folder).open()?; - - let tree = db.keyspace("default", KeyspaceCreateOptions::default)?; - - assert!(!tree.contains_key("a")?); - - let mut tx = db.write_tx(); - tx.insert(&tree, "a", "a"); - tx.commit()?; - - let snapshot = db.read_tx(); - assert!(snapshot.contains_key(&tree, "a")?); - - tree.inner().clear()?; - - assert!(snapshot.contains_key(&tree, "a")?); - - assert!(!tree.contains_key("a")?); - assert_eq!(0, tree.approximate_len()); - - { - let snapshot = db.read_tx(); - assert!(!snapshot.contains_key(&tree, "a")?); - } - - Ok(()) -} diff --git a/crates/fjall/tests/fifo_dirty_read.rs b/crates/fjall/tests/fifo_dirty_read.rs deleted file mode 100644 index 747bf9068..000000000 --- a/crates/fjall/tests/fifo_dirty_read.rs +++ /dev/null @@ -1,31 +0,0 @@ -use fjall::Readable; - -#[test_log::test] -fn fifo_dirty_read() -> fjall::Result<()> { - use fjall::{KeyspaceCreateOptions, SingleWriterTxDatabase}; - use std::sync::Arc; - - let folder = tempfile::tempdir()?; - - let db = SingleWriterTxDatabase::builder(&folder).open()?; - - let tree = db.keyspace("default", || { - KeyspaceCreateOptions::default() - .compaction_strategy(Arc::new(fjall::compaction::Fifo::new(1, None))) - })?; - - assert!(!tree.contains_key("a")?); - - let mut tx = db.write_tx(); - tx.insert(&tree, "a", "a"); - tx.commit()?; - - let snapshot = db.read_tx(); - assert!(snapshot.contains_key(&tree, "a")?); - - tree.inner().rotate_memtable_and_wait()?; - - assert!(snapshot.contains_key(&tree, "a")?); - - Ok(()) -} diff --git a/crates/fjall/tests/format_v4.rs b/crates/fjall/tests/format_v5.rs similarity index 82% rename from crates/fjall/tests/format_v4.rs rename to crates/fjall/tests/format_v5.rs index 1539bdb75..9dc9af9f9 100644 --- a/crates/fjall/tests/format_v4.rs +++ b/crates/fjall/tests/format_v5.rs @@ -1,7 +1,7 @@ use fjall::{Database, Error, FormatVersion, KeyspaceCreateOptions}; #[test] -fn new_database_uses_v4_and_reopens_fixed_width_tables() -> fjall::Result<()> { +fn new_database_uses_v5_and_reopens_fixed_width_tables() -> fjall::Result<()> { let folder = tempfile::tempdir()?; { @@ -16,7 +16,7 @@ fn new_database_uses_v4_and_reopens_fixed_width_tables() -> fjall::Result<()> { assert_eq!( std::fs::read(folder.path().join("version"))?, - [b'F', b'J', b'L', 4], + [b'F', b'J', b'L', 5], ); let database = Database::builder(folder.path()).open()?; @@ -29,13 +29,13 @@ fn new_database_uses_v4_and_reopens_fixed_width_tables() -> fjall::Result<()> { } #[test] -fn v3_database_requires_resync() -> fjall::Result<()> { +fn v4_database_requires_resync() -> fjall::Result<()> { let folder = tempfile::tempdir()?; - std::fs::write(folder.path().join("version"), [b'F', b'J', b'L', 3])?; + std::fs::write(folder.path().join("version"), [b'F', b'J', b'L', 4])?; assert!(matches!( Database::builder(folder.path()).open(), - Err(Error::InvalidVersion(Some(FormatVersion::V3))), + Err(Error::InvalidVersion(Some(FormatVersion::V4))), )); Ok(()) } diff --git a/crates/fjall/tests/journal_large_value.rs b/crates/fjall/tests/journal_large_value.rs index d94fc711d..0642628ab 100644 --- a/crates/fjall/tests/journal_large_value.rs +++ b/crates/fjall/tests/journal_large_value.rs @@ -1,6 +1,6 @@ // Regression test for https://github.com/fjall-rs/fjall/issues/68 -use fjall::{Database, KeyspaceCreateOptions, KvSeparationOptions}; +use fjall::{Database, KeyspaceCreateOptions}; #[test_log::test] fn journal_recover_large_value() -> fjall::Result<()> { @@ -24,32 +24,3 @@ fn journal_recover_large_value() -> fjall::Result<()> { Ok(()) } - -#[test_log::test] -fn journal_recover_large_value_blob() -> fjall::Result<()> { - let folder = tempfile::tempdir()?; - - let large_value = "a".repeat(128_000); - - { - let db = Database::builder(&folder).open()?; - let tree = db.keyspace("default", || { - KeyspaceCreateOptions::default() - .with_kv_separation(Some(KvSeparationOptions::default())) - })?; - tree.insert("a", &large_value)?; - tree.insert("b", "b")?; - } - - { - let db = Database::builder(&folder).open()?; - let tree = db.keyspace("default", || { - KeyspaceCreateOptions::default() - .with_kv_separation(Some(KvSeparationOptions::default())) - })?; - assert_eq!(large_value.as_bytes(), &*tree.get("a")?.unwrap()); - assert_eq!(b"b", &*tree.get("b")?.unwrap()); - } - - Ok(()) -} diff --git a/crates/fjall/tests/keyspace_recover.rs b/crates/fjall/tests/keyspace_recover.rs index 960688102..08bf2236f 100644 --- a/crates/fjall/tests/keyspace_recover.rs +++ b/crates/fjall/tests/keyspace_recover.rs @@ -2,78 +2,11 @@ use fjall::config::{ BlockSizePolicy, FilterPolicy, FilterPolicyEntry, HashRatioPolicy, PinningPolicy, RestartIntervalPolicy, }; -use fjall::{AbstractTree, CompressionType, Database, KeyspaceCreateOptions, KvSeparationOptions}; -use lsm_tree::compaction::CompactionStrategy; -use std::sync::Arc; +use fjall::{AbstractTree, Database, KeyspaceCreateOptions}; use test_log::test; const ITEM_COUNT: usize = 100; -#[test] -fn reload_keyspace_config_fifo() -> fjall::Result<()> { - let folder = tempfile::tempdir()?; - - let strategy = fjall::compaction::Fifo::new(555, Some(6)); - - let expected_kvs = strategy.get_config(); - - { - let db = Database::builder(&folder).open()?; - - let _tree = db.keyspace("default", || { - KeyspaceCreateOptions::default().compaction_strategy(Arc::new(strategy)) - })?; - }; - - { - let db = Database::builder(&folder).open()?; - - let tree = db.keyspace("default", KeyspaceCreateOptions::default)?; - - assert_eq!( - expected_kvs, - tree.config.compaction_strategy.get_config(), - "compaction strategy config does not match", - ); - } - - Ok(()) -} - -#[test] -fn reload_keyspace_config_leveled() -> fjall::Result<()> { - let folder = tempfile::tempdir()?; - - let strategy = fjall::compaction::Leveled::default() - .with_l0_threshold(6) - .with_level_ratio_policy(vec![4.0, 6.0, 8.0]) - .with_table_target_size(100_000_000); - - let expected_kvs = strategy.get_config(); - - { - let db = Database::builder(&folder).open()?; - - let _tree = db.keyspace("default", || { - KeyspaceCreateOptions::default().compaction_strategy(Arc::new(strategy)) - })?; - }; - - { - let db = Database::builder(&folder).open()?; - - let tree = db.keyspace("default", KeyspaceCreateOptions::default)?; - - assert_eq!( - expected_kvs, - tree.config.compaction_strategy.get_config(), - "compaction strategy config does not match", - ); - } - - Ok(()) -} - #[test] fn reload_keyspace_config() -> fjall::Result<()> { let folder = tempfile::tempdir()?; @@ -195,41 +128,6 @@ fn reload_keyspace_config() -> fjall::Result<()> { Ok(()) } -#[test] -fn reload_keyspace_config_blob_opts() -> fjall::Result<()> { - let folder = tempfile::tempdir()?; - - { - let db = Database::builder(&folder).open()?; - - let _tree = db.keyspace("default", || { - KeyspaceCreateOptions::default().with_kv_separation(Some( - KvSeparationOptions::default() - .age_cutoff(0.55) - .compression(CompressionType::None) - .file_target_size(124) - .separation_threshold(515) - .staleness_threshold(0.77), - )) - })?; - }; - - { - let db = Database::builder(&folder).open()?; - let tree = db.keyspace("default", KeyspaceCreateOptions::default)?; - - let blob_opts = tree.config.kv_separation_opts.as_ref().unwrap(); - - assert_eq!(blob_opts.compression, CompressionType::None); - assert_eq!(blob_opts.age_cutoff, 0.55); - assert_eq!(blob_opts.file_target_size, 124); - assert_eq!(blob_opts.separation_threshold, 515); - assert_eq!(blob_opts.staleness_threshold, 0.77); - } - - Ok(()) -} - #[test] fn reload_with_keyspaces() -> fjall::Result<()> { let folder = tempfile::tempdir()?; diff --git a/crates/fjall/tests/keyspace_snapshot.rs b/crates/fjall/tests/keyspace_snapshot.rs index fe2371fff..ca4565e8a 100644 --- a/crates/fjall/tests/keyspace_snapshot.rs +++ b/crates/fjall/tests/keyspace_snapshot.rs @@ -7,9 +7,7 @@ fn keyspace_iter_dirty_read() -> fjall::Result<()> { let db = Database::builder(&folder).open()?; - let tree = db.keyspace("default", || { - KeyspaceCreateOptions::default().with_kv_separation(Default::default()) - })?; + let tree = db.keyspace("default", KeyspaceCreateOptions::default)?; tree.insert("a#1", "a")?; tree.insert("a#2", "b")?; @@ -32,12 +30,8 @@ fn keyspace_snapshot_read() -> fjall::Result<()> { let db = Database::builder(&folder).open()?; - let tree = db.keyspace("default", || { - KeyspaceCreateOptions::default().with_kv_separation(Default::default()) - })?; - let tree2 = db.keyspace("default2", || { - KeyspaceCreateOptions::default().with_kv_separation(Default::default()) - })?; + let tree = db.keyspace("default", KeyspaceCreateOptions::default)?; + let tree2 = db.keyspace("default2", KeyspaceCreateOptions::default)?; tree.insert("a#1", "a")?; tree.insert("a#2", "b")?; diff --git a/crates/fjall/tests/keyspace_v1_load_fixture.rs b/crates/fjall/tests/keyspace_v1_load_fixture.rs deleted file mode 100644 index 273ab6f55..000000000 --- a/crates/fjall/tests/keyspace_v1_load_fixture.rs +++ /dev/null @@ -1,30 +0,0 @@ -use fjall::Database; -use test_log::test; - -#[test] -fn db_load_v1() -> fjall::Result<()> { - let folder = "test_fixture/v1_keyspace"; - - let result = Database::builder(folder).open(); - - matches!( - result, - Err(fjall::Error::InvalidVersion(Some(fjall::FormatVersion::V1))) - ); - - Ok(()) -} - -#[test] -fn db_load_v1_corrupt_journal() -> fjall::Result<()> { - let folder = "test_fixture/v1_keyspace_corrupt_journal"; - - let result = Database::builder(folder).open(); - - matches!( - result, - Err(fjall::Error::InvalidVersion(Some(fjall::FormatVersion::V1))) - ); - - Ok(()) -} diff --git a/crates/fjall/tests/keyspace_v2_load_fixture.rs b/crates/fjall/tests/keyspace_v2_load_fixture.rs deleted file mode 100644 index aa8480bd1..000000000 --- a/crates/fjall/tests/keyspace_v2_load_fixture.rs +++ /dev/null @@ -1,32 +0,0 @@ -use fjall::{Database, FormatVersion, JournalRecoveryError}; -use test_log::test; - -#[test] -fn db_load_v2() -> fjall::Result<()> { - let folder = "test_fixture/v2_keyspace"; - - let result = Database::builder(folder).open(); - - matches!( - result, - Err(fjall::Error::InvalidVersion(Some(FormatVersion::V2))) - ); - - Ok(()) -} - -#[test] -fn db_load_v2_corrupt_journal() -> fjall::Result<()> { - let folder = "test_fixture/v2_keyspace_corrupt_journal"; - - let result = Database::builder(folder).open(); - - matches!( - result, - Err(fjall::Error::JournalRecovery( - JournalRecoveryError::ChecksumMismatch - )), - ); - - Ok(()) -} diff --git a/crates/fjall/tests/prefix_complex.rs b/crates/fjall/tests/prefix_complex.rs deleted file mode 100644 index f46e4c2a6..000000000 --- a/crates/fjall/tests/prefix_complex.rs +++ /dev/null @@ -1,45 +0,0 @@ -use fjall::{Database, KeyspaceCreateOptions}; -use test_log::test; - -#[test] -fn keyspace_prefix_carl() -> fjall::Result<()> { - let folder = tempfile::tempdir()?; - - let db = Database::builder(&folder).open()?; - - let tree = db.keyspace("default", || { - KeyspaceCreateOptions::default().with_kv_separation(Default::default()) - })?; - - tree.insert("a#1", "a".repeat(4_096))?; - tree.insert("a#2", "b".repeat(4_096))?; - tree.insert("a#3", "c".repeat(4_096))?; - - tree.rotate_memtable_and_wait()?; - - { - let mut iter = tree.prefix("a#"); - - let guard = iter.next().expect("should exist"); - let key = guard.key()?; // does not load blob - assert_eq!(b"a#1", &*key); - - let guard = iter.next().expect("should exist"); - let (key, value) = guard.into_inner()?; // loads blob - assert_eq!( - (b"a#2" as &[u8], &b"b".repeat(4_096) as &[u8]), - (&*key, &*value), - ); - - let guard = iter.next().expect("should exist"); - let (key, value) = guard.into_inner()?; // loads blob - assert_eq!( - (b"a#3" as &[u8], &b"c".repeat(4_096) as &[u8]), - (&*key, &*value), - ); - - assert!(iter.next().is_none(), "iter should be consumed"); - } - - Ok(()) -} diff --git a/crates/fjall/tests/standard_keyspace.rs b/crates/fjall/tests/standard_keyspace.rs index ac056c3a0..983e954d9 100644 --- a/crates/fjall/tests/standard_keyspace.rs +++ b/crates/fjall/tests/standard_keyspace.rs @@ -13,10 +13,10 @@ fn standard_fast_paths_preserve_results_and_snapshot() -> fjall::Result<()> { let tree = db.keyspace("standard", KeyspaceCreateOptions::default)?; let mut ingestion = tree.start_ingestion()?; - ingestion.write_prevalidated("a1", "one")?; - ingestion.write_prevalidated("a2", "two")?; - ingestion.write_prevalidated("b1", "three")?; - ingestion.write_prevalidated_weak_tombstone("c1")?; + ingestion.write("a1", "one")?; + ingestion.write("a2", "two")?; + ingestion.write("b1", "three")?; + ingestion.write_weak_tombstone("c1")?; ingestion.finish_exclusive()?; assert_eq!(tree.get_standard("a2")?.as_deref(), Some(b"two".as_slice())); diff --git a/crates/fjall/tests/tx_ryow.rs b/crates/fjall/tests/tx_ryow.rs deleted file mode 100644 index 69e99d03e..000000000 --- a/crates/fjall/tests/tx_ryow.rs +++ /dev/null @@ -1,31 +0,0 @@ -use fjall::Readable; - -#[test_log::test] -fn tx_ryow() -> fjall::Result<()> { - use fjall::{KeyspaceCreateOptions, SingleWriterTxDatabase}; - - let folder = tempfile::tempdir()?; - - let db = SingleWriterTxDatabase::builder(&folder).open()?; - - let tree = db.keyspace("default", KeyspaceCreateOptions::default)?; - - let mut tx = db.write_tx(); - - assert!(!tx.contains_key(&tree, "a")?); - - tx.insert(&tree, "a", "a"); - assert!(tx.contains_key(&tree, "a")?); - - tx.remove(&tree, "a"); - assert!(!tx.contains_key(&tree, "a")?); - - tx.insert(&tree, "a", "a"); - tx.insert(&tree, "a", "c"); - assert_eq!(b"c", &*tx.get(&tree, "a")?.unwrap()); - - tx.remove(&tree, "a"); - assert!(!tx.contains_key(&tree, "a")?); - - Ok(()) -} diff --git a/crates/fjall/tests/tx_ryow_snapshot.rs b/crates/fjall/tests/tx_ryow_snapshot.rs deleted file mode 100644 index 7453fedad..000000000 --- a/crates/fjall/tests/tx_ryow_snapshot.rs +++ /dev/null @@ -1,59 +0,0 @@ -use fjall::Readable; - -/// Tests that modifications to a write transaction do not dirty a previously started snapshot -#[test_log::test] -fn tx_ryow_snapshot() -> fjall::Result<()> { - use fjall::{KeyspaceCreateOptions, SingleWriterTxDatabase}; - - let folder = tempfile::tempdir()?; - - let db = SingleWriterTxDatabase::builder(&folder).open()?; - - let tree = db.keyspace("default", KeyspaceCreateOptions::default)?; - - let mut tx = db.write_tx(); - - { - let iter = tx.iter(&tree); - for x in 0u64..100 { - tx.insert(&tree, x.to_be_bytes(), "a"); - } - assert_eq!(0, iter.count()); - } - - assert_eq!(100, tx.iter(&tree).count()); - - Ok(()) -} - -#[test_log::test] -fn tx_ryow_snapshot_ssi() -> fjall::Result<()> { - use fjall::{KeyspaceCreateOptions, OptimisticTxDatabase}; - - let folder = tempfile::tempdir()?; - - let db = OptimisticTxDatabase::builder(&folder).open()?; - - let tree = db.keyspace("default", KeyspaceCreateOptions::default)?; - - let mut tx = db.write_tx()?; - - { - let iter = tx.iter(&tree); - for x in 0u64..100 { - tx.insert(&tree, x.to_be_bytes(), "a"); - } - assert_eq!(0, iter.count()); - } - - assert_eq!(100, tx.iter(&tree).count()); - - tree.insert("2", "2")?; - assert_eq!(100, tx.iter(&tree).count()); - - let iter = tx.iter(&tree); - tree.insert("3", "3")?; - assert_eq!(100, iter.count()); - - Ok(()) -} diff --git a/crates/fjall/tests/write_buffer_size.rs b/crates/fjall/tests/write_buffer_size.rs index 507713f9a..02b373430 100644 --- a/crates/fjall/tests/write_buffer_size.rs +++ b/crates/fjall/tests/write_buffer_size.rs @@ -1,4 +1,4 @@ -use fjall::{Database, KeyspaceCreateOptions, KvSeparationOptions}; +use fjall::{Database, KeyspaceCreateOptions}; use test_log::test; #[test] @@ -27,32 +27,3 @@ fn write_buffer_size_after_insert() -> fjall::Result<()> { Ok(()) } - -#[test] -fn write_buffer_size_blob() -> fjall::Result<()> { - let folder = tempfile::tempdir()?; - - let db = Database::builder(&folder).open()?; - - let tree = db.keyspace("default", || { - KeyspaceCreateOptions::default().with_kv_separation(Some(KvSeparationOptions::default())) - })?; - assert_eq!(0, db.write_buffer_size()); - - tree.insert("asd", "def")?; - - let write_buffer_size_after = db.write_buffer_size(); - assert!(write_buffer_size_after > 0); - - let mut batch = db.batch(); - batch.insert(&tree, "dsa", "qwe"); - batch.commit()?; - - let write_buffer_size_after_batch = db.write_buffer_size(); - assert!(write_buffer_size_after_batch > write_buffer_size_after); - - tree.rotate_memtable_and_wait()?; - assert_eq!(0, db.write_buffer_size()); - - Ok(()) -} diff --git a/crates/fjall/tests/write_tx.rs b/crates/fjall/tests/write_tx.rs deleted file mode 100644 index 01fced988..000000000 --- a/crates/fjall/tests/write_tx.rs +++ /dev/null @@ -1,20 +0,0 @@ -use fjall::{KeyspaceCreateOptions, Readable, SingleWriterTxDatabase}; -use test_log::test; - -#[test] -fn write_tx_multi_keys() -> fjall::Result<()> { - let folder = tempfile::tempdir()?; - - let db = SingleWriterTxDatabase::builder(&folder).open()?; - let tree = db.keyspace("default", KeyspaceCreateOptions::default)?; - - let mut wtx = db.write_tx(); - wtx.insert(&tree, "1", "abc"); - wtx.insert(&tree, "1", "def"); - wtx.insert(&tree, "1", "ghi"); - assert_eq!(&*wtx.get(&tree, "1")?.unwrap(), b"ghi"); - wtx.commit()?; - assert_eq!(&*tree.get("1")?.unwrap(), b"ghi"); - - Ok(()) -} diff --git a/crates/lsm-tree/.config/nextest.toml b/crates/lsm-tree/.config/nextest.toml deleted file mode 100644 index 6079e36c6..000000000 --- a/crates/lsm-tree/.config/nextest.toml +++ /dev/null @@ -1,2 +0,0 @@ -[profile.default] -slow-timeout = { period = "10s", terminate-after = 6 } diff --git a/crates/lsm-tree/.gitignore b/crates/lsm-tree/.gitignore deleted file mode 100644 index c31cd55da..000000000 --- a/crates/lsm-tree/.gitignore +++ /dev/null @@ -1,26 +0,0 @@ -# Generated by Cargo -# will have compiled files and executables -debug/ -target/ - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html -Cargo.lock - -# These are backup files generated by rustfmt -**/*.rs.bk - -# MSVC Windows builds of rustc generate these, which store debugging information -*.pdb - -.directory - -.lsm.data -.test* -.bench - -mutants* -profile.json -fuzz*/**/out* - -old diff --git a/crates/lsm-tree/.rustfmt.toml b/crates/lsm-tree/.rustfmt.toml deleted file mode 100644 index 1c7741475..000000000 --- a/crates/lsm-tree/.rustfmt.toml +++ /dev/null @@ -1,3 +0,0 @@ -reorder_imports = true -# group_imports = "StdExternalCrate" -# imports_granularity = "crate" diff --git a/crates/lsm-tree/CONTRIBUTING.md b/crates/lsm-tree/CONTRIBUTING.md deleted file mode 100644 index b5aab0e18..000000000 --- a/crates/lsm-tree/CONTRIBUTING.md +++ /dev/null @@ -1,11 +0,0 @@ -# Contributing - -## License - -By contributing to this project, you agree that your contributions will be licensed under the project's license (MIT OR Apache-2.0). - -Thank you for your contribution! - -## Looking for issues? - -https://github.com/fjall-rs/lsm-tree/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22 diff --git a/crates/lsm-tree/Cargo.toml b/crates/lsm-tree/Cargo.toml index b83a8315d..d58813838 100644 --- a/crates/lsm-tree/Cargo.toml +++ b/crates/lsm-tree/Cargo.toml @@ -5,11 +5,8 @@ license.workspace = true version.workspace = true edition.workspace = true readme.workspace = true -include = ["src/**/*", "LICENSE-APACHE", "LICENSE-MIT", "README.md"] repository.workspace = true homepage.workspace = true -keywords = ["lsmt", "lsm", "rocksdb", "leveldb", "key-value"] -categories = ["data-structures", "database-implementations", "algorithms"] [lib] name = "lsm_tree" @@ -18,42 +15,35 @@ path = "src/lib.rs" [features] default = [] lz4 = ["dep:lz4_flex"] -bytes_1 = ["dep:bytes"] -metrics = [] [dependencies] -bytes = { version = "1", optional = true } byteorder = { package = "byteorder-lite", version = "0.1.0" } byteview = { workspace = true } crossbeam-skiplist = "0.1.3" -enum_dispatch = "0.3.13" interval-heap = "0.0.5" log = { workspace = true } lz4_flex = { workspace = true, features = ["checked-decode"], optional = true } -quick_cache = { version = "0.6.16", default-features = false, features = [] } +quick_cache = { version = "0.7.0", default-features = false, features = [] } rustc-hash = { workspace = true } -self_cell = "1.2.0" +self_cell = "1.3.0" sfa = "~1.0.0" tempfile = { workspace = true } -varint-rs = "2.2.0" -xxhash-rust = { version = "0.8.15", features = ["xxh3"] } +varint-rs = "2.2.1" +xxhash-rust = { version = "0.8.18", features = ["xxh3"] } [dev-dependencies] -criterion = { version = "0.8.0", features = ["html_reports"] } +criterion = { version = "0.8.2", features = ["html_reports"] } fs_extra = "1.3.0" -nanoid = "0.4.0" -rand = "0.9.2" +nanoid = "0.5.0" +rand = "0.10.2" strum = { version = "0.28.0", features = ["derive"] } -test-log = "0.2.18" +test-log = "0.2.21" [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = [ 'cfg(coverage,coverage_nightly)', ] } -[package.metadata.cargo-all-features] -denylist = [] - [[bench]] name = "merge" harness = false diff --git a/crates/lsm-tree/README.md b/crates/lsm-tree/README.md deleted file mode 100644 index 533fdac36..000000000 --- a/crates/lsm-tree/README.md +++ /dev/null @@ -1,83 +0,0 @@ -

- -

- -[![CI](https://github.com/fjall-rs/lsm-tree/actions/workflows/test.yml/badge.svg)](https://github.com/fjall-rs/lsm-tree/actions/workflows/test.yml) -[![docs.rs](https://img.shields.io/docsrs/lsm-tree?color=green)](https://docs.rs/lsm-tree) -[![Crates.io](https://img.shields.io/crates/v/lsm-tree?color=blue)](https://crates.io/crates/lsm-tree) -![MSRV](https://img.shields.io/badge/MSRV-1.90.0-blue) -[![dependency status](https://deps.rs/repo/github/fjall-rs/lsm-tree/status.svg)](https://deps.rs/repo/github/fjall-rs/lsm-tree) - -A K.I.S.S. implementation of log-structured merge trees (LSM-trees/LSMTs) in Rust. - -> [!NOTE] -> This crate only provides a primitive LSM-tree, not a full storage engine. -> For example, it does not ship with a write-ahead log. -> You probably want to use https://github.com/fjall-rs/fjall instead. - -## About - -This is the most feature-rich LSM-tree implementation in Rust! It features: - -- Thread-safe `BTreeMap`-like API -- Mostly [safe](./UNSAFE.md) & 100% stable Rust -- Block-based tables with compression support & prefix truncation - - Optional block hash indexes in data blocks for faster point lookups [[3]](#footnotes) - - Per-level filter/index block pinning configuration -- Range & prefix searching with forward and reverse iteration -- Block caching to keep hot data in memory -- File descriptor caching with upper bound to reduce `fopen` syscalls -- *AMQ* filters (currently Bloom filters) to improve point lookup performance -- Multi-versioning of KVs, enabling snapshot reads -- Optionally partitioned block index & filters for better cache efficiency [[1]](#footnotes) -- Leveled and FIFO compaction -- Optional key-value separation for large value workloads [[2]](#footnotes), with automatic garbage collection -- Single deletion tombstones ("weak" deletion) -- Optional compaction filters to run custom logic during compactions - -Keys are limited to 65536 bytes, values are limited to 2^32 bytes. -As is normal with any kind of storage engine, larger keys and values have a bigger performance impact. - -## Sponsors - - - - - - Orbitinghail - - - -## Feature flags - -### lz4 - -Allows using `LZ4` compression, powered by [`lz4_flex`](https://github.com/PSeitz/lz4_flex). - -*Disabled by default.* - -### bytes_1 - -Uses [`bytes`](https://github.com/tokio-rs/bytes) 1.x as the underlying `Slice` type. - -*Disabled by default.* - -## Run unit benchmarks - -```bash -cargo bench --features lz4 -``` - -## License - -All source code is licensed under MIT OR Apache-2.0. - -All contributions are to be licensed as MIT OR Apache-2.0. - -## Footnotes - -[1] https://rocksdb.org/blog/2017/05/12/partitioned-index-filter.html - -[2] https://github.com/facebook/rocksdb/wiki/BlobDB - -[3] https://rocksdb.org/blog/2018/08/23/data-block-hash-index.html diff --git a/crates/lsm-tree/benches/block.rs b/crates/lsm-tree/benches/block.rs index f0ae8beb7..f1d08ad30 100644 --- a/crates/lsm-tree/benches/block.rs +++ b/crates/lsm-tree/benches/block.rs @@ -1,7 +1,7 @@ -use criterion::{criterion_group, criterion_main, BatchSize, Criterion, Throughput}; +use criterion::{BatchSize, Criterion, Throughput, criterion_group, criterion_main}; use lsm_tree::{ - table::block::{Block, BlockType}, CompressionType, + table::block::{Block, BlockType}, }; fn input(size: usize) -> Vec { diff --git a/crates/lsm-tree/benches/bloom.rs b/crates/lsm-tree/benches/bloom.rs index 4c2d8dfe1..5f72db1f6 100644 --- a/crates/lsm-tree/benches/bloom.rs +++ b/crates/lsm-tree/benches/bloom.rs @@ -1,4 +1,4 @@ -use criterion::{criterion_group, criterion_main, Criterion}; +use criterion::{Criterion, criterion_group, criterion_main}; use rand::{Rng, RngCore}; // Not really worth it anymore on new CPUs...? diff --git a/crates/lsm-tree/benches/fd_table.rs b/crates/lsm-tree/benches/fd_table.rs index 79632fa1f..8e2637643 100644 --- a/crates/lsm-tree/benches/fd_table.rs +++ b/crates/lsm-tree/benches/fd_table.rs @@ -1,4 +1,4 @@ -use criterion::{criterion_group, criterion_main, Criterion}; +use criterion::{Criterion, criterion_group, criterion_main}; use std::sync::Arc; fn file_descriptor_table(c: &mut Criterion) { diff --git a/crates/lsm-tree/benches/level_manifest.rs b/crates/lsm-tree/benches/level_manifest.rs index c260a58b9..4b9d86792 100644 --- a/crates/lsm-tree/benches/level_manifest.rs +++ b/crates/lsm-tree/benches/level_manifest.rs @@ -1,5 +1,5 @@ -use criterion::{criterion_group, criterion_main, Criterion}; -use lsm_tree::{config::BlockSizePolicy, AbstractTree, Config}; +use criterion::{Criterion, criterion_group, criterion_main}; +use lsm_tree::{AbstractTree, Config, config::BlockSizePolicy}; fn iterate_tables(c: &mut Criterion) { let mut group = c.benchmark_group("Iterate version tables"); diff --git a/crates/lsm-tree/benches/memtable.rs b/crates/lsm-tree/benches/memtable.rs index db708ddfb..a89e18b66 100644 --- a/crates/lsm-tree/benches/memtable.rs +++ b/crates/lsm-tree/benches/memtable.rs @@ -1,4 +1,4 @@ -use criterion::{criterion_group, criterion_main, Criterion}; +use criterion::{Criterion, criterion_group, criterion_main}; use lsm_tree::{InternalValue, Memtable, SeqNo}; use nanoid::nanoid; diff --git a/crates/lsm-tree/benches/merge.rs b/crates/lsm-tree/benches/merge.rs index cb5f24024..75a2aeb56 100644 --- a/crates/lsm-tree/benches/merge.rs +++ b/crates/lsm-tree/benches/merge.rs @@ -1,6 +1,6 @@ -use criterion::{criterion_group, criterion_main, Criterion}; +use criterion::{Criterion, criterion_group, criterion_main}; use lsm_tree::merge::{BoxedIterator, Merger}; -use lsm_tree::{mvcc_stream::MvccStream, InternalValue, Memtable}; +use lsm_tree::{InternalValue, Memtable, mvcc_stream::MvccStream}; use nanoid::nanoid; fn merger(c: &mut Criterion) { diff --git a/crates/lsm-tree/benches/tree.rs b/crates/lsm-tree/benches/tree.rs index 6e457b93d..c8667c675 100644 --- a/crates/lsm-tree/benches/tree.rs +++ b/crates/lsm-tree/benches/tree.rs @@ -1,7 +1,6 @@ -use criterion::{criterion_group, criterion_main, Criterion}; +use criterion::{Criterion, criterion_group, criterion_main}; use lsm_tree::{ - config::BlockSizePolicy, AbstractTree, Cache, Config, Guard, KvSeparationOptions, SeqNo, - SequenceNumberCounter, + AbstractTree, Cache, Config, Guard, SeqNo, SequenceNumberCounter, config::BlockSizePolicy, }; use std::{path::Path, sync::Arc}; use tempfile::tempdir; @@ -376,32 +375,11 @@ fn disjoint_tree_minmax(c: &mut Criterion) { }); } -fn blob_tree_get(c: &mut Criterion) { - let folder = tempfile::tempdir().unwrap(); - - let tree = config(folder.path()) - .use_cache(Cache::with_capacity_bytes(0).into()) - .with_kv_separation(Some(KvSeparationOptions::default())) - .open() - .unwrap(); - - let value = b"powek5bowa".repeat(100); - - tree.insert("mykey", &value, 0); - - c.bench_function("blob tree get", |b| { - b.iter(|| { - tree.get("mykey", SeqNo::MAX).unwrap().unwrap(); - }); - }); -} - // TODO: benchmark point read disjoint vs non-disjoint level vs disjoint *tree* // TODO: benchmark .prefix().next() and .next_back(), disjoint and non-disjoint criterion_group!( benches, - blob_tree_get, disjoint_tree_minmax, disk_point_read, full_scan, diff --git a/crates/lsm-tree/logo.png b/crates/lsm-tree/logo.png deleted file mode 100644 index d5873dd9aa3a365cb76ba6d3f19527a429bbb9e2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 19998 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�p7q|K)6Z;o<14A{h8&*4e#}W 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 LNkvXXu0mjfM&xy; diff --git a/crates/lsm-tree/renovate.json b/crates/lsm-tree/renovate.json deleted file mode 100644 index 39a2b6e9a..000000000 --- a/crates/lsm-tree/renovate.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "$schema": "https://docs.renovatebot.com/renovate-schema.json", - "extends": [ - "config:base" - ] -} diff --git a/crates/lsm-tree/src/abstract_tree.rs b/crates/lsm-tree/src/abstract_tree.rs index 74f1a1f3b..5998818f7 100644 --- a/crates/lsm-tree/src/abstract_tree.rs +++ b/crates/lsm-tree/src/abstract_tree.rs @@ -3,20 +3,17 @@ // (found in the LICENSE-* files in the repository) use crate::{ - iter_guard::IterGuardImpl, table::Table, version::Version, vlog::BlobFile, AnyTree, BlobTree, - Config, Guard, InternalValue, KvPair, Memtable, SeqNo, TableId, Tree, UserKey, UserValue, + Config, Guard, InternalValue, Memtable, SeqNo, TableId, UserKey, UserValue, + iter_guard::IterGuardImpl, table::Table, version::Version, }; use std::{ ops::RangeBounds, sync::{Arc, MutexGuard, RwLockWriteGuard}, }; -pub type RangeItem = crate::Result; - -type FlushToTablesResult = (Vec, Option>); +type FlushToTablesResult = Vec
; /// Generic Tree API -#[enum_dispatch::enum_dispatch] pub trait AbstractTree { /// Debug method for tracing the MVCC history of a key. #[doc(hidden)] @@ -100,14 +97,8 @@ pub trait AbstractTree { drop(version_history); - if let Some((tables, blob_files)) = self.flush_to_tables(stream)? { - self.register_tables( - &tables, - blob_files.as_deref(), - None, - &sealed_ids, - seqno_threshold, - )?; + if let Some(tables) = self.flush_to_tables(stream)? { + self.register_tables(&tables, &sealed_ids, seqno_threshold)?; } Ok(Some(flushed_size)) @@ -153,17 +144,6 @@ pub trait AbstractTree { /// Returns the approximate number of values reclaimable once weak tombstones can be GC'd. fn weak_tombstone_reclaimable_count(&self) -> u64; - /// Drops tables that are fully contained in a given range. - /// - /// Accepts any `RangeBounds`, including unbounded or exclusive endpoints. - /// If the normalized lower bound is greater than the upper bound, the - /// method returns without performing any work. - /// - /// # Errors - /// - /// Will return `Err` only if an IO error occurs. - fn drop_range, R: RangeBounds>(&self, range: R) -> crate::Result<()>; - /// Drops all tables and clears all memtables atomically. /// /// # Errors @@ -178,11 +158,6 @@ pub trait AbstractTree { /// Will return `Err` if an IO error occurs. fn major_compact(&self, target_size: u64, seqno_threshold: SeqNo) -> crate::Result<()>; - /// Returns the disk space used by stale blobs. - fn stale_blob_bytes(&self) -> u64 { - 0 - } - /// Gets the disk space usage of all filters in the tree. /// /// May not correspond to the actual memory size because filter blocks may be paged out. @@ -197,10 +172,6 @@ pub trait AbstractTree { /// Gets the length of the version free list. fn version_free_list_len(&self) -> usize; - /// Returns the metrics structure. - #[cfg(feature = "metrics")] - fn metrics(&self) -> &Arc; - /// Acquires the flush lock which is required to call [`Tree::flush`]. fn get_flush_lock(&self) -> MutexGuard<'_, ()>; @@ -226,8 +197,6 @@ pub trait AbstractTree { fn register_tables( &self, tables: &[Table], - blob_files: Option<&[BlobFile]>, - frag_map: Option, sealed_memtables_to_delete: &[crate::tree::inner::MemtableId], gc_watermark: SeqNo, ) -> crate::Result<()>; @@ -265,17 +234,6 @@ pub trait AbstractTree { /// Returns the active memtable. fn active_memtable(&self) -> Arc; - /// Returns the tree type. - fn tree_type(&self) -> crate::TreeType { - // NOTE: This is only really safe to do, because we validate - // that the config's kv_separation_opts is consistent with the tree type during recovery. - if self.tree_config().kv_separation_opts.is_some() { - crate::TreeType::Blob - } else { - crate::TreeType::Standard - } - } - /// Seals the active memtable. fn rotate_memtable(&self) -> Option>; @@ -292,9 +250,6 @@ pub trait AbstractTree { /// Can be used to determine whether to write stall. fn l0_run_count(&self) -> usize; - /// Returns the number of blob files currently in the tree. - fn blob_file_count(&self) -> usize; - /// Approximates the number of items in the tree. fn approximate_len(&self) -> usize; diff --git a/crates/lsm-tree/src/any_tree.rs b/crates/lsm-tree/src/any_tree.rs deleted file mode 100644 index 29cfbaf6b..000000000 --- a/crates/lsm-tree/src/any_tree.rs +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright (c) 2024-present, fjall-rs -// This source code is licensed under both the Apache 2.0 and MIT License -// (found in the LICENSE-* files in the repository) - -use crate::{BlobTree, Tree}; -use enum_dispatch::enum_dispatch; - -/// May be a standard [`Tree`] or a [`BlobTree`] -#[derive(Clone)] -#[enum_dispatch(AbstractTree)] -pub enum AnyTree { - /// Standard LSM-tree, see [`Tree`] - Standard(Tree), - - /// Key-value separated LSM-tree, see [`BlobTree`] - Blob(BlobTree), -} diff --git a/crates/lsm-tree/src/blob_tree/gc.rs b/crates/lsm-tree/src/blob_tree/gc.rs deleted file mode 100644 index a905faeeb..000000000 --- a/crates/lsm-tree/src/blob_tree/gc.rs +++ /dev/null @@ -1,307 +0,0 @@ -// Copyright (c) 2024-present, fjall-rs -// This source code is licensed under both the Apache 2.0 and MIT License -// (found in the LICENSE-* files in the repository) - -use crate::{ - blob_tree::handle::BlobIndirection, coding::Decode, compaction::stream::DroppedKvCallback, - version::BlobFileList, vlog::BlobFileId, -}; - -/// Tracks fragmentation information in a blob file -#[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub struct FragmentationEntry { - /// Number of unreferenced (garbage) blobs - pub(crate) len: usize, - - /// Unreferenced (garbage) blob bytes that could be freed (compressed) - pub(crate) bytes: u64, - - /// Unreferenced (garbage) blob bytes that could be freed from disk - pub(crate) on_disk_bytes: u64, -} - -impl FragmentationEntry { - #[must_use] - pub fn new(len: usize, bytes: u64, on_disk_bytes: u64) -> Self { - Self { - len, - bytes, - on_disk_bytes, - } - } -} - -/// Tracks fragmentation information in a value log (list of blob files) -#[derive(Clone, Debug, Default, PartialEq, Eq)] -pub struct FragmentationMap(crate::HashMap); - -impl std::ops::Deref for FragmentationMap { - type Target = crate::HashMap; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl std::ops::DerefMut for FragmentationMap { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - -impl FragmentationMap { - /// Returns the number of bytes that could be freed from disk. - #[must_use] - pub fn stale_bytes(&self) -> u64 { - self.0.values().map(|x| x.on_disk_bytes).sum() - } - - /// Removes blob file entries that are not part of the value log (anymore) - /// to reduce linear memory growth. - pub fn prune(&mut self, value_log: &BlobFileList) { - self.0.retain(|&k, _| value_log.contains_key(k)); - } - - /// Merges a fragmentation map into another. - /// - /// This is used after a compaction stream is summed up (using the expiration callback), to apply - /// the diff to the tree's fragmentation stats. - pub fn merge_into(self, other: &mut Self) { - for (blob_file_id, diff) in self.0 { - other - .0 - .entry(blob_file_id) - .and_modify(|counter| { - counter.bytes += diff.bytes; - counter.len += diff.len; - counter.on_disk_bytes += diff.on_disk_bytes; - }) - .or_insert(diff); - } - } -} - -impl crate::coding::Encode for FragmentationMap { - fn encode_into(&self, writer: &mut W) -> Result<(), crate::Error> { - use byteorder::{WriteBytesExt, LE}; - - #[expect( - clippy::cast_possible_truncation, - reason = "there are always less than 4 billion blob files" - )] - writer.write_u32::(self.len() as u32)?; - - for (blob_file_id, item) in self.iter() { - writer.write_u64::(*blob_file_id)?; - - #[expect( - clippy::cast_possible_truncation, - reason = "there are always less than 4 billion blobs in a blob file" - )] - writer.write_u32::(item.len as u32)?; - - writer.write_u64::(item.bytes)?; - - writer.write_u64::(item.on_disk_bytes)?; - } - - Ok(()) - } -} - -impl crate::coding::Decode for FragmentationMap { - fn decode_from(reader: &mut R) -> Result - where - Self: Sized, - { - use byteorder::{ReadBytesExt, LE}; - - let len = reader.read_u32::()?; - let mut map = - crate::HashMap::with_capacity_and_hasher(len as usize, rustc_hash::FxBuildHasher); - - for _ in 0..len { - let id = reader.read_u64::()?; - let len = reader.read_u32::()? as usize; - let bytes = reader.read_u64::()?; - let on_disk_bytes = reader.read_u64::()?; - map.insert(id, FragmentationEntry::new(len, bytes, on_disk_bytes)); - } - - Ok(Self(map)) - } -} - -impl DroppedKvCallback for FragmentationMap { - fn on_dropped(&mut self, kv: &crate::InternalValue) { - if kv.key.value_type.is_indirection() { - let mut reader = &kv.value[..]; - - #[expect( - clippy::expect_used, - reason = "data is read and checked for corruption, so we expect to be able to deserialize BlobIndirection fine" - )] - let vptr = - BlobIndirection::decode_from(&mut reader).expect("should parse BlobIndirection"); - - let size = u64::from(vptr.size); - let on_disk_size = u64::from(vptr.vhandle.on_disk_size); - - self.0 - .entry(vptr.vhandle.blob_file_id) - .and_modify(|counter| { - counter.len += 1; - counter.bytes += size; - counter.on_disk_bytes += on_disk_size; - }) - .or_insert_with(|| FragmentationEntry { - bytes: size, - on_disk_bytes: on_disk_size, - len: 1, - }); - } - } -} - -#[cfg(test)] -#[expect(clippy::expect_used, clippy::indexing_slicing)] -mod tests { - use super::*; - use crate::{ - coding::{Decode, Encode}, - compaction::stream::CompactionStream, - value::InternalValue, - vlog::ValueHandle, - ValueType, - }; - use std::collections::HashMap; - use test_log::test; - - #[test] - fn frag_map_merge_into() { - let mut map = FragmentationMap(HashMap::default()); - map.0.insert( - 0, - FragmentationEntry { - len: 1, - bytes: 1_000, - on_disk_bytes: 500, - }, - ); - map.0.insert( - 1, - FragmentationEntry { - len: 2, - bytes: 2_000, - on_disk_bytes: 1_000, - }, - ); - - // test merge_into - let mut diff = FragmentationMap(HashMap::default()); - diff.0.insert( - 0, - FragmentationEntry { - len: 3, - bytes: 3_000, - on_disk_bytes: 1_500, - }, - ); - diff.0.insert( - 3, - FragmentationEntry { - len: 4, - bytes: 4_000, - on_disk_bytes: 2_000, - }, - ); - - diff.merge_into(&mut map); - - assert_eq!(map.0.len(), 3); - assert_eq!(map.0[&0].len, 4); - assert_eq!(map.0[&0].bytes, 4_000); - assert_eq!(map.0[&0].on_disk_bytes, 2_000); - assert_eq!(map.0[&1].len, 2); - assert_eq!(map.0[&1].bytes, 2_000); - assert_eq!(map.0[&1].on_disk_bytes, 1_000); - assert_eq!(map.0[&3].len, 4); - assert_eq!(map.0[&3].bytes, 4_000); - assert_eq!(map.0[&3].on_disk_bytes, 2_000); - } - - #[test] - fn frag_map_roundtrip() { - let map = FragmentationMap({ - let mut map = HashMap::default(); - map.insert( - 0, - FragmentationEntry { - len: 1, - bytes: 1_000, - on_disk_bytes: 500, - }, - ); - map.insert( - 1, - FragmentationEntry { - len: 2, - bytes: 2_000, - on_disk_bytes: 1_000, - }, - ); - map - }); - - let encoded = map.encode_into_vec(); - let decoded = FragmentationMap::decode_from(&mut &encoded[..]).expect("should decode map"); - assert_eq!(map, decoded); - } - - #[test] - #[expect(clippy::unwrap_used)] - fn compaction_stream_gc_count_drops() -> crate::Result<()> { - #[rustfmt::skip] - let vec = &[ - InternalValue::from_components("a", b"abc", 1, ValueType::Value), - - InternalValue::from_components("a", BlobIndirection { - size: 1000, - vhandle: ValueHandle { - blob_file_id: 0, - on_disk_size: 500, - offset: 0, - } - }.encode_into_vec(), 0, ValueType::Indirection), - ]; - - let mut my_watcher = FragmentationMap::default(); - - let iter = vec.iter().cloned().map(Ok); - let mut iter = CompactionStream::new(iter, 1_000).with_drop_callback(&mut my_watcher); - - assert_eq!( - // TODO: Seqno is normally reset to 0 - InternalValue::from_components(*b"a", b"abc", 1, ValueType::Value), - iter.next().unwrap()?, - ); - - assert_eq!( - { - let mut map = HashMap::default(); - map.insert( - 0, - FragmentationEntry { - len: 1, - bytes: 1_000, - on_disk_bytes: 500, - }, - ); - map - }, - my_watcher.0, - ); - - Ok(()) - } -} diff --git a/crates/lsm-tree/src/blob_tree/handle.rs b/crates/lsm-tree/src/blob_tree/handle.rs deleted file mode 100644 index 364508482..000000000 --- a/crates/lsm-tree/src/blob_tree/handle.rs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) 2024-present, fjall-rs -// This source code is licensed under both the Apache 2.0 and MIT License -// (found in the LICENSE-* files in the repository) - -use crate::{ - coding::{Decode, Encode}, - vlog::ValueHandle, -}; -use std::io::{Read, Write}; -use varint_rs::{VarintReader, VarintWriter}; - -#[derive(Copy, Clone, Debug)] -pub struct BlobIndirection { - pub(crate) vhandle: ValueHandle, - pub(crate) size: u32, -} - -impl Encode for BlobIndirection { - fn encode_into(&self, writer: &mut W) -> Result<(), crate::Error> { - self.vhandle.encode_into(writer)?; - writer.write_u32_varint(self.size)?; - Ok(()) - } -} - -impl Decode for BlobIndirection { - fn decode_from(reader: &mut R) -> Result { - let vhandle = ValueHandle::decode_from(reader)?; - let size = reader.read_u32_varint()?; - Ok(Self { vhandle, size }) - } -} diff --git a/crates/lsm-tree/src/blob_tree/ingest.rs b/crates/lsm-tree/src/blob_tree/ingest.rs deleted file mode 100644 index 92795c6bf..000000000 --- a/crates/lsm-tree/src/blob_tree/ingest.rs +++ /dev/null @@ -1,274 +0,0 @@ -// Copyright (c) 2024-present, fjall-rs -// This source code is licensed under both the Apache 2.0 and MIT License -// (found in the LICENSE-* files in the repository) - -use crate::{ - blob_tree::handle::BlobIndirection, file::BLOBS_FOLDER, table::Table, - tree::ingest::Ingestion as TableIngestion, vlog::BlobFileWriter, SeqNo, UserKey, UserValue, -}; - -/// Bulk ingestion for [`BlobTree`] -/// -/// Items NEED to be added in ascending key order. -/// -/// Uses table ingestion for the index and a blob file writer for large -/// values so both streams advance together. -pub struct BlobIngestion<'a> { - tree: &'a crate::BlobTree, - pub(crate) table: TableIngestion<'a>, - pub(crate) blob: BlobFileWriter, - seqno: SeqNo, - separation_threshold: u32, - last_key: Option, -} - -impl<'a> BlobIngestion<'a> { - /// Creates a new ingestion. - /// - /// # Errors - /// - /// Will return `Err` if an IO error occurs. - pub fn new(tree: &'a crate::BlobTree) -> crate::Result { - #[expect( - clippy::expect_used, - reason = "cannot define blob tree without kv separation options" - )] - let kv = tree - .index - .config - .kv_separation_opts - .as_ref() - .expect("kv separation options should exist"); - - let blob_file_size = kv.file_target_size; - - let table = TableIngestion::new(&tree.index)?; - let blob = BlobFileWriter::new( - tree.index.0.blob_file_id_counter.clone(), - tree.index.config.path.join(BLOBS_FOLDER), - tree.index.id, - tree.index.config.descriptor_table.clone(), - )? - .use_target_size(blob_file_size) - .use_compression(kv.compression); - - let separation_threshold = kv.separation_threshold; - - Ok(Self { - tree, - table, - blob, - seqno: 0, - separation_threshold, - last_key: None, - }) - } - - /// Writes a key-value pair. - /// - /// # Errors - /// - /// Will return `Err` if an IO error occurs. - pub fn write(&mut self, key: UserKey, value: UserValue) -> crate::Result<()> { - // Check order before any blob I/O to avoid partial writes on failure - if let Some(prev) = &self.last_key { - assert!( - key > *prev, - "next key in ingestion must be greater than last key" - ); - } - - #[expect(clippy::cast_possible_truncation)] - let value_size = value.len() as u32; - - if value_size >= self.separation_threshold { - let vhandle = self.blob.write(&key, self.seqno, &value)?; - - let indirection = BlobIndirection { - vhandle, - size: value_size, - }; - - let cloned_key = key.clone(); - let res = self.table.write_indirection(key, indirection); - if res.is_ok() { - self.last_key = Some(cloned_key); - } - res - } else { - let cloned_key = key.clone(); - let res = self.table.write(key, value); - if res.is_ok() { - self.last_key = Some(cloned_key); - } - res - } - } - - /// Writes a tombstone for a key. - /// - /// # Errors - /// - /// Will return `Err` if an IO error occurs. - pub fn write_tombstone(&mut self, key: UserKey) -> crate::Result<()> { - if let Some(prev) = &self.last_key { - assert!( - key > *prev, - "next key in ingestion must be greater than last key" - ); - } - - let cloned_key = key.clone(); - let res = self.table.write_tombstone(key); - if res.is_ok() { - self.last_key = Some(cloned_key); - } - res - } - - /// Writes a weak tombstone for a key. - /// - /// # Errors - /// - /// Will return `Err` if an IO error occurs. - pub fn write_weak_tombstone(&mut self, key: UserKey) -> crate::Result<()> { - if let Some(prev) = &self.last_key { - assert!( - key > *prev, - "next key in ingestion must be greater than last key" - ); - } - - let cloned_key = key.clone(); - let res = self.table.write_weak_tombstone(key); - if res.is_ok() { - self.last_key = Some(cloned_key); - } - res - } - - /// Finishes the ingestion. - /// - /// # Errors - /// - /// Will return `Err` if an IO error occurs. - #[allow(clippy::significant_drop_tightening)] - pub fn finish(self) -> crate::Result<()> { - use crate::AbstractTree; - - let index = self.index().clone(); - - // CRITICAL SECTION: Atomic flush + seqno allocation + registration - // - // For BlobTree, we must coordinate THREE components atomically: - // 1. Index tree memtable flush - // 2. Value log blob files - // 3. Index tree tables (with blob indirections) - // - // The sequence ensures all components see the same global_seqno: - // 1. Acquire flush lock on index tree - // 2. Flush index tree active memtable - // 3. Finalize blob writer (creates blob files) - // 4. Finalize table writer (creates index tables) - // 5. Allocate next global seqno - // 6. Recover tables with that seqno - // 7. Register version with same seqno + blob files - // - // This prevents race conditions where blob files and their index - // entries could have mismatched sequence numbers. - let flush_lock = index.get_flush_lock(); - - // Flush any pending index memtable writes to ensure ingestion sees - // a consistent snapshot of the index. - // We call rotate + flush directly because we already hold the lock. - index.rotate_memtable(); - index.flush(&flush_lock, 0)?; - - // Finalize the blob writer first, ensuring all large values are - // written to blob files before we finalize the index tables that - // reference them. - let blob_files = self.blob.finish()?; - - // Finalize the table writer, creating index tables with blob - // indirections pointing to the blob files we just created. - let results = self.table.writer.finish()?; - - // Acquire locks for version registration on the index tree. We must - // hold both the compaction state lock and version history lock to - // safely modify the tree's version. - #[expect(clippy::expect_used, reason = "lock is expected to not be poisoned")] - let mut _compaction_state = index.compaction_state.lock().expect("lock is poisoned"); - #[expect(clippy::expect_used, reason = "lock is expected to not be poisoned")] - let mut version_lock = index.version_history.write().expect("lock is poisoned"); - - // Allocate the next global sequence number. This seqno will be shared - // by all ingested tables, blob files, and the version that registers - // them, ensuring consistent MVCC snapshots across the value log. - let global_seqno = index.config.seqno.next(); - - // Recover all created index tables, assigning them the global_seqno - // we just allocated. These tables contain indirections to the blob - // files created above, so they must share the same sequence number - // for MVCC correctness. - // - // We intentionally do NOT pin filter/index blocks here. Large ingests - // are typically placed in level 1, and pinning would increase memory - // pressure unnecessarily. - let created_tables = results - .into_iter() - .map(|(table_id, checksum)| -> crate::Result
{ - Table::recover( - index - .config - .path - .join(crate::file::TABLES_FOLDER) - .join(table_id.to_string()), - checksum, - global_seqno, - index.id, - index.config.cache.clone(), - index.config.descriptor_table.clone(), - false, - false, - #[cfg(feature = "metrics")] - index.metrics.clone(), - ) - }) - .collect::>>()?; - - // Upgrade the version with our ingested tables and blob files, using - // the global_seqno we allocated earlier. This ensures the version, - // tables, and blob files all share the same sequence number, which is - // critical for GC correctness - we must not delete blob files that are - // still referenced by visible snapshots. - // - // We use upgrade_version_with_seqno (instead of upgrade_version) because - // we need precise control over the seqno: it must match the seqno we - // already assigned to the recovered tables. - version_lock.upgrade_version_with_seqno( - &index.config.path, - |current| { - let mut copy = current.clone(); - copy.version = - copy.version - .with_new_l0_run(&created_tables, Some(&blob_files), None); - Ok(copy) - }, - global_seqno, - &self.tree.index.config.visible_seqno, - )?; - - // Perform maintenance on the version history (e.g., clean up old versions). - // We use gc_watermark=0 since ingestion doesn't affect sealed memtables. - if let Err(e) = version_lock.maintenance(&index.config.path, 0) { - log::warn!("Version GC failed: {e:?}"); - } - - Ok(()) - } - - #[inline] - fn index(&self) -> &crate::Tree { - &self.tree.index - } -} diff --git a/crates/lsm-tree/src/blob_tree/mod.rs b/crates/lsm-tree/src/blob_tree/mod.rs deleted file mode 100644 index d38e528c7..000000000 --- a/crates/lsm-tree/src/blob_tree/mod.rs +++ /dev/null @@ -1,631 +0,0 @@ -// Copyright (c) 2024-present, fjall-rs -// This source code is licensed under both the Apache 2.0 and MIT License -// (found in the LICENSE-* files in the repository) - -mod gc; -pub mod handle; -pub mod ingest; - -#[doc(hidden)] -pub use gc::{FragmentationEntry, FragmentationMap}; - -use crate::{ - abstract_tree::{AbstractTree, RangeItem}, - coding::Decode, - iter_guard::{IterGuard, IterGuardImpl}, - table::Table, - tree::inner::MemtableId, - value::InternalValue, - version::Version, - vlog::{Accessor, BlobFile, BlobFileWriter}, - Cache, Config, Memtable, SeqNo, TableId, TreeId, UserKey, UserValue, -}; -use handle::BlobIndirection; -use std::{ - ops::RangeBounds, - path::{Path, PathBuf}, - sync::{Arc, MutexGuard}, -}; - -/// Iterator value guard -pub struct Guard { - tree: crate::BlobTree, - version: Version, - kv: crate::Result, -} - -impl IterGuard for Guard { - fn into_inner_if( - self, - pred: impl Fn(&UserKey) -> bool, - ) -> crate::Result<(UserKey, Option)> { - let kv = self.kv?; - - if pred(&kv.key.user_key) { - resolve_value_handle( - self.tree.id(), - self.tree.blobs_folder.as_path(), - &self.tree.index.config.cache, - &self.version, - kv, - ) - .map(|(k, v)| (k, Some(v))) - } else { - Ok((kv.key.user_key, None)) - } - } - - fn key(self) -> crate::Result { - self.kv.map(|kv| kv.key.user_key) - } - - fn size(self) -> crate::Result { - let kv = self.kv?; - - if kv.key.value_type.is_indirection() { - let mut cursor = std::io::Cursor::new(kv.value); - Ok(BlobIndirection::decode_from(&mut cursor)?.size) - } else { - #[expect(clippy::cast_possible_truncation, reason = "values are u32 max length")] - Ok(kv.value.len() as u32) - } - } - - fn into_inner(self) -> crate::Result<(UserKey, UserValue)> { - resolve_value_handle( - self.tree.id(), - self.tree.blobs_folder.as_path(), - &self.tree.index.config.cache, - &self.version, - self.kv?, - ) - } -} - -fn resolve_value_handle( - tree_id: TreeId, - blobs_folder: &Path, - cache: &Cache, - version: &Version, - item: InternalValue, -) -> RangeItem { - if item.key.value_type.is_indirection() { - let mut cursor = std::io::Cursor::new(item.value); - let vptr = BlobIndirection::decode_from(&mut cursor)?; - - // Resolve indirection using value log - match Accessor::new(&version.blob_files).get( - tree_id, - blobs_folder, - &item.key.user_key, - &vptr.vhandle, - cache, - ) { - Ok(Some(v)) => { - let k = item.key.user_key; - Ok((k, v)) - } - Ok(None) => { - panic!( - "value handle ({:?} => {:?}) did not match any blob - this is a bug; version={}", - item.key.user_key, vptr.vhandle, - version.id(), - ); - } - Err(e) => Err(e), - } - } else { - let k = item.key.user_key; - let v = item.value; - Ok((k, v)) - } -} - -/// A key-value-separated log-structured merge tree -/// -/// This tree is a composite structure, consisting of an -/// index tree (LSM-tree) and a log-structured value log -/// to reduce write amplification. -#[derive(Clone)] -pub struct BlobTree { - /// Index tree that holds value handles or small inline values - #[doc(hidden)] - pub index: crate::Tree, - - blobs_folder: Arc, -} - -impl BlobTree { - pub(crate) fn open(config: Config) -> crate::Result { - use crate::file::{fsync_directory, BLOBS_FOLDER}; - - let index = crate::Tree::open(config)?; - - let blobs_folder = index.config.path.join(BLOBS_FOLDER); - std::fs::create_dir_all(&blobs_folder)?; - fsync_directory(&blobs_folder)?; - - let blob_file_id_to_continue_with = index - .current_version() - .blob_files - .list_ids() - .max() - .map(|x| x + 1) - .unwrap_or_default(); - - index - .0 - .blob_file_id_counter - .set(blob_file_id_to_continue_with); - - Ok(Self { - index, - blobs_folder: Arc::new(blobs_folder), - }) - } -} - -impl AbstractTree for BlobTree { - fn print_trace(&self, key: &[u8]) -> crate::Result<()> { - self.index.print_trace(key) - } - - fn table_file_cache_size(&self) -> usize { - self.index.table_file_cache_size() - } - - fn get_version_history_lock( - &self, - ) -> std::sync::RwLockWriteGuard<'_, crate::version::SuperVersions> { - self.index.get_version_history_lock() - } - - fn next_table_id(&self) -> TableId { - self.index.next_table_id() - } - - fn id(&self) -> crate::TreeId { - self.index.id() - } - - fn get_internal_entry(&self, key: &[u8], seqno: SeqNo) -> crate::Result> { - self.index.get_internal_entry(key, seqno) - } - - fn current_version(&self) -> Version { - self.index.current_version() - } - - #[cfg(feature = "metrics")] - fn metrics(&self) -> &Arc { - self.index.metrics() - } - - fn version_free_list_len(&self) -> usize { - self.index.version_free_list_len() - } - - fn prefix>( - &self, - prefix: K, - seqno: SeqNo, - index: Option<(Arc, SeqNo)>, - ) -> Box + Send + 'static> { - use crate::range::prefix_to_range; - - let super_version = self.index.get_version_for_snapshot(seqno); - let tree = self.clone(); - - let range = prefix_to_range(prefix.as_ref()); - - Box::new( - crate::Tree::create_internal_range(super_version.clone(), &range, seqno, index).map( - move |kv| { - IterGuardImpl::Blob(Guard { - tree: tree.clone(), - version: super_version.version.clone(), - kv, - }) - }, - ), - ) - } - - fn range, R: RangeBounds>( - &self, - range: R, - seqno: SeqNo, - index: Option<(Arc, SeqNo)>, - ) -> Box + Send + 'static> { - let super_version = self.index.get_version_for_snapshot(seqno); - let tree = self.clone(); - - Box::new( - crate::Tree::create_internal_range(super_version.clone(), &range, seqno, index).map( - move |kv| { - IterGuardImpl::Blob(Guard { - tree: tree.clone(), - version: super_version.version.clone(), - kv, - }) - }, - ), - ) - } - - fn tombstone_count(&self) -> u64 { - self.index.tombstone_count() - } - - fn weak_tombstone_count(&self) -> u64 { - self.index.weak_tombstone_count() - } - - fn weak_tombstone_reclaimable_count(&self) -> u64 { - self.index.weak_tombstone_reclaimable_count() - } - - fn drop_range, R: RangeBounds>(&self, range: R) -> crate::Result<()> { - self.index.drop_range(range) - } - - fn clear(&self) -> crate::Result<()> { - let config = self.tree_config(); - let mut versions = self.get_version_history_lock(); - - versions.upgrade_version( - &config.path, - |v| { - let mut copy = v.clone(); - copy.active_memtable = - Arc::new(Memtable::new(self.index.memtable_id_counter.next())); - copy.sealed_memtables = Arc::default(); - copy.version = Version::new(v.version.id() + 1, self.tree_type()); - Ok(copy) - }, - &config.seqno, - &config.visible_seqno, - ) - } - - fn major_compact(&self, target_size: u64, seqno_threshold: SeqNo) -> crate::Result<()> { - self.index.major_compact(target_size, seqno_threshold) - } - - fn clear_active_memtable(&self) { - self.index.clear_active_memtable(); - } - - fn l0_run_count(&self) -> usize { - self.index.l0_run_count() - } - - fn blob_file_count(&self) -> usize { - self.current_version().blob_file_count() - } - - // NOTE: We skip reading from the value log - // because the vHandles already store the value size - fn size_of>(&self, key: K, seqno: SeqNo) -> crate::Result> { - let Some(item) = self.index.get_internal_entry(key.as_ref(), seqno)? else { - return Ok(None); - }; - - Ok(Some(if item.key.value_type.is_indirection() { - let mut cursor = std::io::Cursor::new(item.value); - let vptr = BlobIndirection::decode_from(&mut cursor)?; - vptr.size - } else { - #[expect(clippy::cast_possible_truncation, reason = "values are u32 length max")] - { - item.value.len() as u32 - } - })) - } - - fn stale_blob_bytes(&self) -> u64 { - self.current_version().gc_stats().stale_bytes() - } - - fn filter_size(&self) -> u64 { - self.index.filter_size() - } - - fn pinned_filter_size(&self) -> usize { - self.index.pinned_filter_size() - } - - fn pinned_block_index_size(&self) -> usize { - self.index.pinned_block_index_size() - } - - fn sealed_memtable_count(&self) -> usize { - self.index.sealed_memtable_count() - } - - fn get_flush_lock(&self) -> MutexGuard<'_, ()> { - self.index.get_flush_lock() - } - - fn flush_to_tables( - &self, - stream: impl Iterator>, - ) -> crate::Result, Option>)>> { - use crate::{ - coding::Encode, file::BLOBS_FOLDER, file::TABLES_FOLDER, - table::multi_writer::MultiWriter, - }; - - let start = std::time::Instant::now(); - - let table_folder = self.index.config.path.join(TABLES_FOLDER); - - let data_block_size = self.index.config.data_block_size_policy.get(0); - - let data_block_restart_interval = - self.index.config.data_block_restart_interval_policy.get(0); - let index_block_restart_interval = - self.index.config.index_block_restart_interval_policy.get(0); - - let data_block_compression = self.index.config.data_block_compression_policy.get(0); - let index_block_compression = self.index.config.index_block_compression_policy.get(0); - - let data_block_hash_ratio = self.index.config.data_block_hash_ratio_policy.get(0); - - let index_partitioning = self.index.config.index_block_partitioning_policy.get(0); - let filter_partitioning = self.index.config.filter_block_partitioning_policy.get(0); - - log::debug!("Flushing memtable(s) and performing key-value separation, data_block_restart_interval={data_block_restart_interval}, index_block_restart_interval={index_block_restart_interval}, data_block_size={data_block_size}, data_block_compression={data_block_compression:?}, index_block_compression={index_block_compression:?}"); - log::debug!("=> to table(s) in {}", table_folder.display()); - log::debug!("=> to blob file(s) at {}", self.blobs_folder.display()); - - let mut table_writer = MultiWriter::new( - table_folder.clone(), - self.index.table_id_counter.clone(), - 64 * 1_024 * 1_024, - 0, - )? - .use_data_block_restart_interval(data_block_restart_interval) - .use_index_block_restart_interval(index_block_restart_interval) - .use_data_block_compression(data_block_compression) - .use_index_block_compression(index_block_compression) - .use_data_block_size(data_block_size) - .use_data_block_hash_ratio(data_block_hash_ratio) - .use_bloom_policy({ - use crate::config::FilterPolicyEntry::{Bloom, None}; - use crate::table::filter::BloomConstructionPolicy; - - match self.index.config.filter_policy.get(0) { - Bloom(policy) => policy, - None => BloomConstructionPolicy::BitsPerKey(0.0), - } - }); - - if index_partitioning { - table_writer = table_writer.use_partitioned_index(); - } - if filter_partitioning { - table_writer = table_writer.use_partitioned_filter(); - } - - #[expect( - clippy::expect_used, - reason = "cannot create blob tree without defining kv separation options" - )] - let kv_opts = self - .index - .config - .kv_separation_opts - .as_ref() - .expect("kv separation options should exist"); - - let mut blob_writer = BlobFileWriter::new( - self.index.0.blob_file_id_counter.clone(), - self.index.config.path.join(BLOBS_FOLDER), - self.id(), - self.index.config.descriptor_table.clone(), - )? - .use_target_size(kv_opts.file_target_size) - .use_compression(kv_opts.compression); - - let separation_threshold = kv_opts.separation_threshold; - - for item in stream { - let item = item?; - - if item.is_tombstone() { - // NOTE: Still need to add tombstone to index tree - // But no blob to blob writer - table_writer.write(InternalValue::new(item.key, UserValue::empty()))?; - continue; - } - - let value = item.value; - - #[expect(clippy::cast_possible_truncation, reason = "values are u32 length max")] - let value_size = value.len() as u32; - - if value_size >= separation_threshold { - let vhandle = blob_writer.write(&item.key.user_key, item.key.seqno, &value)?; - - let indirection = BlobIndirection { - vhandle, - size: value_size, - }; - - table_writer.write({ - let mut vptr = - InternalValue::new(item.key.clone(), indirection.encode_into_vec()); - vptr.key.value_type = crate::ValueType::Indirection; - vptr - })?; - - table_writer.register_blob(indirection); - } else { - table_writer.write(InternalValue::new(item.key, value))?; - } - } - - let blob_files = blob_writer.finish()?; - - let result = table_writer.finish()?; - - log::debug!("Flushed memtable(s) in {:?}", start.elapsed()); - - let pin_filter = self.index.config.filter_block_pinning_policy.get(0); - let pin_index = self.index.config.index_block_pinning_policy.get(0); - - // Load tables - let tables = result - .into_iter() - .map(|(table_id, checksum)| -> crate::Result
{ - Table::recover( - table_folder.join(table_id.to_string()), - checksum, - 0, - self.index.id, - self.index.config.cache.clone(), - self.index.config.descriptor_table.clone(), - pin_filter, - pin_index, - #[cfg(feature = "metrics")] - self.index.metrics.clone(), - ) - }) - .collect::>>()?; - - Ok(Some((tables, Some(blob_files)))) - } - - fn register_tables( - &self, - tables: &[Table], - blob_files: Option<&[BlobFile]>, - frag_map: Option, - sealed_memtables_to_delete: &[MemtableId], - gc_watermark: SeqNo, - ) -> crate::Result<()> { - self.index.register_tables( - tables, - blob_files, - frag_map, - sealed_memtables_to_delete, - gc_watermark, - ) - } - - fn compact( - &self, - strategy: Arc, - seqno_threshold: SeqNo, - ) -> crate::Result<()> { - self.index.compact(strategy, seqno_threshold) - } - - fn get_next_table_id(&self) -> TableId { - self.index.get_next_table_id() - } - - fn tree_config(&self) -> &Config { - &self.index.config - } - - fn get_highest_seqno(&self) -> Option { - self.index.get_highest_seqno() - } - - fn active_memtable(&self) -> Arc { - self.index.active_memtable() - } - - fn rotate_memtable(&self) -> Option> { - self.index.rotate_memtable() - } - - fn table_count(&self) -> usize { - self.index.table_count() - } - - fn level_table_count(&self, idx: usize) -> Option { - self.index.level_table_count(idx) - } - - fn approximate_len(&self) -> usize { - self.index.approximate_len() - } - - // NOTE: Override the default implementation to not fetch - // data from the value log, so we get much faster key reads - fn is_empty(&self, seqno: SeqNo, index: Option<(Arc, SeqNo)>) -> crate::Result { - self.index.is_empty(seqno, index) - } - - // NOTE: Override the default implementation to not fetch - // data from the value log, so we get much faster key reads - fn contains_key>(&self, key: K, seqno: SeqNo) -> crate::Result { - self.index.contains_key(key, seqno) - } - - // NOTE: Override the default implementation to not fetch - // data from the value log, so we get much faster scans - fn len(&self, seqno: SeqNo, index: Option<(Arc, SeqNo)>) -> crate::Result { - self.index.len(seqno, index) - } - - fn disk_space(&self) -> u64 { - let version = self.current_version(); - self.index.disk_space() + version.blob_files.on_disk_size() - } - - fn get_highest_memtable_seqno(&self) -> Option { - self.index.get_highest_memtable_seqno() - } - - fn get_highest_persisted_seqno(&self) -> Option { - self.index.get_highest_persisted_seqno() - } - - fn insert, V: Into>( - &self, - key: K, - value: V, - seqno: SeqNo, - ) -> (u64, u64) { - self.index.insert(key, value.into(), seqno) - } - - fn get>(&self, key: K, seqno: SeqNo) -> crate::Result> { - let key = key.as_ref(); - - #[expect(clippy::expect_used, reason = "lock is expected to not be poisoned")] - let super_version = self - .index - .version_history - .read() - .expect("lock is poisoned") - .get_version_for_snapshot(seqno); - - let Some(item) = crate::Tree::get_internal_entry_from_version(&super_version, key, seqno)? - else { - return Ok(None); - }; - - let (_, v) = resolve_value_handle( - self.id(), - self.blobs_folder.as_path(), - &self.index.config.cache, - &super_version.version, - item, - )?; - - Ok(Some(v)) - } - - fn remove>(&self, key: K, seqno: SeqNo) -> (u64, u64) { - self.index.remove(key, seqno) - } - - fn remove_weak>(&self, key: K, seqno: SeqNo) -> (u64, u64) { - self.index.remove_weak(key, seqno) - } -} diff --git a/crates/lsm-tree/src/cache.rs b/crates/lsm-tree/src/cache.rs index 465c80c8c..ce01ecfda 100644 --- a/crates/lsm-tree/src/cache.rs +++ b/crates/lsm-tree/src/cache.rs @@ -2,45 +2,31 @@ // This source code is licensed under both the Apache 2.0 and MIT License // (found in the LICENSE-* files in the repository) +use crate::GlobalTableId; use crate::table::block::Header; use crate::table::{Block, BlockOffset}; -use crate::{GlobalTableId, UserValue}; -use quick_cache::sync::Cache as QuickCache; use quick_cache::Weighter; - -const TAG_BLOCK: u8 = 0; -const TAG_BLOB: u8 = 1; - -#[derive(Clone)] -enum Item { - Block(Block), - Blob(UserValue), -} +use quick_cache::sync::Cache as QuickCache; #[derive(Eq, std::hash::Hash, PartialEq)] -struct CacheKey(u8, u64, u64, u64); +struct CacheKey(GlobalTableId, u64); -impl From<(u8, u64, u64, u64)> for CacheKey { - fn from((tag, root_id, table_id, offset): (u8, u64, u64, u64)) -> Self { - Self(tag, root_id, table_id, offset) +impl CacheKey { + fn from_id(id: GlobalTableId, offset: BlockOffset) -> Self { + Self(id, *offset) } } #[derive(Clone)] struct BlockWeighter; -impl Weighter for BlockWeighter { - fn weight(&self, _: &CacheKey, item: &Item) -> u64 { - use Item::{Blob, Block}; - - match item { - Block(b) => (Header::serialized_len() as u64) + u64::from(b.header.uncompressed_length), - Blob(b) => b.len() as u64, - } +impl Weighter for BlockWeighter { + fn weight(&self, _: &CacheKey, block: &Block) -> u64 { + (Header::serialized_len() as u64) + u64::from(block.header.uncompressed_length) } } -/// Cache, in which blocks or blobs are cached in-memory +/// Cache in which table blocks are cached in memory /// after being retrieved from disk /// /// This speeds up consecutive queries to nearby data, improving @@ -67,7 +53,7 @@ impl Weighter for BlockWeighter { pub struct Cache { // NOTE: rustc_hash performed best: https://fjall-rs.github.io/post/fjall-2-1 /// Concurrent cache implementation - data: QuickCache, + data: QuickCache, /// Capacity in bytes capacity: u64, @@ -115,47 +101,12 @@ impl Cache { #[doc(hidden)] #[must_use] pub fn get_block(&self, id: GlobalTableId, offset: BlockOffset) -> Option { - let key: CacheKey = (TAG_BLOCK, id.tree_id(), id.table_id(), *offset).into(); - - Some(match self.data.get(&key)? { - Item::Block(block) => block, - Item::Blob(_) => unreachable!("invalid cache item"), - }) + let key = CacheKey::from_id(id, offset); + self.data.get(&key) } #[doc(hidden)] pub fn insert_block(&self, id: GlobalTableId, offset: BlockOffset, block: Block) { - self.data.insert( - (TAG_BLOCK, id.tree_id(), id.table_id(), *offset).into(), - Item::Block(block), - ); - } - - #[doc(hidden)] - pub fn insert_blob( - &self, - vlog_id: crate::TreeId, - vhandle: &crate::vlog::ValueHandle, - value: UserValue, - ) { - self.data.insert( - (TAG_BLOB, vlog_id, vhandle.blob_file_id, vhandle.offset).into(), - Item::Blob(value), - ); - } - - #[doc(hidden)] - #[must_use] - pub fn get_blob( - &self, - vlog_id: crate::TreeId, - vhandle: &crate::vlog::ValueHandle, - ) -> Option { - let key: CacheKey = (TAG_BLOB, vlog_id, vhandle.blob_file_id, vhandle.offset).into(); - - Some(match self.data.get(&key)? { - Item::Blob(blob) => blob, - Item::Block(_) => unreachable!("invalid cache item"), - }) + self.data.insert(CacheKey::from_id(id, offset), block); } } diff --git a/crates/lsm-tree/src/compaction/drop_range.rs b/crates/lsm-tree/src/compaction/drop_range.rs deleted file mode 100644 index 4134a195b..000000000 --- a/crates/lsm-tree/src/compaction/drop_range.rs +++ /dev/null @@ -1,100 +0,0 @@ -// Copyright (c) 2024-present, fjall-rs -// This source code is licensed under both the Apache 2.0 and MIT License -// (found in the LICENSE-* files in the repository) - -use super::{Choice, CompactionStrategy}; -use crate::compaction::state::CompactionState; -use crate::version::Version; -use crate::{config::Config, slice::Slice, version::run::Ranged, KeyRange}; -use crate::{HashSet, Table}; -use std::ops::{Bound, RangeBounds}; - -#[derive(Clone, Debug)] -pub struct OwnedBounds { - pub start: Bound, - pub end: Bound, -} - -impl RangeBounds for OwnedBounds { - fn start_bound(&self) -> Bound<&Slice> { - match &self.start { - Bound::Unbounded => Bound::Unbounded, - Bound::Included(key) => Bound::Included(key), - Bound::Excluded(key) => Bound::Excluded(key), - } - } - - fn end_bound(&self) -> Bound<&Slice> { - match &self.end { - Bound::Unbounded => Bound::Unbounded, - Bound::Included(key) => Bound::Included(key), - Bound::Excluded(key) => Bound::Excluded(key), - } - } -} - -impl OwnedBounds { - #[must_use] - pub fn contains(&self, range: &KeyRange) -> bool { - let lower_ok = match &self.start { - Bound::Unbounded => true, - Bound::Included(key) => key.as_ref() <= range.min().as_ref(), - Bound::Excluded(key) => key.as_ref() < range.min().as_ref(), - }; - - if !lower_ok { - return false; - } - - match &self.end { - Bound::Unbounded => true, - Bound::Included(key) => key.as_ref() >= range.max().as_ref(), - Bound::Excluded(key) => key.as_ref() > range.max().as_ref(), - } - } -} - -/// Drops all tables that are **contained** in a key range -pub struct Strategy { - bounds: OwnedBounds, -} - -impl Strategy { - /// Configures a new `DropRange` compaction strategy. - #[must_use] - pub fn new(bounds: OwnedBounds) -> Self { - Self { bounds } - } -} - -impl CompactionStrategy for Strategy { - fn get_name(&self) -> &'static str { - "DropRangeCompaction" - } - - fn choose(&self, version: &Version, _: &Config, state: &CompactionState) -> Choice { - let table_ids: HashSet<_> = version - .iter_levels() - .flat_map(|lvl| lvl.iter()) - .flat_map(|run| { - run.range_overlap_indexes(&self.bounds) - .and_then(|(lo, hi)| run.get(lo..=hi)) - .unwrap_or_default() - .iter() - .filter(|x| self.bounds.contains(x.key_range())) - }) - .map(Table::id) - .collect(); - - // NOTE: This should generally not occur because of the - // tree-level major compaction lock - // But just as a fail-safe... - let some_hidden = table_ids.iter().any(|&id| state.hidden_set().is_hidden(id)); - - if some_hidden { - Choice::DoNothing - } else { - Choice::Drop(table_ids) - } - } -} diff --git a/crates/lsm-tree/src/compaction/fifo.rs b/crates/lsm-tree/src/compaction/fifo.rs deleted file mode 100644 index 7c2a17fd1..000000000 --- a/crates/lsm-tree/src/compaction/fifo.rs +++ /dev/null @@ -1,310 +0,0 @@ -// Copyright (c) 2024-present, fjall-rs -// This source code is licensed under both the Apache 2.0 and MIT License -// (found in the LICENSE-* files in the repository) - -use super::{Choice, CompactionStrategy}; -use crate::{ - compaction::state::CompactionState, config::Config, time::unix_timestamp, version::Version, - HashSet, KvPair, -}; - -#[doc(hidden)] -pub const NAME: &str = "FifoCompaction"; - -/// FIFO-style compaction -/// -/// Limits the tree size to roughly `limit` bytes, deleting the oldest table(s) -/// when the threshold is reached. -/// -/// Will also merge tables if the number of tables in level 0 grows too much, which -/// could cause write stalls. -/// -/// Additionally, a (lazy) TTL can be configured to drop old tables. -/// -/// ###### Caution -/// -/// Only use it for specific workloads where: -/// -/// 1) You only want to store recent data (unimportant logs, ...) -/// 2) The key order of inserts is strictly monotonically increasing or decreasing -/// 3) You only insert new data (no updates/deletes) -#[derive(Clone)] -pub struct Strategy { - /// Data set size limit in bytes - pub limit: u64, - - /// TTL in seconds, will be disabled if 0 or None - pub ttl_seconds: Option, -} - -impl Strategy { - /// Configures a new `Fifo` compaction strategy - #[must_use] - pub fn new(limit: u64, ttl_seconds: Option) -> Self { - Self { limit, ttl_seconds } - } -} - -impl CompactionStrategy for Strategy { - fn get_name(&self) -> &'static str { - NAME - } - - fn get_config(&self) -> Vec { - vec![ - ( - crate::UserKey::from("fifo_limit"), - crate::UserValue::from(self.limit.to_le_bytes()), - ), - ( - crate::UserKey::from("fifo_ttl"), - crate::UserValue::from(if self.ttl_seconds.is_some() { - [1u8] - } else { - [0u8] - }), - ), - ( - crate::UserKey::from("fifo_ttl_seconds"), - crate::UserValue::from(self.ttl_seconds.map(u64::to_le_bytes).unwrap_or_default()), - ), - ] - } - - fn choose(&self, version: &Version, _: &Config, state: &CompactionState) -> Choice { - let first_level = version.l0(); - - // Early return avoids unnecessary work and keeps FIFO a no-op when there is nothing to do. - if first_level.is_empty() { - return Choice::DoNothing; - } - - assert!(first_level.is_disjoint(), "L0 needs to be disjoint"); - - assert!( - !version.level_is_busy(0, state.hidden_set()), - "FIFO compaction never compacts", - ); - - // Account for both table file bytes and value-log (blob) bytes to enforce the true space limit. - let db_size = first_level.size() + version.blob_files.on_disk_size(); - - let mut ids_to_drop = HashSet::default(); - - // Compute TTL cutoff once and perform a single pass to mark expired tables and - // accumulate their sizes. Also collect non-expired tables for possible size-based drops. - let ttl_cutoff = match self.ttl_seconds { - Some(s) if s > 0 => Some( - unix_timestamp() - .as_nanos() - .saturating_sub(u128::from(s) * 1_000_000_000u128), - ), - _ => None, - }; - - let mut ttl_dropped_bytes = 0u64; - let mut alive = Vec::new(); - - for table in first_level.iter().flat_map(|run| run.iter()) { - let expired = - ttl_cutoff.is_some_and(|cutoff| u128::from(table.metadata.created_at) <= cutoff); - - if expired { - ids_to_drop.insert(table.id()); - let linked_blob_file_bytes = table.referenced_blob_bytes().unwrap_or_default(); - ttl_dropped_bytes += table.file_size() + linked_blob_file_bytes; - } else { - alive.push(table); - } - } - - // Subtract TTL-selected bytes to see if we're still over the limit. - let size_after_ttl = db_size.saturating_sub(ttl_dropped_bytes); - - // If we still exceed the limit, drop additional oldest tables until within the limit. - if size_after_ttl > self.limit { - let overshoot = size_after_ttl - self.limit; - - let mut collected_bytes = 0; - - // Oldest-first list by creation time from the non-expired set. - alive.sort_by_key(|t| t.metadata.created_at); - - for table in alive { - if collected_bytes >= overshoot { - break; - } - - ids_to_drop.insert(table.id()); - - let linked_blob_file_bytes = table.referenced_blob_bytes().unwrap_or_default(); - collected_bytes += table.file_size() + linked_blob_file_bytes; - } - } - - if ids_to_drop.is_empty() { - Choice::DoNothing - } else { - Choice::Drop(ids_to_drop) - } - } -} - -#[cfg(test)] -mod tests { - use super::Strategy; - use crate::{AbstractTree, Config, KvSeparationOptions, SequenceNumberCounter}; - use std::sync::Arc; - - #[test] - fn fifo_empty_levels() -> crate::Result<()> { - let dir = tempfile::tempdir()?; - let tree = Config::new( - dir.path(), - SequenceNumberCounter::default(), - SequenceNumberCounter::default(), - ) - .open()?; - - let fifo = Arc::new(Strategy::new(1, None)); - tree.compact(fifo, 0)?; - - assert_eq!(0, tree.table_count()); - Ok(()) - } - - #[test] - fn fifo_below_limit() -> crate::Result<()> { - let dir = tempfile::tempdir()?; - let tree = Config::new( - dir.path(), - SequenceNumberCounter::default(), - SequenceNumberCounter::default(), - ) - .open()?; - - for i in 0..4u8 { - tree.insert([b'k', i].as_slice(), "v", u64::from(i)); - tree.flush_active_memtable(u64::from(i))?; - } - - let before = tree.table_count(); - let fifo = Arc::new(Strategy::new(u64::MAX, None)); - tree.compact(fifo, 4)?; - - assert_eq!(before, tree.table_count()); - Ok(()) - } - - #[test] - fn fifo_more_than_limit() -> crate::Result<()> { - let dir = tempfile::tempdir()?; - let tree = Config::new( - dir.path(), - SequenceNumberCounter::default(), - SequenceNumberCounter::default(), - ) - .open()?; - - for i in 0..4u8 { - tree.insert([b'k', i].as_slice(), "v", u64::from(i)); - tree.flush_active_memtable(u64::from(i))?; - } - - let before = tree.table_count(); - // Very small limit forces dropping oldest tables - let fifo = Arc::new(Strategy::new(1, None)); - tree.compact(fifo, 4)?; - - assert!(tree.table_count() < before); - Ok(()) - } - - #[test] - fn fifo_more_than_limit_blobs() -> crate::Result<()> { - let dir = tempfile::tempdir()?; - let tree = Config::new( - dir.path(), - SequenceNumberCounter::default(), - SequenceNumberCounter::default(), - ) - .with_kv_separation(Some(KvSeparationOptions::default().separation_threshold(1))) - .open()?; - - for i in 0..3u8 { - tree.insert([b'k', i].as_slice(), "$", u64::from(i)); - tree.flush_active_memtable(u64::from(i))?; - } - - let before = tree.table_count(); - let fifo = Arc::new(Strategy::new(1, None)); - tree.compact(fifo, 3)?; - - assert!(tree.table_count() < before); - Ok(()) - } - - #[test] - fn fifo_ttl() -> crate::Result<()> { - let dir = tempfile::tempdir()?; - let tree = Config::new( - dir.path(), - SequenceNumberCounter::default(), - SequenceNumberCounter::default(), - ) - .open()?; - - // Freeze time and create first (older) table at t=1000s - crate::time::set_unix_timestamp_for_test(Some(std::time::Duration::from_secs(1_000))); - tree.insert("a", "1", 0); - tree.flush_active_memtable(0)?; - - // Advance time and create second (newer) table at t=1005s - crate::time::set_unix_timestamp_for_test(Some(std::time::Duration::from_secs(1_005))); - tree.insert("b", "2", 1); - tree.flush_active_memtable(1)?; - - // Now set current time to t=1011s; with TTL=10s, cutoff=1001s => drop first only - crate::time::set_unix_timestamp_for_test(Some(std::time::Duration::from_secs(1_011))); - - assert_eq!(2, tree.table_count()); - - let fifo = Arc::new(Strategy::new(u64::MAX, Some(10))); - tree.compact(fifo, 2)?; - - assert_eq!(1, tree.table_count()); - - // Reset override - crate::time::set_unix_timestamp_for_test(None); - Ok(()) - } - - #[test] - fn fifo_ttl_then_limit_additional_drops_blob_unit() -> crate::Result<()> { - let dir = tempfile::tempdir()?; - let tree = Config::new( - dir.path(), - SequenceNumberCounter::default(), - SequenceNumberCounter::default(), - ) - .with_kv_separation(Some(KvSeparationOptions::default().separation_threshold(1))) - .open()?; - - // Create two tables; we will expire them via time override and force additional drops via limit. - tree.insert("a", "$", 0); - tree.flush_active_memtable(0)?; - tree.insert("b", "$", 1); - tree.flush_active_memtable(1)?; - - crate::time::set_unix_timestamp_for_test(Some(std::time::Duration::from_secs(10_000_000))); - - // TTL=1s will mark both expired; very small limit ensures size-based collection path is also exercised. - let fifo = Arc::new(Strategy::new(1, Some(1))); - tree.compact(fifo, 2)?; - - assert_eq!(0, tree.table_count()); - - crate::time::set_unix_timestamp_for_test(None); - Ok(()) - } -} diff --git a/crates/lsm-tree/src/compaction/filter.rs b/crates/lsm-tree/src/compaction/filter.rs index e1f6f045b..fe06347ea 100644 --- a/crates/lsm-tree/src/compaction/filter.rs +++ b/crates/lsm-tree/src/compaction/filter.rs @@ -3,17 +3,10 @@ //! Compaction filters allow users to run custom logic during compactions, e.g. custom cleanup rules such as TTL. //! Because compactions run in background workers, using compactions filters instead of scans can massively increase the efficiency of the storage engine. use crate::{ - coding::{Decode, Encode}, - compaction::{ - stream::{StreamFilter, StreamFilterVerdict}, - worker::Options, - }, - key::InternalKey, - version::Version, - vlog::{Accessor, BlobFileWriter, ValueHandle}, - BlobIndirection, InternalValue, KvSeparationOptions, UserKey, UserValue, ValueType, + InternalValue, UserKey, UserValue, ValueType, + compaction::stream::{StreamFilter, StreamFilterVerdict}, }; -use std::{panic::RefUnwindSafe, path::Path}; +use std::panic::RefUnwindSafe; /// Verdict returned by a [`CompactionFilter`]. #[non_exhaustive] @@ -91,33 +84,9 @@ pub trait Factory: Send + Sync + RefUnwindSafe { fn make_filter(&self, ctx: &Context) -> Box; } -struct AccessorShared<'a> { - opts: &'a Options, - version: &'a Version, - blobs_folder: &'a Path, -} - -impl AccessorShared<'_> { - /// Fetches a value from the blob store. - fn get_indirect_value( - &self, - user_key: &[u8], - vhandle: &ValueHandle, - ) -> crate::Result> { - Accessor::new(&self.version.blob_files).get( - self.opts.tree_id, - self.blobs_folder, - user_key, - vhandle, - &self.opts.config.cache, - ) - } -} - /// Accessor for the key/value from a compaction filter pub struct ItemAccessor<'a> { item: &'a InternalValue, - shared: &'a AccessorShared<'a>, } impl<'a> ItemAccessor<'a> { @@ -127,45 +96,16 @@ impl<'a> ItemAccessor<'a> { &self.item.key.user_key } - /// Returns whether this item's value is stored separately. - #[must_use] - #[doc(hidden)] - pub fn is_indirection(&self) -> bool { - self.item.key.value_type.is_indirection() - } - /// Get the value of this item. /// /// # Errors /// - /// This method will return an error if blob retrieval fails. + /// This method will return an error if reading the value fails. pub fn value(&self) -> crate::Result { - match self.item.key.value_type { - crate::ValueType::Value => Ok(self.item.value.clone()), - crate::ValueType::Indirection => { - // Resolve and read the value from a blob file. - let mut reader = &self.item.value[..]; - let indirection = BlobIndirection::decode_from(&mut reader)?; - let vhandle = indirection.vhandle; - - let value = self - .shared - .get_indirect_value(&self.item.key.user_key, &vhandle)?; - - if let Some(value) = value { - Ok(value) - } else { - log::error!( - "failed to read referenced blob file during execution of compaction filter. key: {:?}, vptr: {indirection:?}", - self.item.key, - ); - Err(crate::Error::Unrecoverable) - } - } - crate::ValueType::WeakTombstone | crate::ValueType::Tombstone => { - unreachable!("tombstones are filtered out before calling filter") - } + if self.item.is_tombstone() { + unreachable!("tombstones are filtered out before calling filter"); } + Ok(self.item.value.clone()) } } @@ -174,76 +114,12 @@ impl<'a> ItemAccessor<'a> { // NOTE: this slightly helps insulate CompactionStream from lifetime spam pub(crate) struct StreamFilterAdapter<'a, 'b: 'a> { filter: Option<&'a mut (dyn CompactionFilter + 'b)>, - shared: AccessorShared<'a>, - blob_opts: Option<&'a KvSeparationOptions>, - blob_writer: &'a mut Option, ctx: &'a Context, } impl<'a, 'b: 'a> StreamFilterAdapter<'a, 'b> { - pub fn new( - filter: Option<&'a mut (dyn CompactionFilter + 'b)>, - opts: &'a Options, - version: &'a Version, - blobs_folder: &'a Path, - blob_writer: &'a mut Option, - ctx: &'a Context, - ) -> Self { - Self { - filter, - shared: AccessorShared { - opts, - version, - blobs_folder, - }, - blob_opts: opts.config.kv_separation_opts.as_ref(), - blob_writer, - ctx, - } - } - - /// Redirects a write to a blob file if KV separation is enabled and - /// the value meets the separation threshold. - fn handle_write( - &mut self, - prev_key: &InternalKey, - new_value: UserValue, - ) -> crate::Result<(ValueType, UserValue)> { - let Some(blob_opts) = self.blob_opts else { - return Ok((ValueType::Value, new_value)); - }; - - #[expect(clippy::cast_possible_truncation, reason = "values are u32 length max")] - let value_size = new_value.len() as u32; - - if value_size < blob_opts.separation_threshold { - return Ok((ValueType::Value, new_value)); - } - - let writer = if let Some(writer) = self.blob_writer { - writer - } else { - // Instantiate writer as necessary - let writer = BlobFileWriter::new( - self.shared.opts.blob_file_id_generator.clone(), - self.shared.blobs_folder, - self.shared.opts.tree_id, - self.shared.opts.config.descriptor_table.clone(), - )? - .use_target_size(blob_opts.file_target_size) - .use_compression(blob_opts.compression); - - self.blob_writer.insert(writer) - }; - - let vhandle = writer.write(&prev_key.user_key, prev_key.seqno, &new_value)?; - - let indirection = BlobIndirection { - vhandle, - size: value_size, - }; - - Ok((ValueType::Indirection, indirection.encode_into_vec().into())) + pub fn new(filter: Option<&'a mut (dyn CompactionFilter + 'b)>, ctx: &'a Context) -> Self { + Self { filter, ctx } } } @@ -253,13 +129,7 @@ impl<'a, 'b: 'a> StreamFilter for StreamFilterAdapter<'a, 'b> { return Ok(StreamFilterVerdict::Keep); }; - match filter.filter_item( - ItemAccessor { - item, - shared: &self.shared, - }, - self.ctx, - )? { + match filter.filter_item(ItemAccessor { item }, self.ctx)? { Verdict::Destroy => Ok(StreamFilterVerdict::Drop), Verdict::Keep => Ok(StreamFilterVerdict::Keep), Verdict::Remove => Ok(StreamFilterVerdict::Replace(( @@ -270,9 +140,9 @@ impl<'a, 'b: 'a> StreamFilter for StreamFilterAdapter<'a, 'b> { ValueType::WeakTombstone, UserValue::empty(), ))), - Verdict::ReplaceValue(new_value) => self - .handle_write(&item.key, new_value) - .map(StreamFilterVerdict::Replace), + Verdict::ReplaceValue(new_value) => { + Ok(StreamFilterVerdict::Replace((ValueType::Value, new_value))) + } } } } diff --git a/crates/lsm-tree/src/compaction/flavour.rs b/crates/lsm-tree/src/compaction/flavour.rs index 9a5b34723..73ef739e9 100644 --- a/crates/lsm-tree/src/compaction/flavour.rs +++ b/crates/lsm-tree/src/compaction/flavour.rs @@ -2,64 +2,28 @@ // This source code is licensed under both the Apache 2.0 and MIT License // (found in the LICENSE-* files in the repository) -use crate::blob_tree::handle::BlobIndirection; -use crate::blob_tree::FragmentationMap; -use crate::coding::{Decode, Encode}; -use crate::compaction::worker::Options; -use crate::compaction::Input as CompactionPayload; -use crate::file::TABLES_FOLDER; -use crate::table::multi_writer::MultiWriter; -use crate::version::{SuperVersions, Version}; -use crate::vlog::blob_file::scanner::ScanEntry; -use crate::vlog::{BlobFileId, BlobFileMergeScanner, BlobFileWriter}; -use crate::{BlobFile, HashSet, InternalValue, Table}; -use std::iter::Peekable; +use crate::{ + InternalValue, Table, + compaction::{Input as CompactionPayload, worker::Options}, + file::TABLES_FOLDER, + table::multi_writer::MultiWriter, + version::{SuperVersions, Version}, +}; use std::time::Instant; -/// Drains all blobs that come "before" the given vptr. -fn drain_blobs>>( - scanner: &mut Peekable, - key: &[u8], - vptr: &BlobIndirection, -) -> crate::Result<()> { - loop { - let Some(blob) = scanner.next_if(|x| match x { - Ok((entry, blob_file_id)) => { - entry.key != key - || (*blob_file_id != vptr.vhandle.blob_file_id) - || (entry.offset < vptr.vhandle.offset) - } - Err(_) => true, - }) else { - break; - }; - let (entry, _) = blob?; - - assert!(entry.key <= key, "vptr was not matched with blob"); - } - - Ok(()) -} - pub(super) fn prepare_table_writer( version: &Version, opts: &Options, payload: &CompactionPayload, ) -> crate::Result { let table_base_folder = opts.config.path.join(TABLES_FOLDER); - let dst_lvl = payload.canonical_level.into(); - let data_block_size = opts.config.data_block_size_policy.get(dst_lvl); - let data_block_restart_interval = opts.config.data_block_restart_interval_policy.get(dst_lvl); let index_block_restart_interval = opts.config.index_block_restart_interval_policy.get(dst_lvl); - let data_block_compression = opts.config.data_block_compression_policy.get(dst_lvl); let index_block_compression = opts.config.index_block_compression_policy.get(dst_lvl); - let data_block_hash_ratio = opts.config.data_block_hash_ratio_policy.get(dst_lvl); - let index_partitioning = opts.config.index_block_partitioning_policy.get(dst_lvl); let filter_partitioning = opts.config.filter_block_partitioning_policy.get(dst_lvl); @@ -116,219 +80,7 @@ pub(super) fn prepare_table_writer( })) } -// TODO: find a better name -pub(super) trait CompactionFlavour { - fn write(&mut self, item: InternalValue) -> crate::Result<()>; - - #[warn(clippy::too_many_arguments)] - fn finish( - self: Box, - super_version: &mut SuperVersions, - opts: &Options, - payload: &CompactionPayload, - dst_lvl: usize, - blob_frag_map: FragmentationMap, - extra_blob_files: Vec, - ) -> crate::Result<()>; -} - -/// Compaction worker that will relocate blobs that sit in blob files that are being rewritten -pub struct RelocatingCompaction { - inner: StandardCompaction, - blob_scanner: Peekable, - blob_writer: BlobFileWriter, - rewriting_blob_file_ids: HashSet, - rewriting_blob_files: Vec, -} - -impl RelocatingCompaction { - pub fn new( - inner: StandardCompaction, - blob_scanner: Peekable, - blob_writer: BlobFileWriter, - rewriting_blob_files: Vec, - ) -> Self { - Self { - inner, - blob_scanner, - blob_writer, - rewriting_blob_file_ids: rewriting_blob_files.iter().map(BlobFile::id).collect(), - rewriting_blob_files, - } - } - - // TODO: vvv validate/unit test this vvv - fn drain_blobs(&mut self, key: &[u8], indirection: &BlobIndirection) -> crate::Result<()> { - drain_blobs(&mut self.blob_scanner, key, indirection) - } -} - -impl CompactionFlavour for RelocatingCompaction { - fn write(&mut self, item: InternalValue) -> crate::Result<()> { - if item.key.value_type.is_indirection() { - let mut reader = &item.value[..]; - - let indirection = BlobIndirection::decode_from(&mut reader).inspect_err(|e| { - log::error!("Failed to deserialize blob indirection {item:?}: {e:?}"); - })?; - - log::trace!( - "{:?}:{} => encountered indirection: {indirection:?}", - item.key.user_key, - item.key.seqno, - ); - - let indirection = if self - .rewriting_blob_file_ids - .contains(&indirection.vhandle.blob_file_id) - { - self.drain_blobs(&item.key.user_key, &indirection)?; - - #[expect(clippy::expect_used, reason = "vptr is expected to match with blob")] - let (blob_entry, blob_file_id) = self - .blob_scanner - .next() - .expect("vptr was not matched with blob (scanner is unexpectedly exhausted)")?; - - assert_eq!( - blob_file_id, indirection.vhandle.blob_file_id, - "matched blob has different blob file ID than vptr", - ); - assert_eq!( - blob_entry.key, item.key.user_key, - "matched blob has different key than vptr", - ); - assert_eq!( - blob_entry.offset, indirection.vhandle.offset, - "matched blob has different offset than vptr", - ); - - log::trace!( - "=> use blob: {:?}:{} offset: {} from BF {}", - blob_entry.key, - blob_entry.seqno, - blob_entry.offset, - blob_file_id, - ); - - log::trace!("RELOCATE to {indirection:?}"); - - let new_indirection = BlobIndirection { - vhandle: self.blob_writer.write_raw( - &item.key.user_key, - item.key.seqno, - &blob_entry.value, - blob_entry.uncompressed_len, - )?, - size: indirection.size, - }; - - debug_assert_eq!( - new_indirection.vhandle.on_disk_size, indirection.vhandle.on_disk_size, - "redirecting blob should not change its size", - ); - - self.inner - .table_writer - .write(InternalValue::from_components( - item.key.user_key, - new_indirection.encode_into_vec(), - item.key.seqno, - crate::ValueType::Indirection, - ))?; - - new_indirection - } else { - // This blob is not part of the rewritten blob files - // So just pass it through - log::trace!("Pass through {indirection:?} because it is not being relocated"); - self.inner.table_writer.write(item)?; - - indirection - }; - - self.inner.table_writer.register_blob(indirection); - } else { - self.inner.table_writer.write(item)?; - } - - Ok(()) - } - - fn finish( - mut self: Box, - super_version: &mut SuperVersions, - opts: &Options, - payload: &CompactionPayload, - dst_lvl: usize, - blob_frag_map_diff: FragmentationMap, - extra_blob_files: Vec, - ) -> crate::Result<()> { - log::debug!( - "Relocating compaction done in {:?}", - self.inner.start.elapsed(), - ); - - let table_ids_to_delete = std::mem::take(&mut self.inner.tables_to_rewrite); - - let created_tables = self.inner.consume_writer(opts, dst_lvl)?; - let mut created_blob_files = self.blob_writer.finish()?; - created_blob_files.extend(extra_blob_files); - - let mut blob_files_to_drop = self.rewriting_blob_files; - - let current_version = super_version.latest_version(); - - for blob_file in current_version.version.blob_files.iter() { - if blob_file.is_dead(current_version.version.gc_stats()) { - blob_files_to_drop.push(blob_file.clone()); - } - } - - super_version.upgrade_version( - &opts.config.path, - |current| { - let mut copy = current.clone(); - - copy.version = copy.version.with_merge( - &payload.table_ids.iter().copied().collect::>(), - &created_tables, - payload.dest_level as usize, - if blob_frag_map_diff.is_empty() { - None - } else { - Some(blob_frag_map_diff) - }, - created_blob_files, - &blob_files_to_drop - .iter() - .map(BlobFile::id) - .collect::>(), - ); - - Ok(copy) - }, - &opts.global_seqno, - &opts.visible_seqno, - )?; - - // NOTE: If the application were to crash >here< it's fine - // The tables/blob files are not referenced anymore, and will be - // cleaned up upon recovery - for table in table_ids_to_delete { - table.mark_as_deleted(); - } - - for blob_file in blob_files_to_drop { - blob_file.mark_as_deleted(); - } - - Ok(()) - } -} - -/// Standard compaction worker that just passes through all its data -pub struct StandardCompaction { +pub(super) struct StandardCompaction { start: Instant, table_writer: MultiWriter, tables_to_rewrite: Vec
, @@ -343,16 +95,18 @@ impl StandardCompaction { } } - fn consume_writer(self, opts: &Options, dst_lvl: usize) -> crate::Result> { - let table_base_folder = self.table_writer.base_path.clone(); + pub fn write(&mut self, item: InternalValue) -> crate::Result<()> { + self.table_writer.write(item) + } + fn consume_writer(self, opts: &Options, dst_lvl: usize) -> crate::Result> { let pin_filter = opts.config.filter_block_pinning_policy.get(dst_lvl); let pin_index = opts.config.index_block_pinning_policy.get(dst_lvl); + let (table_base_folder, results) = self.table_writer.finish()?; - self.table_writer - .finish()? + results .into_iter() - .map(|(table_id, checksum)| -> crate::Result
{ + .map(|(table_id, checksum)| { Table::recover( table_base_folder.join(table_id.to_string()), checksum, @@ -362,182 +116,42 @@ impl StandardCompaction { opts.config.descriptor_table.clone(), pin_filter, pin_index, - #[cfg(feature = "metrics")] - opts.metrics.clone(), ) }) - .collect::>>() - } -} - -impl CompactionFlavour for StandardCompaction { - fn write(&mut self, item: InternalValue) -> crate::Result<()> { - let indirection = if item.key.value_type.is_indirection() { - Some({ - let mut reader = &item.value[..]; - BlobIndirection::decode_from(&mut reader)? - }) - } else { - None - }; - - self.table_writer.write(item)?; - - if let Some(indirection) = indirection { - self.table_writer.register_blob(indirection); - } - - Ok(()) + .collect() } - fn finish( - mut self: Box, + pub fn finish( + mut self, super_version: &mut SuperVersions, opts: &Options, payload: &CompactionPayload, dst_lvl: usize, - blob_frag_map: FragmentationMap, - extra_blob_files: Vec, ) -> crate::Result<()> { log::debug!("Compaction done in {:?}", self.start.elapsed()); - let table_ids_to_delete = std::mem::take(&mut self.tables_to_rewrite); - + let tables_to_delete = std::mem::take(&mut self.tables_to_rewrite); let created_tables = self.consume_writer(opts, dst_lvl)?; - let mut blob_files_to_drop = Vec::default(); - - let current_version = super_version.latest_version(); - - for blob_file in current_version.version.blob_files.iter() { - if blob_file.is_dead(current_version.version.gc_stats()) { - blob_files_to_drop.push(blob_file.clone()); - } - } - super_version.upgrade_version( &opts.config.path, |current| { let mut copy = current.clone(); - copy.version = copy.version.with_merge( &payload.table_ids.iter().copied().collect::>(), &created_tables, payload.dest_level as usize, - if blob_frag_map.is_empty() { - None - } else { - Some(blob_frag_map) - }, - extra_blob_files, - &blob_files_to_drop - .iter() - .map(BlobFile::id) - .collect::>(), ); - Ok(copy) }, &opts.global_seqno, &opts.visible_seqno, )?; - // NOTE: If the application were to crash >here< it's fine - // The tables are not referenced anymore, and will be - // cleaned up upon recovery - for table in table_ids_to_delete { + for table in tables_to_delete { table.mark_as_deleted(); } - for blob_file in blob_files_to_drop { - blob_file.mark_as_deleted(); - } - - Ok(()) - } -} - -#[cfg(test)] -#[expect(clippy::unwrap_used)] -mod tests { - use super::*; - use crate::{vlog::ValueHandle, UserKey, UserValue}; - - #[expect(clippy::unnecessary_wraps)] - fn entry( - blob_file_id: BlobFileId, - key: &[u8], - offset: u64, - ) -> crate::Result<(ScanEntry, BlobFileId)> { - Ok(( - ScanEntry { - key: UserKey::from(key), - offset, - seqno: 0, - uncompressed_len: 0, - value: UserValue::empty(), - }, - blob_file_id, - )) - } - - #[test] - fn drain_blobs_simple() -> crate::Result<()> { - let mut iter = [ - entry(0, b"a", 0), - entry(0, b"a", 1), - entry(0, b"a", 2), - entry(0, b"a", 3), - entry(0, b"a", 4), - ] - .into_iter() - .peekable(); - - drain_blobs( - &mut iter, - b"a", - &BlobIndirection { - size: 0, - vhandle: ValueHandle { - blob_file_id: 0, - offset: 4, - on_disk_size: 0, - }, - }, - )?; - - assert_eq!(entry(0, b"a", 4)?, iter.next().unwrap()?); - - Ok(()) - } - - #[test] - fn drain_blobs_multiple_keys() -> crate::Result<()> { - let mut iter = [ - entry(0, b"a", 0), - entry(0, b"b", 0), - entry(0, b"c", 0), - entry(0, b"d", 0), - entry(0, b"e", 0), - ] - .into_iter() - .peekable(); - - drain_blobs( - &mut iter, - b"e", - &BlobIndirection { - size: 0, - vhandle: ValueHandle { - blob_file_id: 0, - offset: 0, - on_disk_size: 0, - }, - }, - )?; - - assert_eq!(entry(0, b"e", 0)?, iter.next().unwrap()?); - Ok(()) } } diff --git a/crates/lsm-tree/src/compaction/leveled/mod.rs b/crates/lsm-tree/src/compaction/leveled/mod.rs index f4bd190ab..b8b2f7439 100644 --- a/crates/lsm-tree/src/compaction/leveled/mod.rs +++ b/crates/lsm-tree/src/compaction/leveled/mod.rs @@ -7,12 +7,12 @@ mod test; use super::{Choice, CompactionStrategy, Input as CompactionInput}; use crate::{ - compaction::state::{hidden_set::HiddenSet, CompactionState}, + HashSet, TableId, + compaction::state::{CompactionState, hidden_set::HiddenSet}, config::Config, slice_windows::{GrowingWindowsExt, ShrinkingWindowsExt}, - table::{util::aggregate_run_key_range, Table}, + table::{Table, util::aggregate_run_key_range}, version::{Run, Version}, - HashSet, TableId, }; /// Tries to find the most optimal compaction set from one level into the other. @@ -237,42 +237,6 @@ impl CompactionStrategy for Strategy { NAME } - fn get_config(&self) -> Vec { - vec![ - ( - crate::UserKey::from("leveled_l0_threshold"), - crate::UserValue::from(self.l0_threshold.to_le_bytes()), - ), - ( - crate::UserKey::from("leveled_target_size"), - crate::UserValue::from(self.target_size.to_le_bytes()), - ), - ( - crate::UserKey::from("leveled_level_ratio_policy"), - crate::UserValue::from({ - use byteorder::{LittleEndian, WriteBytesExt}; - - let mut v = vec![]; - - #[expect( - clippy::expect_used, - clippy::cast_possible_truncation, - reason = "writing into Vec should not fail; policies have length of 255 max" - )] - v.write_u8(self.level_ratio_policy.len() as u8) - .expect("cannot fail"); - - for &f in &self.level_ratio_policy { - #[expect(clippy::expect_used, reason = "writing into Vec should not fail")] - v.write_f32::(f).expect("cannot fail"); - } - - v - }), - ), - ] - } - #[expect(clippy::too_many_lines)] fn choose(&self, version: &Version, _: &Config, state: &CompactionState) -> Choice { assert!(version.level_count() == 7, "should have exactly 7 levels"); diff --git a/crates/lsm-tree/src/compaction/leveled/test.rs b/crates/lsm-tree/src/compaction/leveled/test.rs index 5486c86d8..2022d5447 100644 --- a/crates/lsm-tree/src/compaction/leveled/test.rs +++ b/crates/lsm-tree/src/compaction/leveled/test.rs @@ -116,8 +116,8 @@ fn leveled_l0_reached_limit_disjoint_l1() -> crate::Result<()> { tree.flush_active_memtable(0)?; } - let fifo = Arc::new(Strategy::default()); - tree.compact(fifo, 0)?; + let strategy = Arc::new(Strategy::default()); + tree.compact(strategy, 0)?; assert_eq!(1, tree.table_count()); diff --git a/crates/lsm-tree/src/compaction/maintenance.rs b/crates/lsm-tree/src/compaction/maintenance.rs deleted file mode 100644 index aa0974c7a..000000000 --- a/crates/lsm-tree/src/compaction/maintenance.rs +++ /dev/null @@ -1,179 +0,0 @@ -// Copyright (c) 2024-present, fjall-rs -// This source code is licensed under both the Apache 2.0 and MIT License -// (found in the LICENSE-* files in the repository) - -use super::{Choice, CompactionStrategy}; -use crate::{config::Config, level_manifest::LevelManifest, segment::Segment, HashSet, SegmentId}; - -const L0_SEGMENT_CAP: usize = 20; - -/// Maintenance compactor -/// -/// This is a hidden compaction strategy that may be called by other strategies. -/// -/// It cleans up L0 if it grows too large. -#[derive(Default)] -pub struct Strategy; - -/// Choose a run of segments that has the least file size sum. -/// -/// This minimizes the compaction time (+ write amp) for a set of segments we -/// want to partially compact. -pub fn choose_least_effort_compaction(segments: &[Segment], n: usize) -> HashSet { - let num_segments = segments.len(); - - // Ensure that n is not greater than the number of segments - assert!( - n <= num_segments, - "N must be less than or equal to the number of segments" - ); - - let windows = segments.windows(n); - - let window = windows - .min_by_key(|window| window.iter().map(|s| s.file_size()).sum::()) - .expect("should have at least one window"); - - window.iter().map(Segment::id).collect() -} - -impl CompactionStrategy for Strategy { - fn get_name(&self) -> &'static str { - "MaintenanceStrategy" - } - - fn choose(&self, levels: &LevelManifest, _: &Config) -> Choice { - todo!() - } -} -/* -#[cfg(test)] -mod tests { - use super::*; - use crate::{ - cache::Cache, - compaction::{Choice, CompactionStrategy}, - config::Config, - descriptor_table::FileDescriptorTable, - file::LEVELS_MANIFEST_FILE, - level_manifest::LevelManifest, - segment::{ - block::offset::BlockOffset, - block_index::{two_level_index::TwoLevelBlockIndex, BlockIndexImpl}, - file_offsets::FileOffsets, - meta::Metadata, - SegmentInner, - }, - super_segment::Segment, - KeyRange, - }; - use std::sync::{atomic::AtomicBool, Arc}; - use test_log::test; - - #[allow(clippy::expect_used)] - fn fixture_segment(id: SegmentId, created_at: u128) -> Segment { - todo!() - /* let cache = Arc::new(Cache::with_capacity_bytes(10 * 1_024 * 1_024)); - - let block_index = TwoLevelBlockIndex::new((0, id).into(), cache.clone()); - let block_index = Arc::new(BlockIndexImpl::TwoLevel(block_index)); - - SegmentInner { - tree_id: 0, - descriptor_table: Arc::new(FileDescriptorTable::new(512, 1)), - block_index, - - offsets: FileOffsets { - bloom_ptr: BlockOffset(0), - range_filter_ptr: BlockOffset(0), - index_block_ptr: BlockOffset(0), - metadata_ptr: BlockOffset(0), - range_tombstones_ptr: BlockOffset(0), - tli_ptr: BlockOffset(0), - pfx_ptr: BlockOffset(0), - }, - - metadata: Metadata { - data_block_count: 0, - index_block_count: 0, - data_block_size: 4_096, - index_block_size: 4_096, - created_at, - id, - file_size: 1, - compression: crate::segment::meta::CompressionType::None, - table_type: crate::segment::meta::TableType::Block, - item_count: 0, - key_count: 0, - key_range: KeyRange::new((vec![].into(), vec![].into())), - tombstone_count: 0, - range_tombstone_count: 0, - uncompressed_size: 0, - seqnos: (0, created_at as u64), - }, - cache, - - bloom_filter: Some(crate::bloom::BloomFilter::with_fp_rate(1, 0.1)), - - path: "a".into(), - is_deleted: AtomicBool::default(), - } - .into() */ - } - - #[test] - fn maintenance_empty_level() -> crate::Result<()> { - let tempdir = tempfile::tempdir()?; - let compactor = Strategy; - - let levels = LevelManifest::create_new(4, tempdir.path().join(LEVELS_MANIFEST_FILE))?; - - assert_eq!( - compactor.choose(&levels, &Config::default()), - Choice::DoNothing - ); - - Ok(()) - } - - #[test] - fn maintenance_below_limit() -> crate::Result<()> { - let tempdir = tempfile::tempdir()?; - let compactor = Strategy; - - let mut levels = LevelManifest::create_new(4, tempdir.path().join(LEVELS_MANIFEST_FILE))?; - for id in 0..5 { - levels.add(fixture_segment(id, u128::from(id))); - } - - assert_eq!( - compactor.choose(&levels, &Config::default()), - Choice::DoNothing - ); - - Ok(()) - } - - #[test] - fn maintenance_l0_too_large() -> crate::Result<()> { - let tempdir = tempfile::tempdir()?; - let compactor = Strategy; - - let mut levels = LevelManifest::create_new(4, tempdir.path().join(LEVELS_MANIFEST_FILE))?; - for id in 0..(L0_SEGMENT_CAP + 2) { - levels.add(fixture_segment(id as u64, id as u128)); - } - - assert_eq!( - compactor.choose(&levels, &Config::default()), - Choice::Merge(crate::compaction::Input { - dest_level: 0, - segment_ids: [0, 1, 2].into_iter().collect::>(), - target_size: u64::MAX - }) - ); - - Ok(()) - } -} - */ diff --git a/crates/lsm-tree/src/compaction/major.rs b/crates/lsm-tree/src/compaction/major.rs index 0e973cc87..8a5241fce 100644 --- a/crates/lsm-tree/src/compaction/major.rs +++ b/crates/lsm-tree/src/compaction/major.rs @@ -4,7 +4,7 @@ use super::{Choice, CompactionStrategy, Input as CompactionInput}; use crate::{ - compaction::state::CompactionState, config::Config, table::Table, version::Version, HashSet, + HashSet, compaction::state::CompactionState, config::Config, table::Table, version::Version, }; /// Compacts all tables into the last level diff --git a/crates/lsm-tree/src/compaction/mod.rs b/crates/lsm-tree/src/compaction/mod.rs index 8aeea631c..5a3385628 100644 --- a/crates/lsm-tree/src/compaction/mod.rs +++ b/crates/lsm-tree/src/compaction/mod.rs @@ -4,38 +4,26 @@ //! Contains compaction strategies -pub(crate) mod fifo; -pub(crate) mod leveled; -// pub(crate) mod maintenance; -pub(crate) mod drop_range; pub mod filter; mod flavour; +pub(crate) mod leveled; pub(crate) mod major; -pub(crate) mod movedown; pub(crate) mod pulldown; pub(crate) mod state; pub(crate) mod stream; -// pub(crate) mod tiered; pub(crate) mod worker; -pub use fifo::Strategy as Fifo; pub use filter::{CompactionFilter, Factory, ItemAccessor, Verdict}; pub use leveled::Strategy as Leveled; -// pub use tiered::Strategy as SizeTiered; - -pub use {fifo::NAME as FIFO_COMPACTION_NAME, leveled::NAME as LEVELED_COMPACTION_NAME}; /// Alias for `Leveled` pub type Levelled = Leveled; -#[doc(hidden)] -pub use movedown::Strategy as MoveDown; - #[doc(hidden)] pub use pulldown::Strategy as PullDown; use crate::{ - compaction::state::CompactionState, config::Config, version::Version, HashSet, KvPair, TableId, + HashSet, TableId, compaction::state::CompactionState, config::Config, version::Version, }; /// Input for compactor @@ -71,12 +59,6 @@ pub enum Choice { /// Compacts some tables into a new level. Merge(Input), - - /// Delete tables without doing compaction. - /// - /// This may be used by a compaction strategy that wants to delete old data - /// without having to compact it away, like [`fifo::Strategy`]. - Drop(HashSet), } /// Trait for a compaction strategy @@ -88,11 +70,6 @@ pub trait CompactionStrategy { /// Gets the compaction strategy name. fn get_name(&self) -> &'static str; - #[doc(hidden)] - fn get_config(&self) -> Vec { - vec![] - } - /// Decides on what to do based on the current state of the LSM-tree's levels fn choose(&self, version: &Version, config: &Config, state: &CompactionState) -> Choice; } diff --git a/crates/lsm-tree/src/compaction/movedown.rs b/crates/lsm-tree/src/compaction/movedown.rs deleted file mode 100644 index 2b9902a88..000000000 --- a/crates/lsm-tree/src/compaction/movedown.rs +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright (c) 2024-present, fjall-rs -// This source code is licensed under both the Apache 2.0 and MIT License -// (found in the LICENSE-* files in the repository) - -use super::{Choice, CompactionStrategy, Input}; -use crate::{compaction::state::CompactionState, table::Table, version::Version, Config}; - -/// Moves down a level into the destination level. -pub struct Strategy(pub u8, pub u8); - -impl CompactionStrategy for Strategy { - fn get_name(&self) -> &'static str { - "MoveDownCompaction" - } - - fn choose(&self, version: &Version, _: &Config, state: &CompactionState) -> Choice { - if version.level_is_busy(usize::from(self.0), state.hidden_set()) { - return Choice::DoNothing; - } - - let Some(level) = version.level(self.0.into()) else { - return Choice::DoNothing; - }; - - let table_ids = level - .iter() - .flat_map(|run| run.iter()) - .map(Table::id) - .collect(); - - Choice::Move(Input { - table_ids, - dest_level: self.1, - canonical_level: self.1, - target_size: u64::MAX, - }) - } -} diff --git a/crates/lsm-tree/src/compaction/pulldown.rs b/crates/lsm-tree/src/compaction/pulldown.rs index 09a558fd5..135f642ae 100644 --- a/crates/lsm-tree/src/compaction/pulldown.rs +++ b/crates/lsm-tree/src/compaction/pulldown.rs @@ -4,9 +4,9 @@ use super::{Choice, CompactionStrategy}; use crate::{ - compaction::{state::CompactionState, Input}, - version::Version, Config, + compaction::{Input, state::CompactionState}, + version::Version, }; /// Pulls down and merges a level into the destination level. diff --git a/crates/lsm-tree/src/compaction/state/mod.rs b/crates/lsm-tree/src/compaction/state/mod.rs index 3a4d10fce..332eb42f3 100644 --- a/crates/lsm-tree/src/compaction/state/mod.rs +++ b/crates/lsm-tree/src/compaction/state/mod.rs @@ -61,10 +61,6 @@ mod tests { let table_count_before_major_compact = tree.table_count(); - let crate::AnyTree::Standard(tree) = tree else { - unreachable!(); - }; - // { // // NOTE: Purposefully change level manifest to have invalid path // // to force an I/O error @@ -76,12 +72,13 @@ mod tests { assert!(tree.major_compact(u64::MAX, 4).is_err()); - assert!(tree - .compaction_state - .lock() - .expect("lock is poisoned") - .hidden_set() - .is_empty()); + assert!( + tree.compaction_state + .lock() + .expect("lock is poisoned") + .hidden_set() + .is_empty() + ); assert_eq!(table_count_before_major_compact, tree.table_count()); diff --git a/crates/lsm-tree/src/compaction/stream.rs b/crates/lsm-tree/src/compaction/stream.rs index 45a63e4c9..6857cf35d 100644 --- a/crates/lsm-tree/src/compaction/stream.rs +++ b/crates/lsm-tree/src/compaction/stream.rs @@ -7,14 +7,6 @@ use std::iter::Peekable; type Item = crate::Result; -/// A callback that receives all dropped KVs -/// -/// Used for counting blobs that are not referenced anymore because of -/// vHandles that are being dropped through compaction. -pub trait DroppedKvCallback { - fn on_dropped(&mut self, kv: &InternalValue); -} - /// Verdict returned by [`StreamFilter`] #[derive(Debug)] pub enum StreamFilterVerdict { @@ -46,16 +38,13 @@ impl StreamFilter for NoFilter { /// Consumes a stream of KVs and emits a new stream according to GC and tombstone rules /// /// This iterator is used during flushing & compaction. -pub struct CompactionStream<'a, I: Iterator, F: StreamFilter = NoFilter> { +pub struct CompactionStream, F: StreamFilter = NoFilter> { /// KV stream inner: Peekable, /// MVCC watermark to get rid of old versions gc_seqno_threshold: SeqNo, - /// Event emitter that receives all dropped KVs - dropped_callback: Option<&'a mut dyn DroppedKvCallback>, - /// Stream filter filter: F, @@ -64,7 +53,7 @@ pub struct CompactionStream<'a, I: Iterator, F: StreamFilter = NoFi zero_seqnos: bool, } -impl> CompactionStream<'_, I, NoFilter> { +impl> CompactionStream { /// Initializes a new merge iterator #[must_use] pub fn new(iter: I, gc_seqno_threshold: SeqNo) -> Self { @@ -73,7 +62,6 @@ impl> CompactionStream<'_, I, NoFilter> { Self { inner: iter, gc_seqno_threshold, - dropped_callback: None, filter: NoFilter, evict_tombstones: false, zero_seqnos: false, @@ -81,13 +69,12 @@ impl> CompactionStream<'_, I, NoFilter> { } } -impl<'a, I: Iterator, F: StreamFilter + 'a> CompactionStream<'a, I, F> { +impl, F: StreamFilter> CompactionStream { /// Installs a filter into this stream. - pub fn with_filter(self, filter: NF) -> CompactionStream<'a, I, NF> { + pub fn with_filter(self, filter: NF) -> CompactionStream { CompactionStream { inner: self.inner, gc_seqno_threshold: self.gc_seqno_threshold, - dropped_callback: self.dropped_callback, filter, evict_tombstones: self.evict_tombstones, zero_seqnos: self.zero_seqnos, @@ -99,12 +86,6 @@ impl<'a, I: Iterator, F: StreamFilter + 'a> CompactionStream<'a, I, self } - /// Installs a callback that receives all dropped KVs. - pub fn with_drop_callback(mut self, cb: &'a mut dyn DroppedKvCallback) -> Self { - self.dropped_callback = Some(cb); - self - } - /// Sets sequence numbers to zero if they are below the snapshot watermark. /// /// This can save a lot of space, because "0" only takes 1 byte, and sequence numbers are monotonically increasing. @@ -118,15 +99,7 @@ impl<'a, I: Iterator, F: StreamFilter + 'a> CompactionStream<'a, I, loop { let Some(next) = self.inner.next_if(|kv| { if let Ok(kv) = kv { - let expired = kv.key.user_key == key; - - if expired { - if let Some(watcher) = &mut self.dropped_callback { - watcher.on_dropped(kv); - } - } - - expired + kv.key.user_key == key } else { true } @@ -139,7 +112,7 @@ impl<'a, I: Iterator, F: StreamFilter + 'a> CompactionStream<'a, I, } } -impl<'a, I: Iterator, F: StreamFilter + 'a> Iterator for CompactionStream<'a, I, F> { +impl, F: StreamFilter> Iterator for CompactionStream { type Item = Item; fn next(&mut self) -> Option { @@ -150,18 +123,10 @@ impl<'a, I: Iterator, F: StreamFilter + 'a> Iterator for Compaction match fail_iter!(self.filter.filter_item(&head)) { StreamFilterVerdict::Keep => { /* Do nothing */ } StreamFilterVerdict::Replace((new_type, new_value)) => { - // If we are replacing this item's value, call the dropped callback for the previous item - if let Some(watcher) = &mut self.dropped_callback { - watcher.on_dropped(&head); - } head.value = new_value; head.key.value_type = new_type; } StreamFilterVerdict::Drop => { - if let Some(watcher) = &mut self.dropped_callback { - watcher.on_dropped(&head); - } - // Ignore continue; } @@ -223,7 +188,7 @@ impl<'a, I: Iterator, F: StreamFilter + 'a> Iterator for Compaction #[cfg(test)] mod tests { use super::*; - use crate::{value::InternalValue, ValueType}; + use crate::{ValueType, value::InternalValue}; use test_log::test; macro_rules! stream { @@ -259,49 +224,6 @@ mod tests { }; } - #[derive(Default)] - struct TrackCallback { - items: Vec, - } - - impl DroppedKvCallback for TrackCallback { - fn on_dropped(&mut self, kv: &InternalValue) { - self.items.push(kv.clone()); - } - } - - #[test] - #[expect(clippy::unwrap_used)] - fn compaction_stream_expired_callback_1() -> crate::Result<()> { - #[rustfmt::skip] - let vec = stream![ - "a", "", "T", - "a", "", "T", - "a", "", "T", - ]; - - let mut my_watcher = TrackCallback::default(); - - let iter = vec.iter().cloned().map(Ok); - let mut iter = CompactionStream::new(iter, 1_000).with_drop_callback(&mut my_watcher); - - assert_eq!( - InternalValue::from_components(*b"a", *b"", 999, ValueType::Tombstone), - iter.next().unwrap()?, - ); - iter_closed!(iter); - - assert_eq!( - [ - InternalValue::from_components("a", "", 998, ValueType::Tombstone), - InternalValue::from_components("a", "", 997, ValueType::Tombstone), - ], - &*my_watcher.items, - ); - - Ok(()) - } - #[test] #[expect(clippy::unwrap_used)] fn compaction_stream_seqno_zeroing_1() -> crate::Result<()> { @@ -648,11 +570,8 @@ mod tests { "b", "b", "V", ]; - let mut drop_cb = TrackCallback { items: vec![] }; let iter = vec.iter().cloned().map(Ok); - let iter = CompactionStream::new(iter, 995) - .with_filter(Filter(b"7")) - .with_drop_callback(&mut drop_cb); + let iter = CompactionStream::new(iter, 995).with_filter(Filter(b"7")); let out: Vec<_> = iter.map(Result::unwrap).collect(); @@ -664,21 +583,11 @@ mod tests { "a", "", "T", "a", "", "T", ]); - - let fc = InternalValue::from_components; - - #[rustfmt::skip] - assert_eq!(drop_cb.items, [ - fc(b"a", b"6", 996, ValueType::Value), - fc(b"a", b"5", 995, ValueType::Value), - fc(b"a", b"4", 994, ValueType::Value), - fc(b"b", b"b", 999, ValueType::Value), - ]); } pub mod custom_mvcc { use super::*; - use byteorder::{ReadBytesExt, WriteBytesExt, BE}; + use byteorder::{BE, ReadBytesExt, WriteBytesExt}; use test_log::test; /// MVCC trailer size (anything but user key) @@ -774,14 +683,11 @@ mod tests { kv(b"abc", 2, b"a", false), ]; - let mut drop_cb = TrackCallback { items: vec![] }; let iter = vec.iter().cloned().map(Ok); - let iter = CompactionStream::new(iter, 995) - .with_filter(Filter { - mvcc_watermark: 5, - prev_user_key: None, - }) - .with_drop_callback(&mut drop_cb); + let iter = CompactionStream::new(iter, 995).with_filter(Filter { + mvcc_watermark: 5, + prev_user_key: None, + }); let out: Vec<_> = iter.map(Result::unwrap).collect(); @@ -807,14 +713,11 @@ mod tests { kv(b"d", 0, b"c", false), ]; - let mut drop_cb = TrackCallback { items: vec![] }; let iter = vec.iter().cloned().map(Ok); - let iter = CompactionStream::new(iter, 995) - .with_filter(Filter { - mvcc_watermark: 3, - prev_user_key: None, - }) - .with_drop_callback(&mut drop_cb); + let iter = CompactionStream::new(iter, 995).with_filter(Filter { + mvcc_watermark: 3, + prev_user_key: None, + }); let out: Vec<_> = iter.map(Result::unwrap).collect(); diff --git a/crates/lsm-tree/src/compaction/tiered.rs b/crates/lsm-tree/src/compaction/tiered.rs deleted file mode 100644 index 7ee618766..000000000 --- a/crates/lsm-tree/src/compaction/tiered.rs +++ /dev/null @@ -1,343 +0,0 @@ -// Copyright (c) 2024-present, fjall-rs -// This source code is licensed under both the Apache 2.0 and MIT License -// (found in the LICENSE-* files in the repository) - -use super::{Choice, CompactionStrategy}; -use crate::{compaction::state::CompactionState, version::Version, Config}; - -// fn desired_level_size_in_bytes(level_idx: u8, ratio: u8, base_size: u32) -> usize { -// (ratio as usize).pow(u32::from(level_idx + 1)) * (base_size as usize) -// } - -/// Size-tiered compaction strategy (STCS) -/// -/// If a level reaches a threshold, it is merged into a larger table to the next level. -/// -/// STCS suffers from high read and temporary doubled space amplification, but has good write amplification. -#[derive(Clone)] -pub struct Strategy { - /// Base size - pub base_size: u32, - - /// Size ratio between levels of the LSM tree (a.k.a fanout, growth rate). - /// - /// This is the exponential growth of the from one - /// level to the next - /// - /// A level target size is: base_size * level_ratio.pow(#level + 1) - #[expect(clippy::doc_markdown)] - pub level_ratio: u8, -} - -impl Strategy { - /// Creates a new STCS strategy with custom base size - #[must_use] - pub fn new(base_size: u32, level_ratio: u8) -> Self { - Self { - base_size, - level_ratio, - } - } -} - -impl Default for Strategy { - fn default() -> Self { - Self { - base_size: 64 * 1_024 * 1_024, - level_ratio: 4, - } - } -} - -impl CompactionStrategy for Strategy { - fn get_name(&self) -> &'static str { - "TieredStrategy" - } - - fn choose(&self, version: &Version, _config: &Config, state: &CompactionState) -> Choice { - unimplemented!() - } -} -/* -#[cfg(test)] -mod tests { - use super::Strategy; - use crate::{ - cache::Cache, - compaction::{Choice, CompactionStrategy, Input as CompactionInput}, - config::Config, - descriptor_table::FileDescriptorTable, - file::LEVELS_MANIFEST_FILE, - level_manifest::LevelManifest, - segment::{ - block::offset::BlockOffset, - block_index::{two_level_index::TwoLevelBlockIndex, BlockIndexImpl}, - file_offsets::FileOffsets, - meta::{Metadata, SegmentId}, - SegmentInner, - }, - super_segment::Segment, - HashSet, KeyRange, SeqNo, - }; - use std::sync::{atomic::AtomicBool, Arc}; - use test_log::test; - - #[allow(clippy::expect_used)] - fn fixture_segment(id: SegmentId, size_mib: u64, max_seqno: SeqNo) -> Segment { - todo!() - - /* let cache = Arc::new(Cache::with_capacity_bytes(10 * 1_024 * 1_024)); - - let block_index = TwoLevelBlockIndex::new((0, id).into(), cache.clone()); - let block_index = Arc::new(BlockIndexImpl::TwoLevel(block_index)); - - SegmentInner { - tree_id: 0, - descriptor_table: Arc::new(FileDescriptorTable::new(512, 1)), - block_index, - - offsets: FileOffsets { - bloom_ptr: BlockOffset(0), - range_filter_ptr: BlockOffset(0), - index_block_ptr: BlockOffset(0), - metadata_ptr: BlockOffset(0), - range_tombstones_ptr: BlockOffset(0), - tli_ptr: BlockOffset(0), - pfx_ptr: BlockOffset(0), - }, - - metadata: Metadata { - data_block_count: 0, - index_block_count: 0, - data_block_size: 4_096, - index_block_size: 4_096, - created_at: 0, - id, - file_size: size_mib * 1_024 * 1_024, - compression: crate::segment::meta::CompressionType::None, - table_type: crate::segment::meta::TableType::Block, - item_count: 0, - key_count: 0, - key_range: KeyRange::new((vec![].into(), vec![].into())), - tombstone_count: 0, - range_tombstone_count: 0, - uncompressed_size: size_mib * 1_024 * 1_024, - seqnos: (0, max_seqno), - }, - cache, - - bloom_filter: Some(BloomFilter::with_fp_rate(1, 0.1)), - - path: "a".into(), - is_deleted: AtomicBool::default(), - } - .into() */ - } - - #[test] - fn tiered_empty_levels() -> crate::Result<()> { - let tempdir = tempfile::tempdir()?; - - let compactor = Strategy { - base_size: 8 * 1_024 * 1_024, - level_ratio: 8, - }; - - let levels = LevelManifest::create_new(4, tempdir.path().join(LEVELS_MANIFEST_FILE))?; - - assert_eq!( - compactor.choose(&levels, &Config::default()), - Choice::DoNothing - ); - - Ok(()) - } - - #[test] - fn tiered_default_l0() -> crate::Result<()> { - let tempdir = tempfile::tempdir()?; - - let compactor = Strategy { - base_size: 8 * 1_024 * 1_024, - level_ratio: 4, - }; - let config = Config::default(); - - let mut levels = LevelManifest::create_new(4, tempdir.path().join(LEVELS_MANIFEST_FILE))?; - - levels.add(fixture_segment(1, 8, 5)); - assert_eq!(compactor.choose(&levels, &config), Choice::DoNothing); - - levels.add(fixture_segment(2, 8, 6)); - assert_eq!(compactor.choose(&levels, &config), Choice::DoNothing); - - levels.add(fixture_segment(3, 8, 7)); - assert_eq!(compactor.choose(&levels, &config), Choice::DoNothing); - - levels.add(fixture_segment(4, 8, 8)); - - assert_eq!( - compactor.choose(&levels, &config), - Choice::Merge(CompactionInput { - dest_level: 1, - segment_ids: set![1, 2, 3, 4], - target_size: u64::MAX, - }) - ); - - Ok(()) - } - - #[test] - fn tiered_ordering() -> crate::Result<()> { - let tempdir = tempfile::tempdir()?; - - let compactor = Strategy { - base_size: 8 * 1_024 * 1_024, - level_ratio: 2, - }; - let config = Config::default(); - - let mut levels = LevelManifest::create_new(4, tempdir.path().join(LEVELS_MANIFEST_FILE))?; - - levels.add(fixture_segment(1, 8, 0)); - levels.add(fixture_segment(2, 8, 1)); - levels.add(fixture_segment(3, 8, 2)); - levels.add(fixture_segment(4, 8, 3)); - - assert_eq!( - compactor.choose(&levels, &config), - Choice::Merge(CompactionInput { - dest_level: 1, - segment_ids: set![1, 2], - target_size: u64::MAX, - }) - ); - - Ok(()) - } - - #[test] - fn tiered_more_than_min() -> crate::Result<()> { - let tempdir = tempfile::tempdir()?; - - let compactor = Strategy { - base_size: 8 * 1_024 * 1_024, - level_ratio: 4, - }; - let config = Config::default(); - - let mut levels = LevelManifest::create_new(4, tempdir.path().join(LEVELS_MANIFEST_FILE))?; - levels.add(fixture_segment(1, 8, 5)); - levels.add(fixture_segment(2, 8, 6)); - levels.add(fixture_segment(3, 8, 7)); - levels.add(fixture_segment(4, 8, 8)); - - levels.insert_into_level(1, fixture_segment(5, 8 * 4, 9)); - levels.insert_into_level(1, fixture_segment(6, 8 * 4, 10)); - levels.insert_into_level(1, fixture_segment(7, 8 * 4, 11)); - levels.insert_into_level(1, fixture_segment(8, 8 * 4, 12)); - - assert_eq!( - compactor.choose(&levels, &config), - Choice::Merge(CompactionInput { - dest_level: 2, - segment_ids: set![5, 6, 7, 8], - target_size: u64::MAX, - }) - ); - - Ok(()) - } - - #[test] - fn tiered_many_segments() -> crate::Result<()> { - let tempdir = tempfile::tempdir()?; - - let compactor = Strategy { - base_size: 8 * 1_024 * 1_024, - level_ratio: 2, - }; - let config = Config::default(); - - let mut levels = LevelManifest::create_new(4, tempdir.path().join(LEVELS_MANIFEST_FILE))?; - levels.add(fixture_segment(1, 8, 5)); - levels.add(fixture_segment(2, 8, 6)); - levels.add(fixture_segment(3, 8, 7)); - levels.add(fixture_segment(4, 8, 8)); - - assert_eq!( - compactor.choose(&levels, &config), - Choice::Merge(CompactionInput { - dest_level: 1, - segment_ids: set![1, 2], - target_size: u64::MAX, - }) - ); - - Ok(()) - } - - #[test] - fn tiered_deeper_level() -> crate::Result<()> { - let tempdir = tempfile::tempdir()?; - - let compactor = Strategy { - base_size: 8 * 1_024 * 1_024, - level_ratio: 2, - }; - let config = Config::default(); - - let mut levels = LevelManifest::create_new(4, tempdir.path().join(LEVELS_MANIFEST_FILE))?; - levels.add(fixture_segment(1, 8, 5)); - - levels.insert_into_level(1, fixture_segment(2, 8 * 2, 6)); - levels.insert_into_level(1, fixture_segment(3, 8 * 2, 7)); - - assert_eq!( - compactor.choose(&levels, &config), - Choice::Merge(CompactionInput { - dest_level: 2, - segment_ids: set![2, 3], - target_size: u64::MAX, - }) - ); - - let tempdir = tempfile::tempdir()?; - let mut levels = LevelManifest::create_new(4, tempdir.path().join(LEVELS_MANIFEST_FILE))?; - - levels.insert_into_level(2, fixture_segment(2, 8 * 4, 5)); - levels.insert_into_level(2, fixture_segment(3, 8 * 4, 6)); - - assert_eq!( - compactor.choose(&levels, &config), - Choice::Merge(CompactionInput { - dest_level: 3, - segment_ids: set![2, 3], - target_size: u64::MAX, - }) - ); - - Ok(()) - } - - #[test] - fn tiered_last_level() -> crate::Result<()> { - let tempdir = tempfile::tempdir()?; - - let compactor = Strategy { - base_size: 8 * 1_024 * 1_024, - level_ratio: 2, - }; - let config = Config::default(); - - let mut levels = LevelManifest::create_new(4, tempdir.path().join(LEVELS_MANIFEST_FILE))?; - levels.insert_into_level(3, fixture_segment(2, 8, 5)); - levels.insert_into_level(3, fixture_segment(3, 8, 5)); - - assert_eq!(compactor.choose(&levels, &config), Choice::DoNothing); - - Ok(()) - } -} - */ diff --git a/crates/lsm-tree/src/compaction/worker.rs b/crates/lsm-tree/src/compaction/worker.rs index da8d88f14..010d092fd 100644 --- a/crates/lsm-tree/src/compaction/worker.rs +++ b/crates/lsm-tree/src/compaction/worker.rs @@ -4,31 +4,25 @@ use super::{CompactionStrategy, Input as CompactionPayload}; use crate::{ - blob_tree::FragmentationMap, + Config, InternalValue, SeqNo, SequenceNumberCounter, Table, TableId, compaction::{ + Choice, filter::{Context, StreamFilterAdapter}, - flavour::{RelocatingCompaction, StandardCompaction}, + flavour::StandardCompaction, state::CompactionState, stream::CompactionStream, - Choice, }, - file::BLOBS_FOLDER, merge::Merger, run_scanner::RunScanner, stop_signal::StopSignal, tree::inner::TreeId, version::{Run, SuperVersions, Version}, - vlog::{BlobFileMergeScanner, BlobFileScanner, BlobFileWriter}, - BlobFile, Config, HashSet, InternalValue, SeqNo, SequenceNumberCounter, Table, TableId, }; use std::{ sync::{Arc, Mutex, MutexGuard, RwLock, RwLockReadGuard}, time::Instant, }; -#[cfg(feature = "metrics")] -use crate::metrics::Metrics; - pub type CompactionReader<'a> = Box> + 'a>; /// Compaction options @@ -41,8 +35,6 @@ pub struct Options { pub table_id_generator: SequenceNumberCounter, - pub blob_file_id_generator: SequenceNumberCounter, - /// Configuration of tree. pub config: Arc, @@ -59,9 +51,6 @@ pub struct Options { pub mvcc_gc_watermark: u64, pub compaction_state: Arc>, - - #[cfg(feature = "metrics")] - pub metrics: Arc, } impl Options { @@ -71,7 +60,6 @@ impl Options { visible_seqno: tree.config.visible_seqno.clone(), tree_id: tree.id, table_id_generator: tree.table_id_counter.clone(), - blob_file_id_generator: tree.blob_file_id_counter.clone(), config: tree.config.clone(), version_history: tree.version_history.clone(), stop_signal: tree.stop_signal.clone(), @@ -79,9 +67,6 @@ impl Options { mvcc_gc_watermark: 0, compaction_state: tree.compaction_state.clone(), - - #[cfg(feature = "metrics")] - metrics: tree.metrics.clone(), } } } @@ -118,15 +103,6 @@ pub fn do_compaction(opts: &Options) -> crate::Result<()> { move_tables(&compaction_state, opts, &payload) } - Choice::Drop(payload) => { - drop(version_history_lock); - - drop_tables( - compaction_state, - opts, - &payload.into_iter().collect::>(), - ) - } Choice::DoNothing => { log::trace!("Compactor chose to do nothing"); Ok(()) @@ -150,7 +126,7 @@ fn create_compaction_stream<'a>( version: &Version, to_compact: &[TableId], eviction_seqno: SeqNo, -) -> crate::Result>>>> { +) -> crate::Result>>>> { let mut readers: Vec> = vec![]; let mut found = 0; @@ -195,9 +171,9 @@ fn move_tables( .should_decline_compaction(payload.table_ids.iter().copied()) { log::warn!( - "Compaction task created by {:?} contained hidden tables, declining to run it - please report this at https://github.com/fjall-rs/lsm-tree/issues/new?template=bug_report.md", - opts.strategy.get_name(), - ); + "Compaction task created by {:?} contained hidden tables, declining to run it - please report this at https://github.com/fjall-rs/lsm-tree/issues/new?template=bug_report.md", + opts.strategy.get_name(), + ); return Ok(()); } @@ -226,87 +202,6 @@ fn move_tables( Ok(()) } -/// Picks blob files to rewrite (defragment) -fn pick_blob_files_to_rewrite( - picked_tables: &HashSet, - current_version: &Version, - blob_opts: &crate::KvSeparationOptions, -) -> crate::Result> { - use crate::Table; - - // We start off by getting all the blob files that are referenced by the tables - // that we want to compact. - let linked_blob_files = picked_tables - .iter() - .map(|&id| { - current_version.get_table(id).unwrap_or_else(|| { - panic!("Table {id} should exist"); - }) - }) - .map(Table::list_blob_file_references) - .collect::, _>>()?; - - // Then we filter all blob files that are not fragmented or old enough. - let mut linked_blob_files = linked_blob_files - .into_iter() - .flatten() - .flatten() - .map(|blob_file_ref| { - current_version - .blob_files - .get(blob_file_ref.blob_file_id) - .unwrap_or_else(|| { - panic!("Blob file {} should exist", blob_file_ref.blob_file_id); - }) - }) - .filter(|blob_file| { - blob_file.is_stale(current_version.gc_stats(), blob_opts.staleness_threshold) - }) - .filter(|blob_file| { - // NOTE: Dead blob files are dropped anyway during current_version change commit - !blob_file.is_dead(current_version.gc_stats()) - }) - .collect::>() - .into_iter() - .collect::>(); - - linked_blob_files.sort_by_key(|a| a.id()); - - #[expect( - clippy::cast_precision_loss, - clippy::cast_possible_truncation, - clippy::cast_sign_loss, - reason = "precision loss and truncation are acceptable for cutoff calculation" - )] - let cutoff_point = { - let len = linked_blob_files.len() as f32; - (len * blob_opts.age_cutoff) as usize - }; - linked_blob_files.drain(cutoff_point..); - - // IMPORTANT: Additionally, we also have to check if any other tables reference any of our candidate blob files. - // We have to *not* include blob files that are referenced by other tables, because otherwise those - // blob references would point into nothing (becoming dangling). - for table in current_version.iter_tables() { - if picked_tables.contains(&table.id()) { - continue; - } - - let other_refs = table - .list_blob_file_references()? - .unwrap_or_default() - .into_iter() - .filter(|x| linked_blob_files.iter().any(|bf| bf.id() == x.blob_file_id)) - .collect::>(); - - for additional_ref in other_refs { - linked_blob_files.retain(|x| x.id() != additional_ref.blob_file_id); - } - } - - Ok(linked_blob_files.into_iter().cloned().collect::>()) -} - fn hidden_guard( payload: &CompactionPayload, opts: &Options, @@ -365,8 +260,6 @@ fn merge_tables( return Ok(()); }; - let mut blob_frag_map = FragmentationMap::default(); - let Some(mut merge_iter) = create_compaction_stream( ¤t_super_version.version, &payload.table_ids.iter().copied().collect::>(), @@ -390,8 +283,6 @@ fn merge_tables( .evict_tombstones(is_last_level) .zero_seqnos(false); - let blobs_folder = opts.config.path.join(BLOBS_FOLDER); - let filter_ctx = Context { is_last_level }; // Construct the compaction filter @@ -400,78 +291,15 @@ fn merge_tables( f.make_filter(&filter_ctx) }); - // This is used by the compaction filter if it wants to write new blobs - // TODO: the filter should really pipe new blobs into the compaction stream directly, - // TODO: but that will probably require to change the protocol between filter <-> compaction stream a bit - let mut filter_blob_writer = None; - let mut merge_iter = merge_iter.with_filter(StreamFilterAdapter::new( + let merge_iter = merge_iter.with_filter(StreamFilterAdapter::new( compaction_filter.as_deref_mut(), - opts, - ¤t_super_version.version, - &blobs_folder, - &mut filter_blob_writer, &filter_ctx, )); let table_writer = super::flavour::prepare_table_writer(¤t_super_version.version, opts, payload)?; - let start = Instant::now(); - - let mut compactor = match &opts.config.kv_separation_opts { - Some(blob_opts) => { - merge_iter = merge_iter.with_drop_callback(&mut blob_frag_map); - - let blob_files_to_rewrite = pick_blob_files_to_rewrite( - &payload.table_ids, - ¤t_super_version.version, - blob_opts, - )?; - - if blob_files_to_rewrite.is_empty() { - log::debug!("No blob relocation needed"); - - Box::new(StandardCompaction::new(table_writer, tables)) - as Box - } else { - log::debug!( - "Relocate blob files: {:?}", - blob_files_to_rewrite - .iter() - .map(BlobFile::id) - .collect::>(), - ); - - let scanner = BlobFileMergeScanner::new( - blob_files_to_rewrite - .iter() - .map(|bf| BlobFileScanner::new(&bf.0.path, bf.id())) - .collect::>>()?, - ); - - let writer = BlobFileWriter::new( - opts.blob_file_id_generator.clone(), - &blobs_folder, - opts.tree_id, - opts.config.descriptor_table.clone(), - )? - .use_target_size(blob_opts.file_target_size) - .use_passthrough_compression(blob_opts.compression); - - let inner = StandardCompaction::new(table_writer, tables); - - Box::new(RelocatingCompaction::new( - inner, - scanner.peekable(), - writer, - blob_files_to_rewrite, - )) - } - } - None => Box::new(StandardCompaction::new(table_writer, tables)), - }; - - log::trace!("Blob file GC preparation done in {:?}", start.elapsed()); + let mut compactor = StandardCompaction::new(table_writer, tables); drop(version_history_lock); @@ -511,31 +339,8 @@ fn merge_tables( let mut version_history_lock = opts.version_history.write().expect("lock is poisoned"); log::trace!("Acquired super version write lock"); - log::trace!("Blob fragmentation diff: {blob_frag_map:#?}"); - - let extra_blob_files = filter_blob_writer - .map(BlobFileWriter::finish) - .transpose() - .inspect_err(|e| { - // NOTE: We cannot use hidden_guard here because we already locked the compaction state - - log::error!("Compaction failed while finishing filter blob writer: {e:?}"); - - compaction_state - .hidden_set_mut() - .show(payload.table_ids.iter().copied()); - })? - .unwrap_or_default(); - compactor - .finish( - &mut version_history_lock, - opts, - payload, - dst_lvl, - blob_frag_map, - extra_blob_files, - ) + .finish(&mut version_history_lock, opts, payload, dst_lvl) .inspect_err(|e| { // NOTE: We cannot use hidden_guard here because we already locked the compaction state @@ -564,100 +369,10 @@ fn merge_tables( Ok(()) } -fn drop_tables( - compaction_state: MutexGuard<'_, CompactionState>, - opts: &Options, - ids_to_drop: &[TableId], -) -> crate::Result<()> { - #[expect(clippy::expect_used, reason = "lock is expected to not be poisoned")] - let mut version_history_lock = opts.version_history.write().expect("lock is poisoned"); - - // Fail-safe for buggy compaction strategies - if compaction_state - .hidden_set() - .should_decline_compaction(ids_to_drop.iter().copied()) - { - log::warn!( - "Compaction task created by {:?} contained hidden tables, declining to run it - please report this at https://github.com/fjall-rs/lsm-tree/issues/new?template=bug_report.md", - opts.strategy.get_name(), - ); - return Ok(()); - } - - let Some(tables) = ids_to_drop - .iter() - .map(|&id| { - version_history_lock - .latest_version() - .version - .get_table(id) - .cloned() - }) - .collect::>>() - else { - log::warn!( - "Compaction task created by {:?} contained tables not referenced in the level manifest", - opts.strategy.get_name(), - ); - return Ok(()); - }; - - log::debug!("Dropping tables: {ids_to_drop:?}"); - - let mut dropped_blob_files = vec![]; - - // IMPORTANT: Write the manifest with the removed tables first - // Otherwise the table files are deleted, but are still referenced! - version_history_lock.upgrade_version( - &opts.config.path, - |current| { - let mut copy = current.clone(); - - copy.version = copy - .version - .with_dropped(ids_to_drop, &mut dropped_blob_files)?; - - Ok(copy) - }, - &opts.global_seqno, - &opts.visible_seqno, - )?; - - if let Err(e) = version_history_lock.maintenance(&opts.config.path, opts.mvcc_gc_watermark) { - log::error!("Manifest maintenance failed: {e:?}"); - return Err(e); - } - - drop(version_history_lock); - - // NOTE: If the application were to crash >here< it's fine - // The tables are not referenced anymore, and will be - // cleaned up upon recovery - for table in tables { - table.mark_as_deleted(); - } - - for blob_file in dropped_blob_files { - blob_file.mark_as_deleted(); - } - - drop(compaction_state); - - log::trace!("Dropped {} tables", ids_to_drop.len()); - - Ok(()) -} - #[cfg(test)] mod tests { use super::{create_compaction_stream, pick_run_indexes}; - use crate::{ - compaction::{state::CompactionState, Choice, CompactionStrategy, Input}, - config::BlockSizePolicy, - version::Version, - AbstractTree, Config, KvSeparationOptions, SequenceNumberCounter, TableId, - }; - use std::sync::Arc; + use crate::{AbstractTree, SequenceNumberCounter}; use test_log::test; #[test] @@ -826,135 +541,4 @@ mod tests { Ok(()) } - - #[test] - fn compaction_drop_tables() -> crate::Result<()> { - let folder = tempfile::tempdir()?; - - let tree = crate::Config::new( - folder, - SequenceNumberCounter::default(), - SequenceNumberCounter::default(), - ) - .open()?; - - tree.insert("a", "a", 0); - tree.flush_active_memtable(0)?; - assert_eq!(1, tree.approximate_len()); - assert_eq!(0, tree.sealed_memtable_count()); - - tree.insert("b", "a", 1); - tree.flush_active_memtable(0)?; - assert_eq!(2, tree.approximate_len()); - assert_eq!(0, tree.sealed_memtable_count()); - - tree.insert("c", "a", 2); - tree.flush_active_memtable(0)?; - assert_eq!(3, tree.approximate_len()); - assert_eq!(0, tree.sealed_memtable_count()); - - tree.compact(Arc::new(crate::compaction::Fifo::new(1, None)), 3)?; - - assert_eq!(0, tree.table_count()); - - Ok(()) - } - - #[test] - fn blob_file_picking_simple() -> crate::Result<()> { - struct InPlaceStrategy(Vec); - - impl CompactionStrategy for InPlaceStrategy { - fn get_name(&self) -> &'static str { - "InPlaceCompaction" - } - - fn choose(&self, _: &Version, _: &Config, _: &CompactionState) -> Choice { - Choice::Merge(Input { - table_ids: self.0.iter().copied().collect(), - dest_level: 6, - target_size: 64_000_000, - canonical_level: 6, // We don't really care - this compaction is only used for very specific unit tests - }) - } - } - - let folder = tempfile::tempdir()?; - - let tree = crate::Config::new( - folder, - SequenceNumberCounter::default(), - SequenceNumberCounter::default(), - ) - .data_block_size_policy(BlockSizePolicy::all(1)) - .with_kv_separation(Some( - KvSeparationOptions::default() - .separation_threshold(1) - .age_cutoff(1.0) - .staleness_threshold(0.01) - .compression(crate::CompressionType::None), - )) - .open()?; - - tree.insert("a", "a", 0); - tree.insert("b", "b", 0); - tree.insert("c", "c", 0); - tree.flush_active_memtable(1_000)?; - assert_eq!(0, tree.sealed_memtable_count()); - assert_eq!(1, tree.table_count()); - assert_eq!(1, tree.blob_file_count()); - - tree.major_compact(1, 1_000)?; - assert_eq!(3, tree.table_count()); - assert_eq!(1, tree.blob_file_count()); - // We now have tables [1, 2, 3] pointing into blob file 0 - - tree.drop_range("a"..="a")?; - assert_eq!(2, tree.table_count()); - assert_eq!(1, tree.blob_file_count()); - - { - assert_eq!( - &{ - let mut map = crate::HashMap::default(); - map.insert(0, crate::blob_tree::FragmentationEntry::new(1, 1, 1)); - map - }, - &**tree.current_version().gc_stats(), - ); - } - - // Even though we are compacting table #2, blob file is not rewritten - // because table #3 still points into it - tree.compact(Arc::new(InPlaceStrategy(vec![2])), 1_000)?; - assert_eq!(2, tree.table_count()); - assert_eq!(1, tree.blob_file_count()); - - { - assert_eq!( - &{ - let mut map = crate::HashMap::default(); - map.insert(0, crate::blob_tree::FragmentationEntry::new(1, 1, 1)); - map - }, - &**tree.current_version().gc_stats(), - ); - } - - // Because tables #3 & #4 both point into the blob file - // Only selecting both for compaction will actually rewrite the file - tree.compact(Arc::new(InPlaceStrategy(vec![3, 4])), 1_000)?; - assert_eq!(1, tree.table_count()); - assert_eq!(1, tree.blob_file_count()); - - // Fragmentation is cleared up because blob file was relocated - { - assert_eq!( - crate::HashMap::default(), - **tree.current_version().gc_stats(), - ); - } - - Ok(()) - } } diff --git a/crates/lsm-tree/src/config/mod.rs b/crates/lsm-tree/src/config/mod.rs index 93c6b64da..07a64eec6 100644 --- a/crates/lsm-tree/src/config/mod.rs +++ b/crates/lsm-tree/src/config/mod.rs @@ -20,144 +20,16 @@ pub use restart_interval::RestartIntervalPolicy; pub type PartitioningPolicy = PinningPolicy; use crate::{ - compaction::filter::Factory, path::absolute_path, version::DEFAULT_LEVEL_COUNT, AnyTree, - BlobTree, Cache, CompressionType, DescriptorTable, SequenceNumberCounter, Tree, + Cache, CompressionType, DescriptorTable, SequenceNumberCounter, Tree, + compaction::filter::Factory, path::absolute_path, version::DEFAULT_LEVEL_COUNT, }; use std::{ path::{Path, PathBuf}, sync::Arc, }; -/// LSM-tree type -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub enum TreeType { - /// Standard LSM-tree, see [`Tree`] - Standard, - - /// Key-value separated LSM-tree, see [`BlobTree`] - Blob, -} - -impl From for u8 { - fn from(val: TreeType) -> Self { - match val { - TreeType::Standard => 0, - TreeType::Blob => 1, - } - } -} - -impl TryFrom for TreeType { - type Error = (); - - fn try_from(value: u8) -> Result { - match value { - 0 => Ok(Self::Standard), - 1 => Ok(Self::Blob), - _ => Err(()), - } - } -} - const DEFAULT_FILE_FOLDER: &str = ".lsm.data"; -/// Options for key-value separation -#[derive(Clone, Debug, PartialEq)] -pub struct KvSeparationOptions { - /// What type of compression is used for blobs - #[doc(hidden)] - pub compression: CompressionType, - - /// Blob file target size in bytes - #[doc(hidden)] - pub file_target_size: u64, - - /// Key-value separation threshold in bytes - #[doc(hidden)] - pub separation_threshold: u32, - - #[doc(hidden)] - pub staleness_threshold: f32, - - #[doc(hidden)] - pub age_cutoff: f32, -} - -impl Default for KvSeparationOptions { - fn default() -> Self { - Self { - #[cfg(feature="lz4")] - compression: CompressionType::Lz4, - - #[cfg(not(feature="lz4"))] - compression: CompressionType::None, - - file_target_size: /* 64 MiB */ 64 * 1_024 * 1_024, - separation_threshold: /* 1 KiB */ 1_024, - - staleness_threshold: 0.25, - age_cutoff: 0.25, - } - } -} - -impl KvSeparationOptions { - /// Sets the blob compression method. - #[must_use] - pub fn compression(mut self, compression: CompressionType) -> Self { - self.compression = compression; - self - } - - /// Sets the target size of blob files. - /// - /// Smaller blob files allow more granular garbage collection - /// which allows lower space amp for lower write I/O cost. - /// - /// Larger blob files decrease the number of files on disk and maintenance - /// overhead. - /// - /// Defaults to 64 MiB. - #[must_use] - pub fn file_target_size(mut self, bytes: u64) -> Self { - self.file_target_size = bytes; - self - } - - /// Sets the key-value separation threshold in bytes. - /// - /// Smaller value will reduce compaction overhead and thus write amplification, - /// at the cost of lower read performance. - /// - /// Defaults to 1 KiB. - #[must_use] - pub fn separation_threshold(mut self, bytes: u32) -> Self { - self.separation_threshold = bytes; - self - } - - /// Sets the staleness threshold percentage. - /// - /// The staleness percentage determines how much a blob file needs to be fragmented to be - /// picked up by the garbage collection. - /// - /// Defaults to 33%. - #[must_use] - pub fn staleness_threshold(mut self, ratio: f32) -> Self { - self.staleness_threshold = ratio; - self - } - - /// Sets the age cutoff threshold. - /// - /// Defaults to 20%. - #[must_use] - pub fn age_cutoff(mut self, ratio: f32) -> Self { - self.age_cutoff = ratio; - self - } -} - /// Tree configuration builder pub struct Config { /// Folder path @@ -225,9 +97,6 @@ pub struct Config { /// Compaction filter factory pub compaction_filter_factory: Option>, - #[doc(hidden)] - pub kv_separation_opts: Option, - /// The global sequence number generator /// /// Should be shared between multple trees of a database @@ -282,8 +151,6 @@ impl Default for Config { compaction_filter_factory: None, expect_point_read_hits: false, - - kv_separation_opts: None, } } } @@ -443,13 +310,6 @@ impl Config { self } - /// Toggles key-value separation. - #[must_use] - pub fn with_kv_separation(mut self, opts: Option) -> Self { - self.kv_separation_opts = opts; - self - } - /// Installs a custom compaction filter. #[must_use] pub fn with_compaction_filter_factory(mut self, factory: Option>) -> Self { @@ -462,11 +322,7 @@ impl Config { /// # Errors /// /// Will return `Err` if an IO error occurs. - pub fn open(self) -> crate::Result { - Ok(if self.kv_separation_opts.is_some() { - AnyTree::Blob(BlobTree::open(self)?) - } else { - AnyTree::Standard(Tree::open(self)?) - }) + pub fn open(self) -> crate::Result { + Tree::open(self) } } diff --git a/crates/lsm-tree/src/descriptor_table.rs b/crates/lsm-tree/src/descriptor_table.rs index 12df8db2b..f55b1c1de 100644 --- a/crates/lsm-tree/src/descriptor_table.rs +++ b/crates/lsm-tree/src/descriptor_table.rs @@ -3,20 +3,14 @@ // (found in the LICENSE-* files in the repository) use crate::GlobalTableId; -use quick_cache::{sync::Cache as QuickCache, UnitWeighter}; +use quick_cache::{UnitWeighter, sync::Cache as QuickCache}; use std::{fs::File, sync::Arc}; -const TAG_BLOCK: u8 = 0; -const TAG_BLOB: u8 = 1; - type Item = Arc; -#[derive(Eq, std::hash::Hash, PartialEq)] -struct CacheKey(u8, u64, u64); - -/// Caches file descriptors to tables and blob files +/// Caches file descriptors to tables pub struct DescriptorTable { - inner: QuickCache, + inner: QuickCache, } impl DescriptorTable { @@ -41,33 +35,14 @@ impl DescriptorTable { #[must_use] pub fn access_for_table(&self, id: &GlobalTableId) -> Option> { - let key = CacheKey(TAG_BLOCK, id.tree_id(), id.table_id()); - self.inner.get(&key) + self.inner.get(id) } pub fn insert_for_table(&self, id: GlobalTableId, item: Item) { - let key = CacheKey(TAG_BLOCK, id.tree_id(), id.table_id()); - self.inner.insert(key, item); - } - - #[must_use] - pub fn access_for_blob_file(&self, id: &GlobalTableId) -> Option> { - let key = CacheKey(TAG_BLOB, id.tree_id(), id.table_id()); - self.inner.get(&key) - } - - pub fn insert_for_blob_file(&self, id: GlobalTableId, item: Item) { - let key = CacheKey(TAG_BLOB, id.tree_id(), id.table_id()); - self.inner.insert(key, item); + self.inner.insert(id, item); } pub fn remove_for_table(&self, id: &GlobalTableId) { - let key = CacheKey(TAG_BLOCK, id.tree_id(), id.table_id()); - self.inner.remove(&key); - } - - pub fn remove_for_blob_file(&self, id: &GlobalTableId) { - let key = CacheKey(TAG_BLOB, id.tree_id(), id.table_id()); - self.inner.remove(&key); + self.inner.remove(id); } } diff --git a/crates/lsm-tree/src/file.rs b/crates/lsm-tree/src/file.rs index 2c006ddef..8472f16fe 100644 --- a/crates/lsm-tree/src/file.rs +++ b/crates/lsm-tree/src/file.rs @@ -8,7 +8,6 @@ use std::{fs::File, io::Write, path::Path}; pub const MAGIC_BYTES: [u8; 4] = [b'L', b'S', b'M', 3]; pub const TABLES_FOLDER: &str = "tables"; -pub const BLOBS_FOLDER: &str = "blobs"; pub const CURRENT_VERSION_FILE: &str = "current"; /// Reads bytes from a file using `pread`. @@ -50,7 +49,10 @@ pub fn read_exact(file: &File, offset: u64, size: usize) -> std::io::Result Option> { - match self { - Self::File(fd) => Some(fd.clone()), - Self::DescriptorTable(descriptor_table) => { - descriptor_table.access_for_blob_file(table_id) - } - } - } - - pub fn insert_for_blob_file(&self, table_id: GlobalTableId, fd: Arc) { - if let Self::DescriptorTable(descriptor_table) = self { - descriptor_table.insert_for_blob_file(table_id, fd); - } - } } impl std::fmt::Debug for FileAccessor { diff --git a/crates/lsm-tree/src/format_version.rs b/crates/lsm-tree/src/format_version.rs index 37174af40..8773a31a5 100644 --- a/crates/lsm-tree/src/format_version.rs +++ b/crates/lsm-tree/src/format_version.rs @@ -16,6 +16,9 @@ pub enum FormatVersion { /// Fixed-width key/value lengths in table data blocks V4, + + /// 32-bit tree and table IDs + V5, } impl std::fmt::Display for FormatVersion { @@ -31,6 +34,7 @@ impl From for u8 { FormatVersion::V2 => 2, FormatVersion::V3 => 3, FormatVersion::V4 => 4, + FormatVersion::V5 => 5, } } } @@ -44,6 +48,7 @@ impl TryFrom for FormatVersion { 2 => Ok(Self::V2), 3 => Ok(Self::V3), 4 => Ok(Self::V4), + 5 => Ok(Self::V5), _ => Err(()), } } diff --git a/crates/lsm-tree/src/ingestion.rs b/crates/lsm-tree/src/ingestion.rs deleted file mode 100644 index b829690f9..000000000 --- a/crates/lsm-tree/src/ingestion.rs +++ /dev/null @@ -1,126 +0,0 @@ -// Copyright (c) 2024-present, fjall-rs -// This source code is licensed under both the Apache 2.0 and MIT License -// (found in the LICENSE-* files in the repository) - -use crate::{ - blob_tree::ingest::BlobIngestion, tree::ingest::Ingestion, AnyTree, UserKey, UserValue, -}; - -/// Unified ingestion builder over `AnyTree` -// Keep zero allocations and direct dispatch; boxing introduces heap indirection and `dyn` adds virtual dispatch. -// Ingestion calls use `&mut self` in tight loops; the active variant is stable and branch prediction makes the match cheap. -// Allowing this lint preserves hot-path performance at the cost of a larger enum size. -#[expect(clippy::large_enum_variant)] -pub enum AnyIngestion<'a> { - /// Ingestion for a standard LSM-tree - Standard(Ingestion<'a>), - - /// Ingestion for a [`BlobTree`] with KV separation - Blob(BlobIngestion<'a>), -} - -impl AnyIngestion<'_> { - /// Writes a key-value pair. - /// - /// # Errors - /// - /// Will return `Err` if an IO error occurs. - pub fn write, V: Into>( - &mut self, - key: K, - value: V, - ) -> crate::Result<()> { - match self { - Self::Standard(i) => i.write(key.into(), value.into()), - Self::Blob(b) => b.write(key.into(), value.into()), - } - } - - #[doc(hidden)] - pub fn write_prevalidated, V: Into>( - &mut self, - key: K, - value: V, - ) -> crate::Result<()> { - match self { - Self::Standard(i) => i.write_prevalidated(key.into(), value.into()), - Self::Blob(b) => b.write(key.into(), value.into()), - } - } - - /// Writes a tombstone for a key. - /// - /// # Errors - /// - /// Will return `Err` if an IO error occurs. - pub fn write_tombstone>(&mut self, key: K) -> crate::Result<()> { - match self { - Self::Standard(i) => i.write_tombstone(key.into()), - Self::Blob(b) => b.write_tombstone(key.into()), - } - } - - /// Writes a weak tombstone for a key. - /// - /// # Examples - /// - /// ``` - /// # use lsm_tree::Config; - /// # let folder = tempfile::tempdir()?; - /// # let tree = Config::new(folder, Default::default(), Default::default()).open()?; - /// # - /// let mut ingestion = tree.ingestion()?; - /// ingestion.write("a", "abc")?; - /// ingestion.write_weak_tombstone("b")?; - /// ingestion.finish()?; - /// # - /// # Ok::<(), lsm_tree::Error>(()) - /// ``` - /// - /// # Errors - /// - /// Will return `Err` if an IO error occurs. - pub fn write_weak_tombstone>(&mut self, key: K) -> crate::Result<()> { - match self { - Self::Standard(i) => i.write_weak_tombstone(key.into()), - Self::Blob(b) => b.write_weak_tombstone(key.into()), - } - } - - #[doc(hidden)] - pub fn write_prevalidated_weak_tombstone>( - &mut self, - key: K, - ) -> crate::Result<()> { - match self { - Self::Standard(i) => i.write_prevalidated_weak_tombstone(key.into()), - Self::Blob(b) => b.write_weak_tombstone(key.into()), - } - } - - /// Finalizes ingestion and registers created tables (and blob files if present). - /// - /// # Errors - /// - /// Will return `Err` if an IO error occurs. - pub fn finish(self) -> crate::Result<()> { - match self { - Self::Standard(i) => i.finish(), - Self::Blob(b) => b.finish(), - } - } -} - -impl AnyTree { - /// Starts an ingestion for any tree type (standard or blob). - /// - /// # Errors - /// - /// Will return `Err` if an IO error occurs. - pub fn ingestion(&self) -> crate::Result> { - match self { - Self::Standard(t) => Ok(AnyIngestion::Standard(Ingestion::new(t)?)), - Self::Blob(b) => Ok(AnyIngestion::Blob(BlobIngestion::new(b)?)), - } - } -} diff --git a/crates/lsm-tree/src/iter_guard.rs b/crates/lsm-tree/src/iter_guard.rs index 63fb9312e..6ee2d9211 100644 --- a/crates/lsm-tree/src/iter_guard.rs +++ b/crates/lsm-tree/src/iter_guard.rs @@ -1,10 +1,6 @@ -use crate::{ - blob_tree::Guard as BlobGuard, tree::Guard as StandardGuard, KvPair, UserKey, UserValue, -}; -use enum_dispatch::enum_dispatch; +use crate::{KvPair, UserKey, UserValue, tree::Guard as StandardGuard}; /// Guard to access key-value pairs -#[enum_dispatch] pub trait IterGuard { /// Accesses the key-value pair if the predicate returns `true`. /// @@ -53,12 +49,5 @@ pub trait IterGuard { } } -/// Generic iterator value -#[enum_dispatch(IterGuard)] -pub enum IterGuardImpl { - /// Iterator value of a standard LSM-tree - Standard(StandardGuard), - - /// Iterator value of a key-value separated tree - Blob(BlobGuard), -} +/// Iterator value. +pub type IterGuardImpl = StandardGuard; diff --git a/crates/lsm-tree/src/key.rs b/crates/lsm-tree/src/key.rs index 799897bbe..d1e0f2b71 100644 --- a/crates/lsm-tree/src/key.rs +++ b/crates/lsm-tree/src/key.rs @@ -29,7 +29,6 @@ impl std::fmt::Debug for InternalKey { ValueType::Value => "V", ValueType::Tombstone => "T", ValueType::WeakTombstone => "W", - ValueType::Indirection => "Vb", }, ) } diff --git a/crates/lsm-tree/src/lib.rs b/crates/lsm-tree/src/lib.rs index d429d71c5..c564d6a3b 100644 --- a/crates/lsm-tree/src/lib.rs +++ b/crates/lsm-tree/src/lib.rs @@ -60,18 +60,11 @@ macro_rules! fail_iter { } macro_rules! unwrap { - ($x:expr) => {{ - $x.expect("should read") - }}; + ($x:expr) => {{ $x.expect("should read") }}; } -mod any_tree; - mod abstract_tree; -#[doc(hidden)] -pub mod blob_tree; - #[doc(hidden)] mod cache; @@ -100,7 +93,6 @@ mod error; pub mod file; mod hash; -mod ingestion; mod iter_guard; mod key; mod key_range; @@ -112,11 +104,6 @@ mod run_scanner; #[doc(hidden)] pub mod merge; -#[cfg(feature = "metrics")] -pub(crate) mod metrics; - -// mod multi_reader; - #[doc(hidden)] pub mod mvcc_stream; @@ -145,8 +132,6 @@ pub mod util; mod value; mod value_type; mod version; -mod vlog; - /// User defined key (byte array) pub type UserKey = Slice; @@ -158,42 +143,34 @@ pub type KvPair = (UserKey, UserValue); #[doc(hidden)] pub use { - blob_tree::{handle::BlobIndirection, Guard as BlobGuard}, checksum::Checksum, iter_guard::IterGuardImpl, key_range::KeyRange, merge::BoxedIterator, slice::Builder, table::{GlobalTableId, Table, TableId}, - tree::inner::TreeId, tree::Guard as StandardGuard, + tree::inner::TreeId, value::InternalValue, }; pub use { abstract_tree::AbstractTree, - any_tree::AnyTree, - blob_tree::BlobTree, cache::Cache, compression::CompressionType, - config::{Config, KvSeparationOptions, TreeType}, + config::Config, descriptor_table::DescriptorTable, error::{Error, Result}, format_version::FormatVersion, - ingestion::AnyIngestion, iter_guard::IterGuard as Guard, memtable::{Memtable, MemtableId}, seqno::SequenceNumberCounter, slice::Slice, - tree::Tree, + tree::{Tree, ingest::Ingestion}, value::SeqNo, value_type::ValueType, - vlog::BlobFile, }; -#[cfg(feature = "metrics")] -pub use metrics::Metrics; - #[doc(hidden)] #[must_use] #[allow(missing_docs, clippy::missing_errors_doc, clippy::unwrap_used)] diff --git a/crates/lsm-tree/src/manifest.rs b/crates/lsm-tree/src/manifest.rs index 84e70431b..81057a6da 100644 --- a/crates/lsm-tree/src/manifest.rs +++ b/crates/lsm-tree/src/manifest.rs @@ -2,12 +2,11 @@ // This source code is licensed under both the Apache 2.0 and MIT License // (found in the LICENSE-* files in the repository) -use crate::{checksum::ChecksumType, FormatVersion, TreeType}; +use crate::{FormatVersion, checksum::ChecksumType}; use byteorder::ReadBytesExt; use std::{io::Read, path::Path}; pub struct Manifest { - pub version: FormatVersion, pub level_count: u8, } @@ -29,7 +28,11 @@ impl Manifest { FormatVersion::try_from(version).map_err(|()| crate::Error::InvalidVersion(version))? }; - let _tree_type: TreeType = { + if version != FormatVersion::V5 { + return Err(crate::Error::InvalidVersion(version.into())); + } + + { #[expect( clippy::expect_used, reason = "tree_type section must exist in manifest" @@ -40,10 +43,10 @@ impl Manifest { let mut reader = section.buf_reader(path)?; let tree_type = reader.read_u8()?; - tree_type - .try_into() - .map_err(|()| crate::Error::InvalidTag(("TreeType", tree_type)))? - }; + if tree_type != 0 { + return Err(crate::Error::InvalidTag(("TreeType", tree_type))); + } + } let level_count = { #[expect( @@ -85,9 +88,6 @@ impl Manifest { ); } - Ok(Self { - version, - level_count, - }) + Ok(Self { level_count }) } } diff --git a/crates/lsm-tree/src/memtable/mod.rs b/crates/lsm-tree/src/memtable/mod.rs index ced9c12b7..ea5f3364a 100644 --- a/crates/lsm-tree/src/memtable/mod.rs +++ b/crates/lsm-tree/src/memtable/mod.rs @@ -4,8 +4,8 @@ use crate::key::InternalKey; use crate::{ - value::{InternalValue, SeqNo, UserValue}, ValueType, + value::{InternalValue, SeqNo, UserValue}, }; use crossbeam_skiplist::SkipMap; use std::ops::RangeBounds; diff --git a/crates/lsm-tree/src/metrics.rs b/crates/lsm-tree/src/metrics.rs deleted file mode 100644 index fac3baa3e..000000000 --- a/crates/lsm-tree/src/metrics.rs +++ /dev/null @@ -1,212 +0,0 @@ -// Copyright (c) 2024-present, fjall-rs -// This source code is licensed under both the Apache 2.0 and MIT License -// (found in the LICENSE-* files in the repository) - -use std::sync::atomic::Ordering::Relaxed; -use std::sync::atomic::{AtomicU64, AtomicUsize}; - -/// Runtime metrics -/// -/// Are not stored durably, so metrics will reset after a restart/crash. -#[derive(Debug, Default)] -pub struct Metrics { - /// Number of times a table file was opened using `fopen()` - pub(crate) table_file_opened_uncached: AtomicUsize, - - /// Number of times a table file was retrieved from descriptor cache - pub(crate) table_file_opened_cached: AtomicUsize, - - /// Number of index blocks that were actually read from disk - pub(crate) index_block_load_io: AtomicUsize, - - /// Number of filter blocks that were actually read from disk - pub(crate) filter_block_load_io: AtomicUsize, - - /// Number of blocks that were actually read from disk - pub(crate) data_block_load_io: AtomicUsize, - - /// Number of blocks that were read from block cache - pub(crate) index_block_load_cached: AtomicUsize, - - /// Number of blocks that were read from block cache - pub(crate) filter_block_load_cached: AtomicUsize, - - /// Number of blocks that were read from block cache - pub(crate) data_block_load_cached: AtomicUsize, - - /// Number of filter queries that were performed - pub(crate) filter_queries: AtomicUsize, - - /// Number of IOs that were skipped due to filter - pub(crate) io_skipped_by_filter: AtomicUsize, - - /// Number of data block bytes that were requested from OS or disk - pub(crate) data_block_io_requested: AtomicU64, - - /// Number of index block bytes that were requested from OS or disk - pub(crate) index_block_io_requested: AtomicU64, - - /// Number of filter block bytes that were requested from OS or disk - pub(crate) filter_block_io_requested: AtomicU64, -} - -#[expect( - clippy::cast_precision_loss, - reason = "metrics can accept precision loss" -)] -impl Metrics { - /// Returns the cache hit rate for file descriptors in percent (0.0 - 1.0). - pub fn table_file_cache_hit_rate(&self) -> f64 { - let uncached = self.table_file_opened_uncached.load(Relaxed) as f64; - let cached = self.table_file_opened_cached.load(Relaxed) as f64; - - if cached + uncached == 0.0 { - 1.0 - } else { - cached / (cached + uncached) - } - } - - /// Number of I/O data block bytes transferred from disk or OS page cache. - pub fn data_block_io(&self) -> u64 { - self.data_block_io_requested.load(Relaxed) - } - - /// Number of I/O index block bytes transferred from disk or OS page cache. - pub fn index_block_io(&self) -> u64 { - self.index_block_io_requested.load(Relaxed) - } - - /// Number of I/O filter block bytes transferred from disk or OS page cache. - pub fn filter_block_io(&self) -> u64 { - self.filter_block_io_requested.load(Relaxed) - } - - /// Number of I/O block bytes transferred from disk or OS page cache. - pub fn block_io(&self) -> u64 { - self.data_block_io_requested.load(Relaxed) - + self.index_block_io_requested.load(Relaxed) - + self.filter_block_io_requested.load(Relaxed) - } - - /// Number of data blocks that were accessed. - pub fn data_block_load_count(&self) -> usize { - self.data_block_load_cached.load(Relaxed) + self.data_block_load_io.load(Relaxed) - } - - /// Number of index blocks that were accessed. - pub fn index_block_load_count(&self) -> usize { - self.index_block_load_cached.load(Relaxed) + self.index_block_load_io.load(Relaxed) - } - - /// Number of filter blocks that were accessed. - pub fn filter_block_load_count(&self) -> usize { - self.filter_block_load_cached.load(Relaxed) + self.filter_block_load_io.load(Relaxed) - } - - /// Number of blocks that were loaded from disk or OS page cache. - pub fn block_load_io_count(&self) -> usize { - self.data_block_load_io.load(Relaxed) - + self.index_block_load_io.load(Relaxed) - + self.filter_block_load_io.load(Relaxed) - } - - /// Number of data blocks that were loaded from disk or OS page cache. - pub fn data_block_load_cached_count(&self) -> usize { - self.data_block_load_cached.load(Relaxed) - } - - /// Number of index blocks that were loaded from disk or OS page cache. - pub fn index_block_load_cached_count(&self) -> usize { - self.index_block_load_cached.load(Relaxed) - } - - /// Number of filter blocks that were loaded from disk or OS page cache. - pub fn filter_block_load_cached_count(&self) -> usize { - self.filter_block_load_cached.load(Relaxed) - } - - /// Number of blocks that were loaded from disk or OS page cache. - pub fn block_load_cached_count(&self) -> usize { - self.data_block_load_cached.load(Relaxed) - + self.index_block_load_cached.load(Relaxed) - + self.filter_block_load_cached.load(Relaxed) - } - - /// Number of blocks that were accessed. - pub fn block_loads(&self) -> usize { - self.block_load_io_count() + self.block_load_cached_count() - } - - /// Data block cache efficiency in percent (0.0 - 1.0). - pub fn data_block_cache_hit_rate(&self) -> f64 { - let queries = self.data_block_load_count() as f64; - let hits = self.data_block_load_cached_count() as f64; - - if queries == 0.0 { - 1.0 - } else { - hits / queries - } - } - - /// Filter block cache efficiency in percent (0.0 - 1.0). - pub fn filter_block_cache_hit_rate(&self) -> f64 { - let queries = self.filter_block_load_count() as f64; - let hits = self.filter_block_load_cached_count() as f64; - - if queries == 0.0 { - 1.0 - } else { - hits / queries - } - } - - /// Index block cache efficiency in percent (0.0 - 1.0). - pub fn index_block_cache_hit_rate(&self) -> f64 { - let queries = self.index_block_load_count() as f64; - let hits = self.index_block_load_cached_count() as f64; - - if queries == 0.0 { - 1.0 - } else { - hits / queries - } - } - - /// Block cache efficiency in percent (0.0 - 1.0). - pub fn block_cache_hit_rate(&self) -> f64 { - let queries = self.block_loads() as f64; - let hits = self.block_load_cached_count() as f64; - - if queries == 0.0 { - 1.0 - } else { - hits / queries - } - } - - /// Filter efficiency in percent (0.0 - 1.0). - /// - /// Represents the ratio of I/O operations avoided due to filter. - pub fn filter_efficiency(&self) -> f64 { - let queries = self.filter_queries.load(Relaxed) as f64; - let io_skipped = self.io_skipped_by_filter.load(Relaxed) as f64; - - if queries == 0.0 { - 1.0 - } else { - io_skipped / queries - } - } - - /// Number of filter queries performed. - pub fn filter_queries(&self) -> usize { - self.filter_queries.load(Relaxed) - } - - /// Number of I/O operations skipped by filter. - pub fn io_skipped_by_filter(&self) -> usize { - self.io_skipped_by_filter.load(Relaxed) - } -} diff --git a/crates/lsm-tree/src/mvcc_stream.rs b/crates/lsm-tree/src/mvcc_stream.rs index e23246e35..59b93da16 100644 --- a/crates/lsm-tree/src/mvcc_stream.rs +++ b/crates/lsm-tree/src/mvcc_stream.rs @@ -88,7 +88,7 @@ impl>> DoubleEndedIte #[expect(clippy::string_lit_as_bytes)] mod tests { use super::*; - use crate::{value::InternalValue, ValueType}; + use crate::{ValueType, value::InternalValue}; use test_log::test; macro_rules! stream { diff --git a/crates/lsm-tree/src/range.rs b/crates/lsm-tree/src/range.rs index c0cd5df94..5afc8190d 100644 --- a/crates/lsm-tree/src/range.rs +++ b/crates/lsm-tree/src/range.rs @@ -3,6 +3,7 @@ // (found in the LICENSE-* files in the repository) use crate::{ + BoxedIterator, InternalValue, key::InternalKey, memtable::Memtable, merge::Merger, @@ -10,7 +11,6 @@ use crate::{ run_reader::RunReader, value::{SeqNo, UserKey}, version::SuperVersion, - BoxedIterator, InternalValue, }; use self_cell::self_cell; use std::{ diff --git a/crates/lsm-tree/src/run_reader.rs b/crates/lsm-tree/src/run_reader.rs index 96384e66b..2df3e01a1 100644 --- a/crates/lsm-tree/src/run_reader.rs +++ b/crates/lsm-tree/src/run_reader.rs @@ -2,7 +2,7 @@ // This source code is licensed under both the Apache 2.0 and MIT License // (found in the LICENSE-* files in the repository) -use crate::{version::Run, BoxedIterator, InternalValue, Table, UserKey}; +use crate::{BoxedIterator, InternalValue, Table, UserKey, version::Run}; use std::{ ops::{Deref, RangeBounds}, sync::Arc, diff --git a/crates/lsm-tree/src/run_scanner.rs b/crates/lsm-tree/src/run_scanner.rs index 90d2ed702..0de423b30 100644 --- a/crates/lsm-tree/src/run_scanner.rs +++ b/crates/lsm-tree/src/run_scanner.rs @@ -2,7 +2,7 @@ // This source code is licensed under both the Apache 2.0 and MIT License // (found in the LICENSE-* files in the repository) -use crate::{table::Scanner, version::Run, InternalValue, Table}; +use crate::{InternalValue, Table, table::Scanner, version::Run}; use std::sync::Arc; /// Scans through a disjoint run diff --git a/crates/lsm-tree/src/seqno.rs b/crates/lsm-tree/src/seqno.rs index 1fd8fb790..124696806 100644 --- a/crates/lsm-tree/src/seqno.rs +++ b/crates/lsm-tree/src/seqno.rs @@ -4,11 +4,11 @@ use crate::SeqNo; use std::sync::{ + Arc, atomic::{ AtomicU64, Ordering::{AcqRel, Acquire, Release}, }, - Arc, }; /// Thread-safe sequence number generator diff --git a/crates/lsm-tree/src/slice/mod.rs b/crates/lsm-tree/src/slice/mod.rs index 63f9c3745..24e0c6161 100644 --- a/crates/lsm-tree/src/slice/mod.rs +++ b/crates/lsm-tree/src/slice/mod.rs @@ -2,12 +2,6 @@ // This source code is licensed under both the Apache 2.0 and MIT License // (found in the LICENSE-* files in the repository) -// Using tokio bytes -#[cfg(feature = "bytes_1")] -mod slice_bytes; - -// Using byteview -#[cfg(not(feature = "bytes_1"))] mod slice_default; use std::{ @@ -15,12 +9,8 @@ use std::{ sync::Arc, }; -#[cfg(not(feature = "bytes_1"))] pub use slice_default::{Builder, Slice}; -#[cfg(feature = "bytes_1")] -pub use slice_bytes::{Builder, Slice}; - impl AsRef<[u8]> for Slice { fn as_ref(&self) -> &[u8] { &self.0 @@ -29,15 +19,7 @@ impl AsRef<[u8]> for Slice { impl From<&[u8]> for Slice { fn from(value: &[u8]) -> Self { - #[cfg(not(feature = "bytes_1"))] - { - Self(byteview::ByteView::new(value)) - } - - #[cfg(feature = "bytes_1")] - { - Self(bytes::Bytes::from(value.to_vec())) - } + Self(byteview::ByteView::new(value)) } } diff --git a/crates/lsm-tree/src/slice/slice_bytes/mod.rs b/crates/lsm-tree/src/slice/slice_bytes/mod.rs deleted file mode 100644 index 936fe423d..000000000 --- a/crates/lsm-tree/src/slice/slice_bytes/mod.rs +++ /dev/null @@ -1,115 +0,0 @@ -// Copyright (c) 2024-present, fjall-rs -// This source code is licensed under both the Apache 2.0 and MIT License -// (found in the LICENSE-* files in the repository) - -use bytes::Bytes; - -pub use bytes::BytesMut as Builder; - -/// An immutable byte slice that can be cloned without additional heap allocation -/// -/// There is no guarantee of any sort of alignment for zero-copy (de)serialization. -#[derive(Debug, Default, Clone, Eq, Hash, Ord)] -pub struct Slice(pub(super) Bytes); - -impl Slice { - /// Construct a [`Slice`] from a byte slice. - #[must_use] - pub fn new(bytes: &[u8]) -> Self { - Self(Bytes::copy_from_slice(bytes)) - } - - /// Returns the bytes slice containing the entire data. - #[must_use] - pub fn as_slice(&self) -> &[u8] { - self - } - - #[doc(hidden)] - #[must_use] - pub fn empty() -> Self { - Self::default() - } - - #[doc(hidden)] - pub unsafe fn builder_unzeroed(len: usize) -> Builder { - // Use `with_capacity` & `set_len`` to avoid zeroing the buffer - let mut builder = Builder::with_capacity(len); - - // SAFETY: we just allocated `len` bytes, and `read_exact` will fail if - // it doesn't fill the buffer, subsequently dropping the uninitialized - // BytesMut object - #[expect(unsafe_code, reason = "see safety")] - unsafe { - builder.set_len(len); - } - - builder - } - - #[doc(hidden)] - #[must_use] - pub fn slice(&self, range: impl std::ops::RangeBounds) -> Self { - Self(self.0.slice(range)) - } - - #[doc(hidden)] - #[must_use] - pub fn fused(left: &[u8], right: &[u8]) -> Self { - use std::io::Write; - - let len = left.len() + right.len(); - let mut builder = unsafe { Self::builder_unzeroed(len) }; - { - let mut writer = &mut builder[..]; - - writer.write_all(left).expect("should write"); - writer.write_all(right).expect("should write"); - } - - Self(builder.freeze()) - } - - /// Constructs a [`Slice`] from an I/O reader by pulling in `len` bytes. - /// - /// The reader may not read the existing buffer. - #[doc(hidden)] - pub fn from_reader(reader: &mut R, len: usize) -> std::io::Result { - let mut builder = unsafe { Self::builder_unzeroed(len) }; - - // SAFETY: Normally, read_exact over an uninitialized buffer is UB, - // however we know that in lsm-tree etc. only I/O readers or cursors over Vecs are used - // so it's safe - // - // The safe API is unstable: https://github.com/rust-lang/rust/issues/78485 - reader.read_exact(&mut builder)?; - - Ok(Self(builder.freeze())) - } -} - -impl From for Slice { - fn from(value: Bytes) -> Self { - Self(value) - } -} - -impl From for Bytes { - fn from(value: Slice) -> Self { - value.0 - } -} - -// Bytes::from> is zero-copy optimized -impl From> for Slice { - fn from(value: Vec) -> Self { - Self(Bytes::from(value)) - } -} - -// Bytes::from is zero-copy optimized -impl From for Slice { - fn from(value: String) -> Self { - Self(Bytes::from(value)) - } -} diff --git a/crates/lsm-tree/src/stop_signal.rs b/crates/lsm-tree/src/stop_signal.rs index 8d83cfaab..5eaa4c91a 100644 --- a/crates/lsm-tree/src/stop_signal.rs +++ b/crates/lsm-tree/src/stop_signal.rs @@ -2,7 +2,7 @@ // This source code is licensed under both the Apache 2.0 and MIT License // (found in the LICENSE-* files in the repository) -use std::sync::{atomic::AtomicBool, Arc}; +use std::sync::{Arc, atomic::AtomicBool}; #[derive(Clone, Debug, Default)] pub struct StopSignal(Arc); diff --git a/crates/lsm-tree/src/table/block_index/two_level.rs b/crates/lsm-tree/src/table/block_index/two_level.rs index 3d0e3b5fe..c30b8583a 100644 --- a/crates/lsm-tree/src/table/block_index/two_level.rs +++ b/crates/lsm-tree/src/table/block_index/two_level.rs @@ -15,9 +15,6 @@ use crate::{ }; use std::{path::PathBuf, sync::Arc}; -#[cfg(feature = "metrics")] -use crate::Metrics; - /// Index that translates item keys to data block handles /// /// Only the top-level index is loaded into memory. @@ -28,9 +25,6 @@ pub struct TwoLevelBlockIndex { pub(crate) file_accessor: FileAccessor, pub(crate) cache: Arc, pub(crate) compression: CompressionType, - - #[cfg(feature = "metrics")] - pub(crate) metrics: Arc, } impl TwoLevelBlockIndex { @@ -47,9 +41,6 @@ impl TwoLevelBlockIndex { file_accessor: self.file_accessor.clone(), cache: self.cache.clone(), compression: self.compression, - - #[cfg(feature = "metrics")] - metrics: self.metrics.clone(), } } } @@ -69,9 +60,6 @@ pub struct Iter { file_accessor: FileAccessor, cache: Arc, compression: CompressionType, - - #[cfg(feature = "metrics")] - metrics: Arc, } impl Iter { @@ -133,8 +121,6 @@ impl Iterator for Iter { &handle.into_inner(), BlockType::Index, self.compression, - #[cfg(feature = "metrics")] - &self.metrics, )); let index_block = IndexBlock::new(block); @@ -196,8 +182,6 @@ impl DoubleEndedIterator for Iter { &handle.into_inner(), BlockType::Index, self.compression, - #[cfg(feature = "metrics")] - &self.metrics, )); let index_block = IndexBlock::new(block); diff --git a/crates/lsm-tree/src/table/block_index/volatile.rs b/crates/lsm-tree/src/table/block_index/volatile.rs index ad957d3ec..0e7fce1f6 100644 --- a/crates/lsm-tree/src/table/block_index/volatile.rs +++ b/crates/lsm-tree/src/table/block_index/volatile.rs @@ -15,9 +15,6 @@ use crate::{ }; use std::{path::PathBuf, sync::Arc}; -#[cfg(feature = "metrics")] -use crate::Metrics; - /// Index that translates item keys to data block handles /// /// The index is loaded on demand. @@ -28,9 +25,6 @@ pub struct VolatileBlockIndex { pub(crate) cache: Arc, pub(crate) handle: BlockHandle, pub(crate) compression: CompressionType, - - #[cfg(feature = "metrics")] - pub(crate) metrics: Arc, } impl VolatileBlockIndex { @@ -56,9 +50,6 @@ pub struct Iter { lo: Option<(UserKey, SeqNo)>, hi: Option<(UserKey, SeqNo)>, - - #[cfg(feature = "metrics")] - pub(crate) metrics: Arc, } impl Iter { @@ -74,9 +65,6 @@ impl Iter { lo: None, hi: None, - - #[cfg(feature = "metrics")] - metrics: index.metrics.clone(), } } } @@ -108,8 +96,6 @@ impl Iterator for Iter { &self.handle, BlockType::Index, self.compression, - #[cfg(feature = "metrics")] - &self.metrics, )); let index_block = IndexBlock::new(block); @@ -148,8 +134,6 @@ impl DoubleEndedIterator for Iter { &self.handle, BlockType::Index, self.compression, - #[cfg(feature = "metrics")] - &self.metrics, )); let index_block = IndexBlock::new(block); diff --git a/crates/lsm-tree/src/table/data_block/mod.rs b/crates/lsm-tree/src/table/data_block/mod.rs index 777f3b9dd..ad0074f52 100644 --- a/crates/lsm-tree/src/table/data_block/mod.rs +++ b/crates/lsm-tree/src/table/data_block/mod.rs @@ -851,39 +851,6 @@ mod tests { Ok(()) } - #[test] - fn data_block_vhandle() -> crate::Result<()> { - let items = [InternalValue::from_components( - "abc", - "world", - 1, - crate::ValueType::Indirection, - )]; - - for restart_interval in 1..=16 { - let bytes = DataBlock::encode_into_vec(&items, restart_interval, 0.0)?; - let serialized_len = bytes.len(); - - let data_block = DataBlock::new(Block { - data: bytes.into(), - header: Header { - block_type: BlockType::Data, - checksum: Checksum::from_raw(0), - data_length: 0, - uncompressed_length: 0, - }, - }); - - assert_eq!(data_block.len(), items.len()); - assert_eq!(data_block.inner.size(), serialized_len); - - assert_eq!(Some(items[0].clone()), data_block.point_read(b"abc", 777)); - assert!(data_block.point_read(b"abc", 1).is_none()); - } - - Ok(()) - } - #[test] fn data_block_mvcc_read_first() -> crate::Result<()> { let items = [InternalValue::from_components( diff --git a/crates/lsm-tree/src/table/filter/blocked_bloom/builder._rs b/crates/lsm-tree/src/table/filter/blocked_bloom/builder._rs deleted file mode 100644 index 4aac77c8b..000000000 --- a/crates/lsm-tree/src/table/filter/blocked_bloom/builder._rs +++ /dev/null @@ -1,169 +0,0 @@ -// Copyright (c) 2024-present, fjall-rs -// This source code is licensed under both the Apache 2.0 and MIT License -// (found in the LICENSE-* files in the repository) - -use super::super::bit_array::Builder as BitArrayBuilder; -use super::super::standard_bloom::builder::secondary_hash; -use crate::{ - file::MAGIC_BYTES, - table::filter::{blocked_bloom::CACHE_LINE_BYTES, FilterType}, -}; -use byteorder::{LittleEndian, WriteBytesExt}; -use std::io::Write; - -#[derive(Debug)] -pub struct Builder { - /// Raw bytes exposed as bit array - inner: BitArrayBuilder, - - /// Number of hash functions - pub(crate) k: usize, - - /// Number of blocks in the blocked bloom filter - pub(crate) num_blocks: usize, -} - -impl Builder { - #[expect( - clippy::expect_used, - reason = "we write into a Vec, so no I/O error can happen" - )] - #[must_use] - pub fn build(&self) -> Vec { - let mut v = vec![]; - - // Write header - v.write_all(&MAGIC_BYTES).expect("should not fail"); - - // NOTE: Filter type - v.write_u8(FilterType::BlockedBloom.into()) - .expect("should not fail"); - - // NOTE: Hash type (unused) - v.write_u8(0).expect("should not fail"); - - v.write_u64::(self.num_blocks as u64) - .expect("should not fail"); - v.write_u64::(self.k as u64) - .expect("should not fail"); - v.write_all(self.inner.bytes()).expect("should not fail"); - - v - } - - /// Constructs a bloom filter that can hold `n` items - /// while maintaining a certain false positive rate `fpr`. - #[must_use] - pub fn with_fp_rate(n: usize, fpr: f32) -> Self { - use std::f32::consts::LN_2; - - assert!(n > 0); - - // NOTE: Some sensible minimum - let fpr = fpr.max(0.000_000_1); - - // NOTE: We add ~5-25% more bits to account for blocked bloom filters being a bit less accurate - // See https://dl.acm.org/doi/10.1145/1498698.1594230 - let bonus = match fpr { - _ if fpr <= 0.001 => 1.25, - _ if fpr <= 0.01 => 1.2, - _ if fpr <= 0.1 => 1.1, - _ => 1.05, - }; - - let m = ((Self::calculate_m(n, fpr)) as f32 * bonus) as usize; - let bpk = m / n; - let k = (((bpk as f32) * LN_2) as usize).max(1); - - let num_blocks = m.div_ceil(CACHE_LINE_BYTES * 8); - - Self { - inner: BitArrayBuilder::with_capacity(num_blocks * CACHE_LINE_BYTES), - k, - num_blocks, - } - } - - /// Constructs a bloom filter that can hold `n` items - /// with `bpk` bits per key. - /// - /// 10 bits per key is a sensible default. - #[must_use] - pub fn with_bpk(n: usize, bpk: u8) -> Self { - use std::f32::consts::LN_2; - - assert!(bpk > 0); - assert!(n > 0); - - let bpk = bpk as usize; - - let m = n * bpk; - let k = (((bpk as f32) * LN_2) as usize).max(1); - - let num_blocks = m.div_ceil(CACHE_LINE_BYTES * 8); - - Self { - inner: BitArrayBuilder::with_capacity(num_blocks * CACHE_LINE_BYTES), - k, - num_blocks, - } - } - - fn calculate_m(n: usize, fp_rate: f32) -> usize { - use std::f32::consts::LN_2; - - let n = n as f32; - let ln2_squared = LN_2.powi(2); - - let numerator = n * fp_rate.ln(); - let m = -(numerator / ln2_squared); - - // Round up to next byte - ((m / 8.0).ceil() * 8.0) as usize - } - - /// Adds the key to the filter. - pub fn set_with_hash(&mut self, mut h1: u64) { - let mut h2 = secondary_hash(h1); - - let block_idx = h1 % (self.num_blocks as u64); - - for i in 1..(self.k as u64) { - let idx = h1 % (CACHE_LINE_BYTES as u64 * 8); - - #[expect( - clippy::cast_possible_truncation, - reason = "filters tend to be pretty small, definitely less than 4 GiB, even for large tables" - )] - self.inner - .enable_bit(Self::get_bit_idx(block_idx as usize, idx as usize)); - - h1 = h1.wrapping_add(h2); - h2 = h2.wrapping_mul(i); - } - } - - #[must_use] - pub fn get_bit_idx(block_idx: usize, idx_in_block: usize) -> usize { - block_idx * CACHE_LINE_BYTES * 8 + idx_in_block - } - - /// Gets the hash of a key. - #[must_use] - pub fn get_hash(key: &[u8]) -> u64 { - super::super::standard_bloom::Builder::get_hash(key) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use test_log::test; - - #[test] - fn bloom_calculate_m() { - assert_eq!(9_592, Builder::calculate_m(1_000, 0.01)); - assert_eq!(4_800, Builder::calculate_m(1_000, 0.1)); - assert_eq!(4_792_536, Builder::calculate_m(1_000_000, 0.1)); - } -} diff --git a/crates/lsm-tree/src/table/filter/blocked_bloom/mod._rs b/crates/lsm-tree/src/table/filter/blocked_bloom/mod._rs deleted file mode 100644 index da0fb689a..000000000 --- a/crates/lsm-tree/src/table/filter/blocked_bloom/mod._rs +++ /dev/null @@ -1,193 +0,0 @@ -// Copyright (c) 2024-present, fjall-rs -// This source code is licensed under both the Apache 2.0 and MIT License -// (found in the LICENSE-* files in the repository) - -mod builder; - -pub use builder::Builder; - -use super::bit_array::BitArrayReader; -use crate::{ - file::MAGIC_BYTES, - table::filter::{standard_bloom::builder::secondary_hash, FilterType}, -}; -use byteorder::{LittleEndian, ReadBytesExt}; -use std::io::{Cursor, Read}; - -const CACHE_LINE_BYTES: usize = 64; - -/// A blocked bloom filter -/// -/// Allows buffering the key hashes before actual filter construction -/// which is needed to properly calculate the filter size, as the number of items -/// are unknown during table construction. -/// -/// The filter uses double hashing instead of `k` hash functions, see: -/// -#[derive(Debug)] -pub struct BlockedBloomFilterReader<'a> { - /// Raw bytes exposed as bit array - inner: BitArrayReader<'a>, - - /// Number of hash functions - pub(crate) k: usize, - - /// Number of blocks in the blocked bloom filter - pub(crate) num_blocks: usize, -} - -impl<'a> BlockedBloomFilterReader<'a> { - pub fn new(slice: &'a [u8]) -> crate::Result { - let mut reader = Cursor::new(slice); - - // Check header - let mut magic = [0u8; MAGIC_BYTES.len()]; - reader.read_exact(&mut magic)?; - - if magic != MAGIC_BYTES { - return Err(crate::Error::InvalidHeader("BloomFilter")); - } - - // NOTE: Filter type - let filter_type = reader.read_u8()?; - let filter_type = FilterType::try_from(filter_type)?; - assert_eq!( - FilterType::BlockedBloom, - filter_type, - "Invalid filter type, got={filter_type:?}, expected={:?}", - FilterType::BlockedBloom, - ); - - // NOTE: Hash type (unused) - let hash_type = reader.read_u8()?; - assert_eq!(0, hash_type, "Invalid bloom hash type"); - - let num_blocks = reader.read_u64::()? as usize; - let k = reader.read_u64::()? as usize; - - let offset = reader.position() as usize; - - Ok(Self { - k, - num_blocks, - inner: BitArrayReader::new(slice.get(offset..).expect("should be in bounds")), - }) - } - - fn bytes(&self) -> &[u8] { - self.inner.bytes() - } - - /// Size of bloom filter in bytes - #[must_use] - fn len(&self) -> usize { - self.inner.bytes().len() - } - - /// Returns `true` if the hash may be contained. - /// - /// Will never have a false negative. - #[must_use] - pub fn contains_hash(&self, mut h1: u64) -> bool { - let mut h2 = secondary_hash(h1); - - let block_idx = h1 % (self.num_blocks as u64); - - for i in 1..(self.k as u64) { - let bit_idx = h1 % (CACHE_LINE_BYTES as u64 * 8); - - if !self.has_bit(block_idx as usize, bit_idx as usize) { - return false; - } - - h1 = h1.wrapping_add(h2); - h2 = h2.wrapping_mul(i); - } - - true - } - - /// Returns `true` if the bit at `idx` is `1`. - fn has_bit(&self, block_idx: usize, idx_in_block: usize) -> bool { - self.inner - .get(Builder::get_bit_idx(block_idx, idx_in_block)) - } - - /// Gets the hash of a key. - #[must_use] - pub fn get_hash(key: &[u8]) -> u64 { - Builder::get_hash(key) - } - - /// Returns `true` if the item may be contained. - /// - /// Will never have a false negative. - #[must_use] - pub fn contains(&self, key: &[u8]) -> bool { - self.contains_hash(Self::get_hash(key)) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use test_log::test; - - #[test] - fn filter_bloom_blocked_serde_round_trip() -> crate::Result<()> { - let mut filter = Builder::with_fp_rate(10, 0.0001); - - let keys = &[ - b"item0", b"item1", b"item2", b"item3", b"item4", b"item5", b"item6", b"item7", - b"item8", b"item9", - ]; - - for key in keys { - filter.set_with_hash(BlockedBloomFilterReader::get_hash(*key)); - } - - let filter_bytes = filter.build(); - let filter_copy = BlockedBloomFilterReader::new(&filter_bytes)?; - - assert_eq!(filter.k, filter_copy.k); - assert_eq!(filter.num_blocks, filter_copy.num_blocks); - assert!(!filter_copy.contains(b"asdasads")); - assert!(!filter_copy.contains(b"item10")); - assert!(!filter_copy.contains(b"cxycxycxy")); - - Ok(()) - } - - #[test] - fn filter_bloom_blocked_basic() -> crate::Result<()> { - let mut filter = Builder::with_fp_rate(10, 0.0001); - - let keys = [ - b"item0" as &[u8], - b"item1", - b"item2", - b"item3", - b"item4", - b"item5", - b"item6", - b"item7", - b"item8", - b"item9", - ]; - - for key in &keys { - filter.set_with_hash(Builder::get_hash(key)); - } - - let filter_bytes = filter.build(); - let filter = BlockedBloomFilterReader::new(&filter_bytes)?; - - for key in &keys { - assert!(filter.contains(key)); - } - - assert!(!filter.contains(b"asdasdasdasdasdasdasd")); - - Ok(()) - } -} diff --git a/crates/lsm-tree/src/table/filter/mod.rs b/crates/lsm-tree/src/table/filter/mod.rs index 012d9ce3f..f4ee4d9cd 100644 --- a/crates/lsm-tree/src/table/filter/mod.rs +++ b/crates/lsm-tree/src/table/filter/mod.rs @@ -4,7 +4,6 @@ pub mod bit_array; pub mod block; -// pub mod blocked_bloom; pub mod standard_bloom; use standard_bloom::Builder as StandardBloomFilterBuilder; diff --git a/crates/lsm-tree/src/table/id.rs b/crates/lsm-tree/src/table/id.rs index 309c5e3d8..05b952f5f 100644 --- a/crates/lsm-tree/src/table/id.rs +++ b/crates/lsm-tree/src/table/id.rs @@ -2,9 +2,9 @@ // This source code is licensed under both the Apache 2.0 and MIT License // (found in the LICENSE-* files in the repository) -use crate::tree::inner::TreeId; +use crate::{SequenceNumberCounter, tree::inner::TreeId}; -pub type TableId = u64; +pub type TableId = u32; #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct GlobalTableId(TreeId, TableId); @@ -22,11 +22,15 @@ impl GlobalTableId { } impl From<(TreeId, TableId)> for GlobalTableId { - fn from((tid, sid): (TreeId, TableId)) -> Self { - Self(tid, sid) + fn from((tree_id, table_id): (TreeId, TableId)) -> Self { + Self(tree_id, table_id) } } +pub(crate) fn next_table_id(counter: &SequenceNumberCounter) -> TableId { + counter.next().try_into().expect("ran out of table IDs") +} + #[cfg(test)] mod tests { use super::*; @@ -40,5 +44,6 @@ mod tests { assert_eq!(global_table_id.tree_id(), 42); assert_eq!(global_table_id.table_id(), 7); + assert_eq!(size_of::(), 8); } } diff --git a/crates/lsm-tree/src/table/inner.rs b/crates/lsm-tree/src/table/inner.rs index 749768e48..5c0336198 100644 --- a/crates/lsm-tree/src/table/inner.rs +++ b/crates/lsm-tree/src/table/inner.rs @@ -2,9 +2,6 @@ // This source code is licensed under both the Apache 2.0 and MIT License // (found in the LICENSE-* files in the repository) -#[cfg(feature = "metrics")] -use crate::metrics::Metrics; - use super::{block_index::BlockIndexImpl, meta::ParsedMeta, regions::ParsedRegions}; use crate::{ Checksum, GlobalTableId, SeqNo, @@ -15,7 +12,7 @@ use crate::{ }; use std::{ path::PathBuf, - sync::{Arc, OnceLock, atomic::AtomicBool}, + sync::{Arc, atomic::AtomicBool}, }; pub struct Inner { @@ -58,13 +55,6 @@ pub struct Inner { pub(super) checksum: Checksum, pub(super) global_seqno: SeqNo, - - #[cfg(feature = "metrics")] - pub(crate) metrics: Arc, - - /// Cached sum of referenced blob file bytes for this table. - /// Lazily computed on first access to avoid repeated I/O in compaction decisions. - pub(crate) cached_blob_bytes: OnceLock, } impl Inner { diff --git a/crates/lsm-tree/src/table/iter.rs b/crates/lsm-tree/src/table/iter.rs index 1eb9c0193..9cdf509d4 100644 --- a/crates/lsm-tree/src/table/iter.rs +++ b/crates/lsm-tree/src/table/iter.rs @@ -16,9 +16,6 @@ use crate::{ use self_cell::self_cell; use std::{path::PathBuf, sync::Arc}; -#[cfg(feature = "metrics")] -use crate::metrics::Metrics; - type InnerIter<'a> = DataBlockIter<'a>; pub enum Bound { @@ -113,9 +110,6 @@ pub struct Iter { hi_data_block: Option, range: Bounds, - - #[cfg(feature = "metrics")] - metrics: Arc, } impl Iter { @@ -127,7 +121,6 @@ impl Iter { file_accessor: FileAccessor, cache: Arc, compression: CompressionType, - #[cfg(feature = "metrics")] metrics: Arc, ) -> Self { Self { table_id, @@ -149,9 +142,6 @@ impl Iter { hi_data_block: None, range: (None, None), - - #[cfg(feature = "metrics")] - metrics, } } @@ -257,8 +247,6 @@ impl Iterator for Iter { &BlockHandle::new(handle.offset(), handle.size()), crate::table::block::BlockType::Data, self.compression, - #[cfg(feature = "metrics")] - &self.metrics, )) } }; @@ -378,8 +366,6 @@ impl DoubleEndedIterator for Iter { &BlockHandle::new(handle.offset(), handle.size()), crate::table::block::BlockType::Data, self.compression, - #[cfg(feature = "metrics")] - &self.metrics, )) } }; diff --git a/crates/lsm-tree/src/table/meta.rs b/crates/lsm-tree/src/table/meta.rs index 946c855ef..646cb97f0 100644 --- a/crates/lsm-tree/src/table/meta.rs +++ b/crates/lsm-tree/src/table/meta.rs @@ -74,6 +74,17 @@ macro_rules! read_u64 { }}; } +macro_rules! read_u32 { + ($block:expr, $name:expr) => {{ + let bytes = $block + .point_read($name, SeqNo::MAX) + .unwrap_or_else(|| panic!("meta property {:?} should exist", $name)); + + let mut bytes = &bytes.value[..]; + bytes.read_u32::()? + }}; +} + impl ParsedMeta { #[expect(clippy::expect_used, clippy::too_many_lines)] pub fn load_with_handle(file: &File, handle: &BlockHandle) -> crate::Result { @@ -95,11 +106,7 @@ impl ParsedMeta { .expect("Table version should exist") .value; - assert!( - matches!(&*table_version, [3u8 | 4u8]), - "unsupported table version {}", - table_version[0], - ); + assert_eq!(&*table_version, [5], "unsupported table version"); } { @@ -136,7 +143,7 @@ impl ParsedMeta { "index block restart intervals >1 are not supported for this version", ); - let id = read_u64!(block, b"table_id"); + let id = read_u32!(block, b"table_id"); let item_count = read_u64!(block, b"item_count"); let tombstone_count = read_u64!(block, b"tombstone_count"); let data_block_count = read_u64!(block, b"block_count#data"); diff --git a/crates/lsm-tree/src/table/mod.rs b/crates/lsm-tree/src/table/mod.rs index 38da2de39..553b4c46b 100644 --- a/crates/lsm-tree/src/table/mod.rs +++ b/crates/lsm-tree/src/table/mod.rs @@ -22,6 +22,7 @@ mod tests; pub use block::{Block, BlockOffset}; pub use data_block::DataBlock; +pub(crate) use id::next_table_id; pub use id::{GlobalTableId, TableId}; pub use index_block::{BlockHandle, IndexBlock, KeyedBlockHandle}; pub use scanner::Scanner; @@ -37,7 +38,6 @@ use crate::{ block_index::{BlockIndex, FullBlockIndex, TwoLevelBlockIndex, VolatileBlockIndex}, filter::block::FilterBlock, regions::ParsedRegions, - writer::LinkedFile, }, value::PointReadValue, }; @@ -53,9 +53,6 @@ use std::{ }; use util::load_block; -#[cfg(feature = "metrics")] -use crate::metrics::Metrics; - pub type TableInner = Inner; /// A disk segment (a.k.a. `Table`, `SSTable`, `SST`, `sorted string table`) that is located on disk @@ -91,70 +88,6 @@ impl Table { self.0.global_seqno } - pub fn referenced_blob_bytes(&self) -> crate::Result { - if let Some(v) = self.0.cached_blob_bytes.get() { - return Ok(*v); - } - - let sum = self - .list_blob_file_references()? - .map(|bf| bf.iter().map(|f| f.on_disk_bytes).sum::()) - .unwrap_or_default(); - - let _ = self.0.cached_blob_bytes.set(sum); - Ok(sum) - } - - pub fn list_blob_file_references(&self) -> crate::Result>> { - use byteorder::{LE, ReadBytesExt}; - - Ok(if let Some(handle) = &self.regions.linked_blob_files { - let table_id = self.global_id(); - - let (fd, fd_cache_miss) = - if let Some(fd) = self.file_accessor.access_for_table(&table_id) { - (fd, false) - } else { - (Arc::new(File::open(&*self.path)?), true) - }; - - // Read the exact region using pread-style helper - let buf = crate::file::read_exact(&fd, *handle.offset(), handle.size() as usize)?; - - // If we opened the file here, cache the FD for future accesses - if fd_cache_miss { - self.file_accessor.insert_for_table(table_id, fd); - } - - // Parse the buffer - let mut reader = &buf[..]; - let len = reader.read_u32::()?; - let mut blob_files = Vec::with_capacity(len as usize); - - for _ in 0..len { - let blob_file_id = reader.read_u64::()?; - let len = reader.read_u64::()?; - let bytes = reader.read_u64::()?; - let on_disk_bytes = reader.read_u64::()?; - - #[expect( - clippy::cast_possible_truncation, - reason = "truncation is not expected to happen" - )] - blob_files.push(LinkedFile { - blob_file_id, - bytes, - len: len as usize, - on_disk_bytes, - }); - } - - Some(blob_files) - } else { - None - }) - } - /// Gets the global table ID. #[must_use] fn global_id(&self) -> GlobalTableId { @@ -208,8 +141,6 @@ impl Table { handle, block_type, compression, - #[cfg(feature = "metrics")] - &self.metrics, ) } @@ -252,9 +183,6 @@ impl Table { key_hash: u64, point_read: impl FnOnce(&Self, &[u8], SeqNo) -> crate::Result>, ) -> crate::Result> { - #[cfg(feature = "metrics")] - use std::sync::atomic::Ordering::Relaxed; - // Translate seqno to "our" seqno let seqno = seqno.saturating_sub(self.global_seqno()); @@ -299,35 +227,11 @@ impl Table { if let Some(filter_block) = &filter_block { if !filter_block.maybe_contains_hash(key_hash)? { - #[cfg(feature = "metrics")] - { - self.metrics.filter_queries.fetch_add(1, Relaxed); - self.metrics.io_skipped_by_filter.fetch_add(1, Relaxed); - } - return Ok(None); } } - let item = point_read(self, key, seqno); - - #[cfg(not(feature = "metrics"))] - { - item - } - - #[cfg(feature = "metrics")] - { - // NOTE: Only increment the filter queries when the filter reported a miss - // and we actually waste an I/O for a non-existing item. - // Otherwise, the filter efficiency decreases whenever an item is hit. - // https://github.com/fjall-rs/lsm-tree/issues/246 - item.inspect(|maybe_kv| { - if maybe_kv.is_none() && filter_block.is_some() { - self.metrics.filter_queries.fetch_add(1, Relaxed); - } - }) - } + point_read(self, key, seqno) } fn point_read(&self, key: &[u8], seqno: SeqNo) -> crate::Result> { @@ -432,8 +336,6 @@ impl Table { self.file_accessor.clone(), self.cache.clone(), self.metadata.data_block_compression, - #[cfg(feature = "metrics")] - self.metrics.clone(), ); match range.start_bound() { @@ -485,7 +387,6 @@ impl Table { descriptor_table: Option>, pin_filter: bool, pin_index: bool, - #[cfg(feature = "metrics")] metrics: Arc, ) -> crate::Result { use meta::ParsedMeta; use regions::ParsedRegions; @@ -495,12 +396,20 @@ impl Table { let mut file = std::fs::File::open(&file_path)?; let file_path = Arc::new(file_path); - #[cfg(feature = "metrics")] - metrics - .table_file_opened_uncached - .fetch_add(1, std::sync::atomic::Ordering::Relaxed); - let trailer = sfa::Reader::from_reader(&mut file)?; + + let table_version = trailer + .toc() + .section(b"table_version") + .ok_or(crate::Error::Unrecoverable)?; + if table_version.len() != 1 { + return Err(crate::Error::Unrecoverable); + } + let version = byteorder::ReadBytesExt::read_u8(&mut table_version.buf_reader(&file_path)?)?; + if version != 5 { + return Err(crate::Error::InvalidVersion(version)); + } + let regions = ParsedRegions::parse_from_toc(trailer.toc())?; log::trace!("Reading meta block, with meta_ptr={:?}", regions.metadata); @@ -529,9 +438,6 @@ impl Table { path: Arc::clone(&file_path), file_accessor: file_accessor.clone(), table_id: (tree_id, metadata.id).into(), - - #[cfg(feature = "metrics")] - metrics: metrics.clone(), }) } else if pin_index { log::trace!( @@ -551,9 +457,6 @@ impl Table { handle: regions.tli, path: Arc::clone(&file_path), table_id: (tree_id, metadata.id).into(), - - #[cfg(feature = "metrics")] - metrics: metrics.clone(), }) }; @@ -624,11 +527,6 @@ impl Table { checksum, global_seqno, - - #[cfg(feature = "metrics")] - metrics, - - cached_blob_bytes: std::sync::OnceLock::new(), }))) } diff --git a/crates/lsm-tree/src/table/multi_writer.rs b/crates/lsm-tree/src/table/multi_writer.rs index 1464028e7..44a31309d 100644 --- a/crates/lsm-tree/src/table/multi_writer.rs +++ b/crates/lsm-tree/src/table/multi_writer.rs @@ -2,11 +2,9 @@ // This source code is licensed under both the Apache 2.0 and MIT License // (found in the LICENSE-* files in the repository) -use super::{filter::BloomConstructionPolicy, writer::Writer}; +use super::{filter::BloomConstructionPolicy, id::next_table_id, writer::Writer}; use crate::{ - Checksum, CompressionType, HashMap, SequenceNumberCounter, TableId, UserKey, - blob_tree::handle::BlobIndirection, table::writer::LinkedFile, value::InternalValue, - vlog::BlobFileId, + Checksum, CompressionType, SequenceNumberCounter, TableId, UserKey, value::InternalValue, }; use std::path::PathBuf; @@ -44,9 +42,6 @@ pub struct MultiWriter { bloom_policy: BloomConstructionPolicy, current_key: Option, - has_items: bool, - - linked_blobs: HashMap, /// Level the tables are written to initial_level: u8, @@ -60,7 +55,7 @@ impl MultiWriter { target_size: u64, initial_level: u8, ) -> crate::Result { - let current_table_id = table_id_generator.next(); + let current_table_id = next_table_id(&table_id_generator); let path = base_path.join(current_table_id.to_string()); let writer = Writer::new(path, current_table_id, initial_level)?; @@ -91,28 +86,9 @@ impl MultiWriter { bloom_policy: BloomConstructionPolicy::default(), current_key: None, - has_items: false, - - linked_blobs: HashMap::default(), }) } - pub fn register_blob(&mut self, indirection: BlobIndirection) { - self.linked_blobs - .entry(indirection.vhandle.blob_file_id) - .and_modify(|entry| { - entry.bytes += u64::from(indirection.size); - entry.on_disk_bytes += u64::from(indirection.vhandle.on_disk_size); - entry.len += 1; - }) - .or_insert_with(|| LinkedFile { - blob_file_id: indirection.vhandle.blob_file_id, - bytes: u64::from(indirection.size), - on_disk_bytes: u64::from(indirection.vhandle.on_disk_size), - len: 1, - }); - } - #[must_use] pub fn use_partitioned_index(mut self) -> Self { self.use_partitioned_index = true; @@ -184,7 +160,7 @@ impl MultiWriter { fn rotate(&mut self) -> crate::Result<()> { log::debug!("Rotating table writer"); - let new_table_id = self.table_id_generator.next(); + let new_table_id = next_table_id(&self.table_id_generator); let path = self.base_path.join(new_table_id.to_string()); let mut new_writer = Writer::new(path, new_table_id, self.initial_level)? @@ -203,17 +179,7 @@ impl MultiWriter { new_writer = new_writer.use_partitioned_filter(); } - let mut old_writer = std::mem::replace(&mut self.writer, new_writer); - - for linked in self.linked_blobs.values() { - old_writer.link_blob_file( - linked.blob_file_id, - linked.len, - linked.bytes, - linked.on_disk_bytes, - ); - } - self.linked_blobs.clear(); + let old_writer = std::mem::replace(&mut self.writer, new_writer); if let Some((table_id, checksum)) = old_writer.finish()? { self.results.push((table_id, checksum)); @@ -235,7 +201,6 @@ impl MultiWriter { } self.writer.write(item)?; - self.has_items = true; Ok(()) } @@ -246,32 +211,22 @@ impl MultiWriter { } self.writer.write_distinct(item)?; - self.has_items = true; Ok(()) } pub(crate) fn is_empty(&self) -> bool { - !self.has_items + self.results.is_empty() && self.writer.meta.first_key.is_none() } /// Finishes the last table, making sure all data is written durably /// /// Returns the metadata of created tables - pub fn finish(mut self) -> crate::Result> { - for linked in self.linked_blobs.values() { - self.writer.link_blob_file( - linked.blob_file_id, - linked.len, - linked.bytes, - linked.on_disk_bytes, - ); - } - + pub fn finish(mut self) -> crate::Result<(PathBuf, Vec<(TableId, Checksum)>)> { if let Some((table_id, checksum)) = self.writer.finish()? { self.results.push((table_id, checksum)); } - Ok(self.results) + Ok((self.base_path, self.results)) } } diff --git a/crates/lsm-tree/src/table/regions.rs b/crates/lsm-tree/src/table/regions.rs index 5644d2f2b..1ee27de94 100644 --- a/crates/lsm-tree/src/table/regions.rs +++ b/crates/lsm-tree/src/table/regions.rs @@ -31,10 +31,6 @@ fn toc_entry_to_handle(entry: &TocEntry) -> BlockHandle { /// |--------------| /// | filter | <- may not exist /// |--------------| -/// | ... | -/// |--------------| -/// | linked blobs | <- may not exist -/// |--------------| /// | meta | /// |--------------| /// | toc | @@ -47,7 +43,6 @@ pub struct ParsedRegions { pub index: Option, pub filter_tli: Option, pub filter: Option, - pub linked_blob_files: Option, pub metadata: BlockHandle, } @@ -64,7 +59,6 @@ impl ParsedRegions { })?, index: toc.section(b"index").map(toc_entry_to_handle), filter: toc.section(b"filter").map(toc_entry_to_handle), - linked_blob_files: toc.section(b"linked_blob_files").map(toc_entry_to_handle), metadata: toc .section(b"meta") .map(toc_entry_to_handle) diff --git a/crates/lsm-tree/src/table/tests.rs b/crates/lsm-tree/src/table/tests.rs index 622564936..37e37bea5 100644 --- a/crates/lsm-tree/src/table/tests.rs +++ b/crates/lsm-tree/src/table/tests.rs @@ -42,9 +42,6 @@ fn test_with_table( let (_, checksum) = writer.finish()?.unwrap(); { - #[cfg(feature = "metrics")] - let metrics = Arc::new(Metrics::default()); - let table = Table::recover( file.clone(), checksum, @@ -54,8 +51,6 @@ fn test_with_table( Some(Arc::new(DescriptorTable::new(10))), false, false, - #[cfg(feature = "metrics")] - metrics, )?; assert_eq!(0, table.id()); @@ -72,9 +67,6 @@ fn test_with_table( } { - #[cfg(feature = "metrics")] - let metrics = Arc::new(Metrics::default()); - let table = Table::recover( file.clone(), checksum, @@ -84,8 +76,6 @@ fn test_with_table( Some(Arc::new(DescriptorTable::new(10))), true, false, - #[cfg(feature = "metrics")] - metrics, )?; assert_eq!(0, table.id()); @@ -102,9 +92,6 @@ fn test_with_table( } { - #[cfg(feature = "metrics")] - let metrics = Arc::new(Metrics::default()); - let table = Table::recover( file.clone(), checksum, @@ -114,8 +101,6 @@ fn test_with_table( Some(Arc::new(DescriptorTable::new(10))), false, true, - #[cfg(feature = "metrics")] - metrics, )?; assert_eq!(0, table.id()); @@ -132,9 +117,6 @@ fn test_with_table( } { - #[cfg(feature = "metrics")] - let metrics = Arc::new(Metrics::default()); - let table = Table::recover( file.clone(), checksum, @@ -144,8 +126,6 @@ fn test_with_table( Some(Arc::new(DescriptorTable::new(10))), true, true, - #[cfg(feature = "metrics")] - metrics, )?; assert_eq!(0, table.id()); @@ -162,9 +142,6 @@ fn test_with_table( } { - #[cfg(feature = "metrics")] - let metrics = Arc::new(Metrics::default()); - let table = Table::recover( file.clone(), checksum, @@ -174,8 +151,6 @@ fn test_with_table( None, true, true, - #[cfg(feature = "metrics")] - metrics, )?; assert_eq!(0, table.id()); @@ -210,9 +185,6 @@ fn test_with_table( let (_, checksum) = writer.finish()?.unwrap(); { - #[cfg(feature = "metrics")] - let metrics = Arc::new(Metrics::default()); - let table = Table::recover( file.clone(), checksum, @@ -222,8 +194,6 @@ fn test_with_table( Some(Arc::new(DescriptorTable::new(10))), false, false, - #[cfg(feature = "metrics")] - metrics, )?; assert_eq!(0, table.id()); @@ -239,9 +209,6 @@ fn test_with_table( } { - #[cfg(feature = "metrics")] - let metrics = Arc::new(Metrics::default()); - let table = Table::recover( file.clone(), checksum, @@ -251,8 +218,6 @@ fn test_with_table( Some(Arc::new(DescriptorTable::new(10))), true, false, - #[cfg(feature = "metrics")] - metrics, )?; assert_eq!(0, table.id()); @@ -268,9 +233,6 @@ fn test_with_table( } { - #[cfg(feature = "metrics")] - let metrics = Arc::new(Metrics::default()); - let table = Table::recover( file.clone(), checksum, @@ -280,8 +242,6 @@ fn test_with_table( Some(Arc::new(DescriptorTable::new(10))), false, true, - #[cfg(feature = "metrics")] - metrics, )?; assert_eq!(0, table.id()); @@ -298,9 +258,6 @@ fn test_with_table( } { - #[cfg(feature = "metrics")] - let metrics = Arc::new(Metrics::default()); - let table = Table::recover( file.clone(), checksum, @@ -310,8 +267,6 @@ fn test_with_table( Some(Arc::new(DescriptorTable::new(10))), true, true, - #[cfg(feature = "metrics")] - metrics, )?; assert_eq!(0, table.id()); @@ -328,9 +283,6 @@ fn test_with_table( } { - #[cfg(feature = "metrics")] - let metrics = Arc::new(Metrics::default()); - let table = Table::recover( file, checksum, @@ -340,8 +292,6 @@ fn test_with_table( None, true, true, - #[cfg(feature = "metrics")] - metrics, )?; assert_eq!(0, table.id()); @@ -850,7 +800,6 @@ fn table_zero_bpk() -> crate::Result<()> { clippy::indexing_slicing, clippy::cast_possible_truncation )] -#[cfg(not(feature = "metrics"))] fn table_read_fuzz_1() -> crate::Result<()> { use crate::Slice; use crate::ValueType::{Tombstone, Value}; @@ -1299,8 +1248,6 @@ fn table_partitioned_index() -> crate::Result<()> { Some(Arc::new(crate::DescriptorTable::new(10))), true, true, - #[cfg(feature = "metrics")] - Default::default(), ) .unwrap(); @@ -1408,8 +1355,6 @@ fn table_global_seqno() -> crate::Result<()> { Some(Arc::new(crate::DescriptorTable::new(10))), true, true, - #[cfg(feature = "metrics")] - Default::default(), )?; // global seqno is 7, so a1 is = 8 -> can not be read by snapshot=8 @@ -1459,8 +1404,6 @@ fn table_return_global_seqno() -> crate::Result<()> { Some(Arc::new(crate::DescriptorTable::new(10))), true, true, - #[cfg(feature = "metrics")] - Default::default(), )?; assert_eq!( diff --git a/crates/lsm-tree/src/table/util.rs b/crates/lsm-tree/src/table/util.rs index 95dd8f6a7..3758a70c8 100644 --- a/crates/lsm-tree/src/table/util.rs +++ b/crates/lsm-tree/src/table/util.rs @@ -9,9 +9,6 @@ use crate::{ }; use std::{path::Path, sync::Arc}; -#[cfg(feature = "metrics")] -use crate::metrics::Metrics; - #[must_use] pub fn aggregate_run_key_range(tables: &[Table]) -> KeyRange { #[expect(clippy::expect_used, reason = "runs are never empty by definition")] @@ -37,41 +34,18 @@ pub fn load_block( handle: &BlockHandle, block_type: BlockType, compression: CompressionType, - #[cfg(feature = "metrics")] metrics: &Metrics, ) -> crate::Result { - #[cfg(feature = "metrics")] - use std::sync::atomic::Ordering::Relaxed; - log::trace!("load {block_type:?} block {handle:?}"); if let Some(block) = cache.get_block(table_id, handle.offset()) { - #[cfg(feature = "metrics")] - match block_type { - BlockType::Filter => { - metrics.filter_block_load_cached.fetch_add(1, Relaxed); - } - BlockType::Index => { - metrics.index_block_load_cached.fetch_add(1, Relaxed); - } - BlockType::Data | BlockType::Meta => { - metrics.data_block_load_cached.fetch_add(1, Relaxed); - } - } - return Ok(block); } let (fd, fd_cache_miss) = if let Some(cached_fd) = file_accessor.access_for_table(&table_id) { - #[cfg(feature = "metrics")] - metrics.table_file_opened_cached.fetch_add(1, Relaxed); - (cached_fd, false) } else { let fd = std::fs::File::open(path)?; - #[cfg(feature = "metrics")] - metrics.table_file_opened_uncached.fetch_add(1, Relaxed); - (Arc::new(fd), true) }; @@ -84,31 +58,6 @@ pub fn load_block( ))); } - #[cfg(feature = "metrics")] - match block_type { - BlockType::Filter => { - metrics.filter_block_load_io.fetch_add(1, Relaxed); - - metrics - .filter_block_io_requested - .fetch_add(handle.size().into(), Relaxed); - } - BlockType::Index => { - metrics.index_block_load_io.fetch_add(1, Relaxed); - - metrics - .index_block_io_requested - .fetch_add(handle.size().into(), Relaxed); - } - BlockType::Data | BlockType::Meta => { - metrics.data_block_load_io.fetch_add(1, Relaxed); - - metrics - .data_block_io_requested - .fetch_add(handle.size().into(), Relaxed); - } - } - // Cache FD if fd_cache_miss { file_accessor.insert_for_table(table_id, fd); diff --git a/crates/lsm-tree/src/table/writer/mod.rs b/crates/lsm-tree/src/table/writer/mod.rs index f7636c5aa..8c8804c27 100644 --- a/crates/lsm-tree/src/table/writer/mod.rs +++ b/crates/lsm-tree/src/table/writer/mod.rs @@ -23,19 +23,10 @@ use crate::{ }, }, time::unix_timestamp, - vlog::BlobFileId, }; use index::BlockIndexWriter; use std::{fs::File, io::BufWriter, path::PathBuf}; -#[derive(Copy, Clone, PartialEq, Eq, Debug, std::hash::Hash)] -pub struct LinkedFile { - pub blob_file_id: BlobFileId, - pub bytes: u64, - pub on_disk_bytes: u64, - pub len: usize, -} - #[derive(Default)] struct FixedLen { len: Option, @@ -115,8 +106,6 @@ pub struct Writer { /// Tracks the previously written item to detect weak tombstone/value pairs previous_item: Option<(UserKey, ValueType)>, - linked_blob_files: Vec, - initial_level: u8, } @@ -166,26 +155,9 @@ impl Writer { bloom_policy: BloomConstructionPolicy::default(), previous_item: None, - - linked_blob_files: Vec::new(), }) } - pub fn link_blob_file( - &mut self, - blob_file_id: BlobFileId, - len: usize, - bytes: u64, - on_disk_bytes: u64, - ) { - self.linked_blob_files.push(LinkedFile { - blob_file_id, - bytes, - on_disk_bytes, - len, - }); - } - #[must_use] pub fn use_partitioned_filter(mut self) -> Self { self.filter_writer = Box::new(filter::PartitionedFilterWriter::new(self.bloom_policy)) @@ -477,28 +449,8 @@ impl Writer { log::trace!("Finishing filter writer"); let filter_block_count = self.filter_writer.finish(&mut self.file_writer)?; - if !self.linked_blob_files.is_empty() { - use byteorder::{LE, WriteBytesExt}; - - self.file_writer.start("linked_blob_files")?; - - #[expect( - clippy::cast_possible_truncation, - reason = "there are never 4 billion blob files linked to a single table" - )] - self.file_writer - .write_u32::(self.linked_blob_files.len() as u32)?; - - for file in self.linked_blob_files { - self.file_writer.write_u64::(file.blob_file_id)?; - self.file_writer.write_u64::(file.len as u64)?; - self.file_writer.write_u64::(file.bytes)?; - self.file_writer.write_u64::(file.on_disk_bytes)?; - } - } - self.file_writer.start("table_version")?; - self.file_writer.write_all(&[0x4])?; + self.file_writer.write_all(&[0x5])?; // Write metadata self.file_writer.start("meta")?; @@ -567,7 +519,7 @@ impl Writer { meta("seqno#max", &self.meta.highest_seqno.to_le_bytes()), meta("seqno#min", &self.meta.lowest_seqno.to_le_bytes()), meta("table_id", &self.table_id.to_le_bytes()), - meta("table_version", &[4u8]), + meta("table_version", &[5u8]), meta( "tombstone_count", &(self.meta.tombstone_count as u64).to_le_bytes(), diff --git a/crates/lsm-tree/src/time.rs b/crates/lsm-tree/src/time.rs index 4cb0f3845..ee1686029 100644 --- a/crates/lsm-tree/src/time.rs +++ b/crates/lsm-tree/src/time.rs @@ -4,32 +4,9 @@ /// Gets the unix timestamp as a duration pub fn unix_timestamp() -> std::time::Duration { - #[cfg(test)] - #[allow(clippy::significant_drop_in_scrutinee, clippy::expect_used)] - { - if let Some(cell) = NOW_OVERRIDE.get() { - if let Some(override_val) = *cell.lock().expect("lock is poisoned") { - return override_val; - } - } - } - let now = std::time::SystemTime::now(); #[expect(clippy::expect_used, reason = "trivial")] now.duration_since(std::time::SystemTime::UNIX_EPOCH) .expect("time went backwards") } - -#[cfg(test)] -use std::sync::{Mutex, OnceLock}; - -#[cfg(test)] -static NOW_OVERRIDE: OnceLock>> = OnceLock::new(); - -#[cfg(test)] -#[allow(clippy::expect_used)] -pub fn set_unix_timestamp_for_test(value: Option) { - let cell = NOW_OVERRIDE.get_or_init(|| Mutex::new(None)); - *cell.lock().expect("lock is poisoned") = value; -} diff --git a/crates/lsm-tree/src/tree/ingest.rs b/crates/lsm-tree/src/tree/ingest.rs index 5dc5a9f37..ff066d2af 100644 --- a/crates/lsm-tree/src/tree/ingest.rs +++ b/crates/lsm-tree/src/tree/ingest.rs @@ -3,11 +3,7 @@ // (found in the LICENSE-* files in the repository) use super::Tree; -use crate::{ - BlobIndirection, SeqNo, UserKey, UserValue, config::FilterPolicyEntry, - table::multi_writer::MultiWriter, -}; -use std::path::PathBuf; +use crate::{UserKey, UserValue, config::FilterPolicyEntry, table::multi_writer::MultiWriter}; pub const INITIAL_CANONICAL_LEVEL: usize = 1; @@ -18,10 +14,9 @@ pub const INITIAL_CANONICAL_LEVEL: usize = 1; /// Ingested data bypasses memtables and is written directly into new tables, /// using the same table writer configuration that is used for flush and compaction. pub struct Ingestion<'a> { - folder: PathBuf, tree: &'a Tree, pub(crate) writer: MultiWriter, - seqno: SeqNo, + #[cfg(debug_assertions)] last_key: Option, } @@ -46,53 +41,49 @@ impl<'a> Ingestion<'a> { .get(INITIAL_CANONICAL_LEVEL); // TODO: maybe create a PrepareMultiWriter that can be used by flush, ingest and compaction worker - let mut writer = MultiWriter::new( - folder.clone(), - tree.table_id_counter.clone(), - 64 * 1_024 * 1_024, - 6, - )? - .use_bloom_policy({ - if tree.config.expect_point_read_hits { - crate::config::BloomConstructionPolicy::BitsPerKey(0.0) - } else if let FilterPolicyEntry::Bloom(p) = - tree.config.filter_policy.get(INITIAL_CANONICAL_LEVEL) - { - p - } else { - crate::config::BloomConstructionPolicy::BitsPerKey(0.0) - } - }) - .use_data_block_size( - tree.config - .data_block_size_policy - .get(INITIAL_CANONICAL_LEVEL), - ) - .use_data_block_hash_ratio( - tree.config - .data_block_hash_ratio_policy - .get(INITIAL_CANONICAL_LEVEL), - ) - .use_data_block_compression( - tree.config - .data_block_compression_policy - .get(INITIAL_CANONICAL_LEVEL), - ) - .use_index_block_compression( - tree.config - .index_block_compression_policy - .get(INITIAL_CANONICAL_LEVEL), - ) - .use_data_block_restart_interval( - tree.config - .data_block_restart_interval_policy - .get(INITIAL_CANONICAL_LEVEL), - ) - .use_index_block_restart_interval( - tree.config - .index_block_restart_interval_policy - .get(INITIAL_CANONICAL_LEVEL), - ); + let mut writer = + MultiWriter::new(folder, tree.table_id_counter.clone(), 64 * 1_024 * 1_024, 6)? + .use_bloom_policy({ + if tree.config.expect_point_read_hits { + crate::config::BloomConstructionPolicy::BitsPerKey(0.0) + } else if let FilterPolicyEntry::Bloom(p) = + tree.config.filter_policy.get(INITIAL_CANONICAL_LEVEL) + { + p + } else { + crate::config::BloomConstructionPolicy::BitsPerKey(0.0) + } + }) + .use_data_block_size( + tree.config + .data_block_size_policy + .get(INITIAL_CANONICAL_LEVEL), + ) + .use_data_block_hash_ratio( + tree.config + .data_block_hash_ratio_policy + .get(INITIAL_CANONICAL_LEVEL), + ) + .use_data_block_compression( + tree.config + .data_block_compression_policy + .get(INITIAL_CANONICAL_LEVEL), + ) + .use_index_block_compression( + tree.config + .index_block_compression_policy + .get(INITIAL_CANONICAL_LEVEL), + ) + .use_data_block_restart_interval( + tree.config + .data_block_restart_interval_policy + .get(INITIAL_CANONICAL_LEVEL), + ) + .use_index_block_restart_interval( + tree.config + .index_block_restart_interval_policy + .get(INITIAL_CANONICAL_LEVEL), + ); if index_partitioning { writer = writer.use_partitioned_index(); @@ -102,97 +93,45 @@ impl<'a> Ingestion<'a> { } Ok(Self { - folder, tree, writer, - seqno: 0, + #[cfg(debug_assertions)] last_key: None, }) } + #[cfg(debug_assertions)] + fn validate_key(&mut self, key: &UserKey) { + if let Some(previous) = &self.last_key { + debug_assert!( + key > previous, + "next key in ingestion must be greater than last key" + ); + } + self.last_key = Some(key.clone()); + } + /// Writes a key-value pair. /// /// # Errors /// /// Will return `Err` if an IO error occurs. - pub(crate) fn write_indirection( + pub fn write, V: Into>( &mut self, - key: UserKey, - indirection: BlobIndirection, + key: K, + value: V, ) -> crate::Result<()> { - use crate::coding::Encode; + let key = key.into(); + let value = value.into(); - if let Some(prev) = &self.last_key { - assert!( - key > *prev, - "next key in ingestion must be greater than last key" - ); - } - - let cloned_key = key.clone(); - self.writer - .write_distinct(crate::InternalValue::from_components( - key, - indirection.encode_into_vec(), - self.seqno, - crate::ValueType::Indirection, - ))?; - - self.writer.register_blob(indirection); - - // Remember the last user key to validate the next call's ordering - self.last_key = Some(cloned_key); - - Ok(()) - } - - /// Writes a key-value pair. - /// - /// # Errors - /// - /// Will return `Err` if an IO error occurs. - pub fn write(&mut self, key: UserKey, value: UserValue) -> crate::Result<()> { - if let Some(prev) = &self.last_key { - assert!( - key > *prev, - "next key in ingestion must be greater than last key" - ); - } - - self.writer - .write_distinct(crate::InternalValue::from_components( - key.clone(), - value, - self.seqno, - crate::ValueType::Value, - ))?; - - // Remember the last user key to validate the next call's ordering - self.last_key = Some(key); - - Ok(()) - } - - /// Writes a key that the caller has already proven is greater than every - /// preceding key. Debug builds retain the ordering assertion. - #[doc(hidden)] - pub fn write_prevalidated(&mut self, key: UserKey, value: UserValue) -> crate::Result<()> { #[cfg(debug_assertions)] - { - if let Some(prev) = &self.last_key { - debug_assert!( - key > *prev, - "next key in ingestion must be greater than last key" - ); - } - self.last_key = Some(key.clone()); - } + self.validate_key(&key); self.writer .write_distinct(crate::InternalValue::from_components( key, value, - self.seqno, + 0, crate::ValueType::Value, )) } @@ -202,26 +141,19 @@ impl<'a> Ingestion<'a> { /// # Errors /// /// Will return `Err` if an IO error occurs. - pub fn write_tombstone(&mut self, key: UserKey) -> crate::Result<()> { - if let Some(prev) = &self.last_key { - assert!( - key > *prev, - "next key in ingestion must be greater than last key" - ); - } + pub fn write_tombstone>(&mut self, key: K) -> crate::Result<()> { + let key = key.into(); + + #[cfg(debug_assertions)] + self.validate_key(&key); self.writer .write_distinct(crate::InternalValue::from_components( - key.clone(), + key, crate::UserValue::empty(), - self.seqno, + 0, crate::ValueType::Tombstone, - ))?; - - // Remember the last user key to validate the next call's ordering - self.last_key = Some(key); - - Ok(()) + )) } /// Writes a weak tombstone for a key. @@ -229,48 +161,17 @@ impl<'a> Ingestion<'a> { /// # Errors /// /// Will return `Err` if an IO error occurs. - pub fn write_weak_tombstone(&mut self, key: UserKey) -> crate::Result<()> { - if let Some(prev) = &self.last_key { - assert!( - key > *prev, - "next key in ingestion must be greater than last key" - ); - } + pub fn write_weak_tombstone>(&mut self, key: K) -> crate::Result<()> { + let key = key.into(); - self.writer - .write_distinct(crate::InternalValue::from_components( - key.clone(), - crate::UserValue::empty(), - self.seqno, - crate::ValueType::WeakTombstone, - ))?; - - // Remember the last user key to validate the next call's ordering - self.last_key = Some(key); - - Ok(()) - } - - /// Writes a weak tombstone whose key the caller has already proven is - /// greater than every preceding key. Debug builds retain the assertion. - #[doc(hidden)] - pub fn write_prevalidated_weak_tombstone(&mut self, key: UserKey) -> crate::Result<()> { #[cfg(debug_assertions)] - { - if let Some(prev) = &self.last_key { - debug_assert!( - key > *prev, - "next key in ingestion must be greater than last key" - ); - } - self.last_key = Some(key.clone()); - } + self.validate_key(&key); self.writer .write_distinct(crate::InternalValue::from_components( key, crate::UserValue::empty(), - self.seqno, + 0, crate::ValueType::WeakTombstone, )) } @@ -282,6 +183,43 @@ impl<'a> Ingestion<'a> { /// Will return `Err` if an IO error occurs. #[allow(clippy::significant_drop_tightening)] pub fn finish(self) -> crate::Result<()> { + self.finish_inner(true) + } + + /// Finishes ingestion without checking or flushing memtables. + /// + /// The caller must ensure that no memtable writes can occur before or + /// during completion. + /// + /// # Errors + /// + /// Will return `Err` if an IO error occurs. + #[allow(clippy::significant_drop_tightening)] + pub fn finish_exclusive(self) -> crate::Result<()> { + #[cfg(debug_assertions)] + { + #[expect(clippy::expect_used, reason = "lock is expected to not be poisoned")] + let super_version = self + .tree + .version_history + .read() + .expect("lock is poisoned") + .latest_version(); + + debug_assert!( + super_version.active_memtable.is_empty() + && super_version + .sealed_memtables + .iter() + .all(|memtable| memtable.is_empty()), + "exclusive ingestion requires empty memtables", + ); + } + + self.finish_inner(false) + } + + fn finish_inner(self, flush_memtables: bool) -> crate::Result<()> { use crate::{AbstractTree, Table}; if self.writer.is_empty() { @@ -289,7 +227,9 @@ impl<'a> Ingestion<'a> { return Ok(()); } - // CRITICAL SECTION: Atomic flush + seqno allocation + registration + // General path critical section: atomic flush + seqno allocation + + // registration. The exclusive path skips this because its caller + // guarantees that memtable writes cannot occur. // // We must ensure no concurrent writes interfere between flushing the // active memtable and registering the ingested tables. The sequence is: @@ -306,16 +246,18 @@ impl<'a> Ingestion<'a> { // The seqno would be disconnected from the flush, violating MVCC. // // By holding the flush lock throughout, we guarantee atomicity. - let flush_lock = self.tree.get_flush_lock(); + let flush_lock = flush_memtables.then(|| self.tree.get_flush_lock()); - // Flush any pending memtable writes to ensure ingestion sees a - // consistent snapshot and lookup order remains correct. - // We call rotate + flush directly because we already hold the lock. - self.tree.rotate_memtable(); - self.tree.flush(&flush_lock, 0)?; + if let Some(flush_lock) = flush_lock.as_ref() { + // Flush any pending memtable writes to ensure ingestion sees a + // consistent snapshot and lookup order remains correct. + // We call rotate + flush directly because we already hold the lock. + self.tree.rotate_memtable(); + self.tree.flush(flush_lock, 0)?; + } // Finalize the ingestion writer, writing all buffered data to disk. - let results = self.writer.finish()?; + let (folder, results) = self.writer.finish()?; log::info!("Finished ingestion writer"); @@ -344,7 +286,7 @@ impl<'a> Ingestion<'a> { .into_iter() .map(|(table_id, checksum)| -> crate::Result
{ Table::recover( - self.folder.join(table_id.to_string()), + folder.join(table_id.to_string()), checksum, global_seqno, self.tree.id, @@ -352,8 +294,6 @@ impl<'a> Ingestion<'a> { self.tree.config.descriptor_table.clone(), false, false, - #[cfg(feature = "metrics")] - self.tree.metrics.clone(), ) }) .collect::>>()?; @@ -369,7 +309,7 @@ impl<'a> Ingestion<'a> { &self.tree.config.path, |current| { let mut copy = current.clone(); - copy.version = copy.version.with_new_l0_run(&created_tables, None, None); + copy.version = copy.version.with_new_l0_run(&created_tables); Ok(copy) }, global_seqno, @@ -385,3 +325,14 @@ impl<'a> Ingestion<'a> { Ok(()) } } + +impl Tree { + /// Starts a bulk ingestion into this tree. + /// + /// # Errors + /// + /// Will return `Err` if an IO error occurs. + pub fn ingestion(&self) -> crate::Result> { + Ingestion::new(self) + } +} diff --git a/crates/lsm-tree/src/tree/inner.rs b/crates/lsm-tree/src/tree/inner.rs index 85c8f9d0e..9acf8b3c3 100644 --- a/crates/lsm-tree/src/tree/inner.rs +++ b/crates/lsm-tree/src/tree/inner.rs @@ -7,17 +7,15 @@ use crate::{ compaction::state::CompactionState, config::Config, stop_signal::StopSignal, + table::next_table_id, version::{SuperVersions, Version, persist_version}, }; use std::sync::{Arc, Mutex, RwLock, atomic::AtomicU64}; -#[cfg(feature = "metrics")] -use crate::metrics::Metrics; - /// Unique tree ID /// /// Tree IDs are monotonically increasing integers. -pub type TreeId = u64; +pub type TreeId = u32; /// Unique memtable ID /// @@ -27,7 +25,10 @@ pub type MemtableId = u64; /// Hands out a unique (monotonically increasing) tree ID. pub fn get_next_tree_id() -> TreeId { static TREE_ID_COUNTER: AtomicU64 = AtomicU64::new(0); - TREE_ID_COUNTER.fetch_add(1, std::sync::atomic::Ordering::Relaxed) + TREE_ID_COUNTER + .fetch_add(1, std::sync::atomic::Ordering::Relaxed) + .try_into() + .expect("ran out of tree IDs") } pub struct TreeInner { @@ -42,10 +43,6 @@ pub struct TreeInner { #[doc(hidden)] pub table_id_counter: SequenceNumberCounter, - // This is not really used in the normal tree, but we need it in the blob tree - /// Hands out a unique (monotonically increasing) blob file ID - pub(crate) blob_file_id_counter: SequenceNumberCounter, - pub(crate) version_history: Arc>, pub(crate) compaction_state: Arc>, @@ -65,43 +62,28 @@ pub struct TreeInner { /// Serializes flush operations. pub(crate) flush_lock: Mutex<()>, - - #[doc(hidden)] - #[cfg(feature = "metrics")] - pub metrics: Arc, } impl TreeInner { pub(crate) fn create_new(config: Config) -> crate::Result { - let version = Version::new( - 0, - if config.kv_separation_opts.is_some() { - crate::TreeType::Blob - } else { - crate::TreeType::Standard - }, - ); + let version = Version::new(0); persist_version(&config.path, &version)?; Ok(Self { id: get_next_tree_id(), memtable_id_counter: SequenceNumberCounter::new(1), table_id_counter: SequenceNumberCounter::default(), - blob_file_id_counter: SequenceNumberCounter::default(), config: Arc::new(config), version_history: Arc::new(RwLock::new(SuperVersions::new(version))), stop_signal: StopSignal::default(), major_compaction_lock: RwLock::default(), flush_lock: Mutex::default(), compaction_state: Arc::new(Mutex::new(CompactionState::default())), - - #[cfg(feature = "metrics")] - metrics: Metrics::default().into(), }) } pub fn get_next_table_id(&self) -> TableId { - self.table_id_counter.next() + next_table_id(&self.table_id_counter) } } diff --git a/crates/lsm-tree/src/tree/mod.rs b/crates/lsm-tree/src/tree/mod.rs index c517131ee..217c77074 100644 --- a/crates/lsm-tree/src/tree/mod.rs +++ b/crates/lsm-tree/src/tree/mod.rs @@ -9,7 +9,7 @@ pub mod sealed; use crate::{ AbstractTree, Checksum, KvPair, SeqNo, SequenceNumberCounter, TableId, UserKey, UserValue, ValueType, - compaction::{CompactionStrategy, drop_range::OwnedBounds, state::CompactionState}, + compaction::{CompactionStrategy, state::CompactionState}, config::Config, file::CURRENT_VERSION_FILE, format_version::FormatVersion, @@ -20,19 +20,18 @@ use crate::{ slice::Slice, table::Table, value::InternalValue, - version::{SuperVersion, SuperVersions, Version, recovery::recover}, - vlog::BlobFile, + version::{ + SuperVersion, SuperVersions, Version, + recovery::{get_current_version, recover}, + }, }; use inner::{TreeId, TreeInner}; use std::{ - ops::{Bound, RangeBounds}, + ops::RangeBounds, path::Path, sync::{Arc, Mutex, RwLock}, }; -#[cfg(feature = "metrics")] -use crate::metrics::Metrics; - /// Iterator value guard pub struct Guard(crate::Result<(UserKey, UserValue)>); @@ -100,17 +99,17 @@ impl AbstractTree for Tree { } fn next_table_id(&self) -> TableId { - self.0.table_id_counter.get() + self.0 + .table_id_counter + .get() + .try_into() + .expect("ran out of table IDs") } fn id(&self) -> TreeId { self.id } - fn blob_file_count(&self) -> usize { - 0 - } - fn print_trace(&self, key: &[u8]) -> crate::Result<()> { #[expect(clippy::expect_used, reason = "lock is expected to not be poisoned")] let super_version = self @@ -179,11 +178,6 @@ impl AbstractTree for Tree { self.flush_lock.lock().expect("lock is poisoned") } - #[cfg(feature = "metrics")] - fn metrics(&self) -> &Arc { - &self.0.metrics - } - fn version_free_list_len(&self) -> usize { #[expect(clippy::expect_used, reason = "lock is expected to not be poisoned")] self.version_history @@ -198,10 +192,7 @@ impl AbstractTree for Tree { seqno: SeqNo, index: Option<(Arc, SeqNo)>, ) -> Box + Send + 'static> { - Box::new( - self.create_prefix(&prefix, seqno, index) - .map(|kv| IterGuardImpl::Standard(Guard(kv))), - ) + Box::new(self.create_prefix(&prefix, seqno, index).map(Guard)) } fn range, R: RangeBounds>( @@ -210,10 +201,7 @@ impl AbstractTree for Tree { seqno: SeqNo, index: Option<(Arc, SeqNo)>, ) -> Box + Send + 'static> { - Box::new( - self.create_range(&range, seqno, index) - .map(|kv| IterGuardImpl::Standard(Guard(kv))), - ) + Box::new(self.create_range(&range, seqno, index).map(Guard)) } /// Returns the number of tombstones in the tree. @@ -240,27 +228,6 @@ impl AbstractTree for Tree { .sum() } - fn drop_range, R: RangeBounds>(&self, range: R) -> crate::Result<()> { - let (bounds, is_empty) = Self::range_bounds_to_owned_bounds(&range); - - if is_empty { - return Ok(()); - } - - let strategy = Arc::new(crate::compaction::drop_range::Strategy::new(bounds)); - - // IMPORTANT: Write lock so we can be the only compaction going on - #[expect(clippy::expect_used, reason = "lock is expected to not be poisoned")] - let _lock = self - .0 - .major_compaction_lock - .write() - .expect("lock is poisoned"); - - log::info!("Starting drop_range compaction"); - self.inner_compact(strategy, 0) - } - fn clear(&self) -> crate::Result<()> { let config = self.tree_config(); let mut versions = self.get_version_history_lock(); @@ -271,7 +238,7 @@ impl AbstractTree for Tree { let mut copy = v.clone(); copy.active_memtable = Arc::new(Memtable::new(self.memtable_id_counter.next())); copy.sealed_memtables = Arc::default(); - copy.version = Version::new(v.version.id() + 1, self.tree_type()); + copy.version = Version::new(v.version.id() + 1); Ok(copy) }, &config.seqno, @@ -342,7 +309,7 @@ impl AbstractTree for Tree { fn flush_to_tables( &self, stream: impl Iterator>, - ) -> crate::Result, Option>)>> { + ) -> crate::Result>> { use crate::{file::TABLES_FOLDER, table::multi_writer::MultiWriter}; use std::time::Instant; @@ -368,27 +335,23 @@ impl AbstractTree for Tree { folder.display(), ); - let mut table_writer = MultiWriter::new( - folder.clone(), - self.table_id_counter.clone(), - 64 * 1_024 * 1_024, - 0, - )? - .use_data_block_restart_interval(data_block_restart_interval) - .use_index_block_restart_interval(index_block_restart_interval) - .use_data_block_compression(data_block_compression) - .use_index_block_compression(index_block_compression) - .use_data_block_size(data_block_size) - .use_data_block_hash_ratio(data_block_hash_ratio) - .use_bloom_policy({ - use crate::config::FilterPolicyEntry::{Bloom, None}; - use crate::table::filter::BloomConstructionPolicy; + let mut table_writer = + MultiWriter::new(folder, self.table_id_counter.clone(), 64 * 1_024 * 1_024, 0)? + .use_data_block_restart_interval(data_block_restart_interval) + .use_index_block_restart_interval(index_block_restart_interval) + .use_data_block_compression(data_block_compression) + .use_index_block_compression(index_block_compression) + .use_data_block_size(data_block_size) + .use_data_block_hash_ratio(data_block_hash_ratio) + .use_bloom_policy({ + use crate::config::FilterPolicyEntry::{Bloom, None}; + use crate::table::filter::BloomConstructionPolicy; - match self.config.filter_policy.get(0) { - Bloom(policy) => policy, - None => BloomConstructionPolicy::BitsPerKey(0.0), - } - }); + match self.config.filter_policy.get(0) { + Bloom(policy) => policy, + None => BloomConstructionPolicy::BitsPerKey(0.0), + } + }); if index_partitioning { table_writer = table_writer.use_partitioned_index(); @@ -401,7 +364,7 @@ impl AbstractTree for Tree { table_writer.write(item?)?; } - let result = table_writer.finish()?; + let (folder, result) = table_writer.finish()?; log::debug!("Flushed memtable(s) in {:?}", start.elapsed()); @@ -421,29 +384,21 @@ impl AbstractTree for Tree { self.config.descriptor_table.clone(), pin_filter, pin_index, - #[cfg(feature = "metrics")] - self.metrics.clone(), ) }) .collect::>>()?; - Ok(Some((tables, None))) + Ok(Some(tables)) } #[expect(clippy::significant_drop_tightening)] fn register_tables( &self, tables: &[Table], - blob_files: Option<&[BlobFile]>, - frag_map: Option, sealed_memtables_to_delete: &[crate::tree::inner::MemtableId], gc_watermark: SeqNo, ) -> crate::Result<()> { - log::trace!( - "Registering {} tables, {} blob files", - tables.len(), - blob_files.map(<[BlobFile]>::len).unwrap_or_default(), - ); + log::trace!("Registering {} tables", tables.len()); #[expect(clippy::expect_used, reason = "lock is expected to not be poisoned")] let mut _compaction_state = self.compaction_state.lock().expect("lock is poisoned"); @@ -467,11 +422,7 @@ impl AbstractTree for Tree { |current| { let mut copy = current.clone(); - copy.version = copy.version.with_new_l0_run( - tables, - blob_files, - frag_map.filter(|x| !x.is_empty()), - ); + copy.version = copy.version.with_new_l0_run(tables); for &table_id in sealed_memtables_to_delete { log::trace!("releasing sealed memtable #{table_id}"); @@ -789,51 +740,6 @@ impl Tree { None } - pub(crate) fn get_version_for_snapshot(&self, seqno: SeqNo) -> SuperVersion { - #[expect(clippy::expect_used, reason = "lock is expected to not be poisoned")] - self.version_history - .read() - .expect("lock is poisoned") - .get_version_for_snapshot(seqno) - } - - /// Normalizes a user-provided range into owned `Bound` values. - /// - /// Returns a tuple containing: - /// - the `OwnedBounds` that mirror the original bounds semantics (including - /// inclusive/exclusive markers and unbounded endpoints), and - /// - a `bool` flag indicating whether the normalized range is logically - /// empty (e.g., when the lower bound is greater than the upper bound). - /// - /// Callers can use the flag to detect empty ranges and skip further work - /// while still having access to the normalized bounds for non-empty cases. - fn range_bounds_to_owned_bounds, R: RangeBounds>( - range: &R, - ) -> (OwnedBounds, bool) { - use Bound::{Excluded, Included, Unbounded}; - - let start = match range.start_bound() { - Included(key) => Included(Slice::from(key.as_ref())), - Excluded(key) => Excluded(Slice::from(key.as_ref())), - Unbounded => Unbounded, - }; - - let end = match range.end_bound() { - Included(key) => Included(Slice::from(key.as_ref())), - Excluded(key) => Excluded(Slice::from(key.as_ref())), - Unbounded => Unbounded, - }; - - let is_empty = - if let (Included(lo) | Excluded(lo), Included(hi) | Excluded(hi)) = (&start, &end) { - lo.as_ref() > hi.as_ref() - } else { - false - }; - - (OwnedBounds { start, end }, is_empty) - } - /// Opens an LSM-tree in the given directory. /// /// Will recover previous state if the folder was previously @@ -954,7 +860,7 @@ impl Tree { .insert(value) } - /// Recovers previous state, by loading the level manifest, tables and blob files. + /// Recovers previous state by loading the level manifest and tables. /// /// # Errors /// @@ -967,63 +873,33 @@ impl Tree { let tree_id = get_next_tree_id(); - #[cfg(feature = "metrics")] - let metrics = Arc::new(Metrics::default()); - - let version = Self::recover_levels( - &config.path, - tree_id, - &config, - #[cfg(feature = "metrics")] - &metrics, - )?; - { - let manifest_path = config.path.join(format!("v{}", version.id())); + let current_version = get_current_version(&config.path)?; + let manifest_path = config.path.join(format!("v{current_version}")); let reader = sfa::Reader::new(&manifest_path)?; let manifest = Manifest::decode_from(&manifest_path, &reader)?; - if manifest.version != FormatVersion::V4 { - return Err(crate::Error::InvalidVersion(manifest.version.into())); - } - - let requested_tree_type = match config.kv_separation_opts { - Some(_) => crate::TreeType::Blob, - None => crate::TreeType::Standard, - }; - - if version.tree_type() != requested_tree_type { - log::error!( - "Tried to open a {requested_tree_type:?}Tree, but the existing tree is of type {:?}Tree. This indicates a misconfiguration or corruption.", - version.tree_type(), - ); - return Err(crate::Error::Unrecoverable); - } - // IMPORTANT: Restore persisted config config.level_count = manifest.level_count; } + let version = Self::recover_levels(&config.path, tree_id, &config)?; + let highest_table_id = version .iter_tables() .map(Table::id) .max() .unwrap_or_default(); - let inner = TreeInner { id: tree_id, memtable_id_counter: SequenceNumberCounter::new(1), - table_id_counter: SequenceNumberCounter::new(highest_table_id + 1), - blob_file_id_counter: SequenceNumberCounter::default(), + table_id_counter: SequenceNumberCounter::new(u64::from(highest_table_id) + 1), version_history: Arc::new(RwLock::new(SuperVersions::new(version))), stop_signal: StopSignal::default(), config: Arc::new(config), major_compaction_lock: RwLock::default(), flush_lock: Mutex::default(), compaction_state: Arc::new(Mutex::new(CompactionState::default())), - - #[cfg(feature = "metrics")] - metrics, }; Ok(Self(Arc::new(inner))) @@ -1055,7 +931,6 @@ impl Tree { tree_path: P, tree_id: TreeId, config: &Config, - #[cfg(feature = "metrics")] metrics: &Arc, ) -> crate::Result { use crate::{TableId, file::TABLES_FOLDER, file::fsync_directory}; @@ -1152,8 +1027,6 @@ impl Tree { config.descriptor_table.clone(), pin_filter, pin_index, - #[cfg(feature = "metrics")] - metrics.clone(), )?; tables.push(table); @@ -1176,14 +1049,7 @@ impl Tree { log::debug!("Successfully recovered {} tables", tables.len()); - let (blob_files, orphaned_blob_files) = crate::vlog::recover_blob_files( - &tree_path.join(crate::file::BLOBS_FOLDER), - &recovery.blob_file_ids, - tree_id, - config.descriptor_table.as_ref(), - )?; - - let version = Version::from_recovery(recovery, &tables, &blob_files)?; + let version = Version::from_recovery(recovery, &tables)?; // NOTE: Cleanup old versions // But only after we definitely recovered the latest version @@ -1194,11 +1060,6 @@ impl Tree { std::fs::remove_file(&table_path)?; } - for blob_file_path in orphaned_blob_files { - log::debug!("Deleting orphaned blob file {}", blob_file_path.display()); - std::fs::remove_file(&blob_file_path)?; - } - Ok(version) } diff --git a/crates/lsm-tree/src/util.rs b/crates/lsm-tree/src/util.rs index 444426286..f6931df5d 100644 --- a/crates/lsm-tree/src/util.rs +++ b/crates/lsm-tree/src/util.rs @@ -2,8 +2,8 @@ // This source code is licensed under both the Apache 2.0 and MIT License // (found in the LICENSE-* files in the repository) -use crate::range::prefix_upper_range; use crate::UserKey; +use crate::range::prefix_upper_range; use std::ops::RangeBounds; pub use crate::range::prefix_to_range; diff --git a/crates/lsm-tree/src/value.rs b/crates/lsm-tree/src/value.rs index e872160e4..c9b228eb6 100644 --- a/crates/lsm-tree/src/value.rs +++ b/crates/lsm-tree/src/value.rs @@ -2,7 +2,7 @@ // This source code is licensed under both the Apache 2.0 and MIT License // (found in the LICENSE-* files in the repository) -use crate::{key::InternalKey, Slice, ValueType}; +use crate::{Slice, ValueType, key::InternalKey}; /// User defined key pub type UserKey = Slice; diff --git a/crates/lsm-tree/src/value_type.rs b/crates/lsm-tree/src/value_type.rs index fa4774999..114264e1b 100644 --- a/crates/lsm-tree/src/value_type.rs +++ b/crates/lsm-tree/src/value_type.rs @@ -14,11 +14,6 @@ pub enum ValueType { /// "Weak" deletion (a.k.a. `SingleDelete` in `RocksDB`) WeakTombstone, - - /// Value pointer - /// - /// Points to a blob in a blob file. - Indirection = 4, } impl ValueType { @@ -27,10 +22,6 @@ impl ValueType { pub fn is_tombstone(self) -> bool { self == Self::Tombstone || self == Self::WeakTombstone } - - pub(crate) fn is_indirection(self) -> bool { - self == Self::Indirection - } } impl TryFrom for ValueType { @@ -41,7 +32,6 @@ impl TryFrom for ValueType { 0 => Ok(Self::Value), 1 => Ok(Self::Tombstone), 2 => Ok(Self::WeakTombstone), - 4 => Ok(Self::Indirection), _ => Err(()), } } @@ -53,7 +43,6 @@ impl From for u8 { ValueType::Value => 0, ValueType::Tombstone => 1, ValueType::WeakTombstone => 2, - ValueType::Indirection => 4, } } } diff --git a/crates/lsm-tree/src/version/blob_file_list.rs b/crates/lsm-tree/src/version/blob_file_list.rs deleted file mode 100644 index 1c595126a..000000000 --- a/crates/lsm-tree/src/version/blob_file_list.rs +++ /dev/null @@ -1,60 +0,0 @@ -use crate::{ - blob_tree::FragmentationMap, - vlog::{BlobFile, BlobFileId}, - HashMap, -}; - -// TODO: MSRV: [1.91] Replace with BTreeMap + extract_if -#[derive(Clone, Default)] -pub struct BlobFileList(HashMap); - -impl BlobFileList { - pub fn new(blob_files: HashMap) -> Self { - Self(blob_files) - } - - /// On-disk size - pub fn on_disk_size(&self) -> u64 { - self.iter().map(|bf| bf.0.meta.total_compressed_bytes).sum() - } - - pub fn len(&self) -> usize { - self.0.len() - } - - pub fn extend>(&mut self, iter: I) { - self.0.extend(iter); - } - - pub fn contains_key(&self, key: BlobFileId) -> bool { - self.0.contains_key(&key) - } - - pub fn prune_dead(&mut self, gc_stats: &FragmentationMap) -> Vec { - self.0 - .extract_if(|_, blob_file| blob_file.is_dead(gc_stats)) - .map(|(_, v)| v) - .collect() - } - - pub fn insert(&mut self, key: BlobFileId, value: BlobFile) { - self.0.insert(key, value); - } - - pub fn remove(&mut self, key: BlobFileId) -> Option { - self.0.remove(&key) - } - - /// Visits all blob files in no particular order. - pub fn iter(&self) -> impl Iterator { - self.0.values() - } - - pub fn list_ids(&self) -> impl Iterator { - self.0.keys() - } - - pub fn get(&self, key: BlobFileId) -> Option<&BlobFile> { - self.0.get(&key) - } -} diff --git a/crates/lsm-tree/src/version/mod.rs b/crates/lsm-tree/src/version/mod.rs index 2126ff1c5..2611942d1 100644 --- a/crates/lsm-tree/src/version/mod.rs +++ b/crates/lsm-tree/src/version/mod.rs @@ -2,28 +2,20 @@ // This source code is licensed under both the Apache 2.0 and MIT License // (found in the LICENSE-* files in the repository) -mod blob_file_list; mod optimize; mod persist; pub mod recovery; pub mod run; mod super_version; -pub use blob_file_list::BlobFileList; pub use persist::persist_version; pub use run::Run; pub use super_version::{SuperVersion, SuperVersions}; -use crate::blob_tree::{FragmentationEntry, FragmentationMap}; use crate::checksum::ChecksumType; -use crate::coding::Encode; use crate::compaction::state::hidden_set::HiddenSet; use crate::version::recovery::Recovery; -use crate::TreeType; -use crate::{ - vlog::{BlobFile, BlobFileId}, - HashSet, KeyRange, Table, TableId, -}; +use crate::{HashSet, KeyRange, Table, TableId}; use optimize::optimize_runs; use run::Ranged; use std::{ops::Deref, sync::Arc}; @@ -142,22 +134,8 @@ pub struct VersionInner { /// The version's ID id: VersionId, - tree_type: TreeType, - /// The individual LSM-tree levels which consist of runs of tables levels: Vec, - - // NOTE: We purposefully use Arc<_> to avoid deep cloning the blob files again and again - // - // Changing the value log tends to happen way less often than other modifications to the - // LSM-tree - // - /// Blob files for large values (value log) - #[doc(hidden)] - pub blob_files: Arc, - - /// Blob file fragmentation - gc_stats: Arc, } /// A version is an immutable, point-in-time view of a tree's structure @@ -178,20 +156,11 @@ impl std::ops::Deref for Version { // TODO: impl using generics so we can easily unit test Version transformation functions impl Version { - /// Returns the initial tree type. - pub fn tree_type(&self) -> TreeType { - self.tree_type - } - /// Returns the version ID. pub fn id(&self) -> VersionId { self.id } - pub fn gc_stats(&self) -> &FragmentationMap { - &self.gc_stats - } - pub fn l0(&self) -> &Level { #[expect(clippy::expect_used)] self.levels.first().expect("L0 should exist") @@ -208,25 +177,15 @@ impl Version { } /// Creates a new empty version. - pub fn new(id: VersionId, tree_type: TreeType) -> Self { + pub fn new(id: VersionId) -> Self { let levels = (0..DEFAULT_LEVEL_COUNT).map(|_| Level::empty()).collect(); Self { - inner: Arc::new(VersionInner { - id, - tree_type, - levels, - blob_files: Arc::default(), - gc_stats: Arc::default(), - }), + inner: Arc::new(VersionInner { id, levels }), } } - pub(crate) fn from_recovery( - recovery: Recovery, - tables: &[Table], - blob_files: &[BlobFile], - ) -> crate::Result { + pub(crate) fn from_recovery(recovery: Recovery, tables: &[Table]) -> crate::Result { let version_levels = recovery .table_ids .iter() @@ -259,31 +218,13 @@ impl Version { }) .collect::>>()?; - Ok(Self::from_levels( - recovery.curr_version_id, - recovery.tree_type, - version_levels, - BlobFileList::new(blob_files.iter().cloned().map(|bf| (bf.id(), bf)).collect()), - recovery.gc_stats, - )) + Ok(Self::from_levels(recovery.curr_version_id, version_levels)) } /// Creates a new pre-populated version. - pub fn from_levels( - id: VersionId, - tree_type: TreeType, - levels: Vec, - blob_files: BlobFileList, - gc_stats: FragmentationMap, - ) -> Self { + pub fn from_levels(id: VersionId, levels: Vec) -> Self { Self { - inner: Arc::new(VersionInner { - id, - tree_type, - levels, - blob_files: Arc::new(blob_files), - gc_stats: Arc::new(gc_stats), - }), + inner: Arc::new(VersionInner { id, levels }), } } @@ -302,10 +243,6 @@ impl Version { self.iter_levels().map(|x| x.table_count()).sum() } - pub fn blob_file_count(&self) -> usize { - self.blob_files.len() - } - /// Returns an iterator over all tables. pub fn iter_tables(&self) -> impl Iterator { self.levels @@ -324,12 +261,7 @@ impl Version { } /// Creates a new version with the additional run added to the "top" of L0. - pub fn with_new_l0_run( - &self, - run: &[Table], - blob_files: Option<&[BlobFile]>, - diff: Option, - ) -> Self { + pub fn with_new_l0_run(&self, run: &[Table]) -> Self { let id = self.id + 1; let mut levels = vec![]; @@ -366,49 +298,19 @@ impl Version { // L1+ levels.extend(self.levels.iter().skip(1).cloned()); - // Value log - let value_log = if let Some(blob_files) = blob_files { - let mut copy = self.blob_files.deref().clone(); - copy.extend(blob_files.iter().cloned().map(|bf| (bf.id(), bf))); - copy.into() - } else { - self.blob_files.clone() - }; - - let gc_stats = if let Some(diff) = diff { - let mut copy = self.gc_stats.deref().clone(); - diff.merge_into(&mut copy); - copy.prune(&value_log); - Arc::new(copy) - } else { - self.gc_stats.clone() - }; - Self { - inner: Arc::new(VersionInner { - id, - tree_type: self.tree_type, - levels, - blob_files: value_log, - gc_stats, - }), + inner: Arc::new(VersionInner { id, levels }), } } /// Returns a new version with a list of tables removed. /// /// The table files are not immediately deleted, this is handled by the version system's free list. - pub fn with_dropped( - &self, - ids: &[TableId], - dropped_blob_files: &mut Vec, - ) -> crate::Result { + pub fn with_dropped(&self, ids: &[TableId]) -> crate::Result { let id = self.id + 1; let mut levels = vec![]; - let mut dropped_tables: Vec
= vec![]; - for level in &self.levels { let runs = level .runs @@ -417,12 +319,7 @@ impl Version { // TODO: don't clone Arc inner if we don't need to modify let mut run: Run<_> = run.deref().clone(); - let removed_tables = run - .inner_mut() - .extract_if(.., |x| ids.contains(&x.metadata.id)); - - dropped_tables.extend(removed_tables); - + run.retain(|x| !ids.contains(&x.metadata.id)); run }) .filter(|x| !x.is_empty()) @@ -433,61 +330,12 @@ impl Version { levels.push(Level::from_runs(runs.into_iter().map(Arc::new).collect())); } - let gc_stats = if dropped_tables.is_empty() { - self.gc_stats.clone() - } else { - let mut copy = self.gc_stats.deref().clone(); - - for table in &dropped_tables { - let linked_blob_files = table.list_blob_file_references()?.unwrap_or_default(); - - for blob_file in linked_blob_files { - copy.entry(blob_file.blob_file_id) - .and_modify(|counter| { - counter.bytes += blob_file.bytes; - counter.len += blob_file.len; - }) - .or_insert_with(|| { - FragmentationEntry::new( - blob_file.len, - blob_file.bytes, - blob_file.on_disk_bytes, - ) - }); - } - } - - Arc::new(copy) - }; - - let value_log = if dropped_tables.is_empty() { - self.blob_files.clone() - } else { - let mut copy = self.blob_files.deref().clone(); - dropped_blob_files.extend(copy.prune_dead(&gc_stats)); - Arc::new(copy) - }; - Ok(Self { - inner: Arc::new(VersionInner { - id, - tree_type: self.tree_type, - levels, - blob_files: value_log, - gc_stats, - }), + inner: Arc::new(VersionInner { id, levels }), }) } - pub fn with_merge( - &self, - old_ids: &[TableId], - new_tables: &[Table], - dest_level: usize, - diff: Option, - new_blob_files: Vec, - blob_files_to_drop: &HashSet, - ) -> Self { + pub fn with_merge(&self, old_ids: &[TableId], new_tables: &[Table], dest_level: usize) -> Self { let id = self.id + 1; let mut levels = vec![]; @@ -516,47 +364,8 @@ impl Version { levels.push(Level::from_runs(runs.into_iter().map(Arc::new).collect())); } - let has_diff = diff.is_some(); - - let value_log = if has_diff || !new_blob_files.is_empty() || !blob_files_to_drop.is_empty() - { - let mut copy = self.blob_files.deref().clone(); - - for blob_file in new_blob_files { - copy.insert(blob_file.id(), blob_file); - } - - for &id in blob_files_to_drop { - copy.remove(id); - } - - Arc::new(copy) - } else { - self.blob_files.clone() - }; - - let gc_stats = if has_diff || !blob_files_to_drop.is_empty() { - let mut copy = self.gc_stats.deref().clone(); - - if let Some(diff) = diff { - diff.merge_into(&mut copy); - } - - copy.prune(&value_log); - - Arc::new(copy) - } else { - self.gc_stats.clone() - }; - Self { - inner: Arc::new(VersionInner { - id, - tree_type: self.tree_type, - levels, - blob_files: value_log, - gc_stats, - }), + inner: Arc::new(VersionInner { id, levels }), } } @@ -598,13 +407,7 @@ impl Version { } Self { - inner: Arc::new(VersionInner { - id, - tree_type: self.tree_type, - levels, - blob_files: self.blob_files.clone(), - gc_stats: self.gc_stats.clone(), - }), + inner: Arc::new(VersionInner { id, levels }), } } } @@ -623,13 +426,13 @@ impl Version { // writer.start("format_version")?; - writer.write_u8(FormatVersion::V4.into())?; + writer.write_u8(FormatVersion::V5.into())?; writer.start("crate_version")?; writer.write_all(env!("CARGO_PKG_VERSION").as_bytes())?; writer.start("tree_type")?; - writer.write_u8(self.tree_type.into())?; + writer.write_u8(0)?; writer.start("level_count")?; #[expect( @@ -672,7 +475,7 @@ impl Version { // Tables for table in run.iter() { - writer.write_u64::(table.id())?; + writer.write_u32::(table.id())?; writer.write_u8(0)?; // Checksum type, 0 = XXH3 writer.write_u128::(table.checksum().into_u128())?; writer.write_u64::(table.global_seqno())?; @@ -680,25 +483,6 @@ impl Version { } } - writer.start("blob_files")?; - - // Blob file count - #[expect( - clippy::cast_possible_truncation, - reason = "there are always less than 4 billion blob files" - )] - writer.write_u32::(self.blob_files.len() as u32)?; - - for file in self.blob_files.iter() { - writer.write_u64::(file.id())?; - writer.write_u8(0)?; // Checksum type, 0 = XXH3 - writer.write_u128::(file.0.checksum.into_u128())?; - } - - writer.start("blob_gc_stats")?; - - self.gc_stats.encode_into(writer)?; - Ok(()) } } diff --git a/crates/lsm-tree/src/version/persist.rs b/crates/lsm-tree/src/version/persist.rs index 299460eeb..60eda893c 100644 --- a/crates/lsm-tree/src/version/persist.rs +++ b/crates/lsm-tree/src/version/persist.rs @@ -1,6 +1,6 @@ use crate::{ checksum::ChecksummedWriter, - file::{fsync_directory, retry_transient_io, rewrite_atomic, CURRENT_VERSION_FILE}, + file::{CURRENT_VERSION_FILE, fsync_directory, retry_transient_io, rewrite_atomic}, version::Version, }; use byteorder::{LittleEndian, WriteBytesExt}; @@ -56,13 +56,12 @@ pub fn persist_version(folder: &Path, version: &Version) -> crate::Result<()> { #[cfg(test)] mod tests { use super::*; - use crate::TreeType; use test_log::test; #[test] fn version_persist_replaces_orphaned_file() -> crate::Result<()> { let dir = tempfile::tempdir()?; - let version = Version::new(0, TreeType::Standard); + let version = Version::new(0); // Simulates the leftover of a persist that failed midway std::fs::write(dir.path().join("v0"), b"partial")?; diff --git a/crates/lsm-tree/src/version/recovery.rs b/crates/lsm-tree/src/version/recovery.rs index 515d5c9be..f414a1094 100644 --- a/crates/lsm-tree/src/version/recovery.rs +++ b/crates/lsm-tree/src/version/recovery.rs @@ -2,10 +2,7 @@ // This source code is licensed under both the Apache 2.0 and MIT License // (found in the LICENSE-* files in the repository) -use crate::{ - coding::Decode, file::CURRENT_VERSION_FILE, version::VersionId, vlog::BlobFileId, Checksum, - SeqNo, TableId, TreeType, -}; +use crate::{Checksum, SeqNo, TableId, file::CURRENT_VERSION_FILE, version::VersionId}; use byteorder::{LittleEndian, ReadBytesExt}; use std::path::Path; @@ -24,11 +21,8 @@ pub struct RecoveredTable { } pub struct Recovery { - pub tree_type: TreeType, pub curr_version_id: VersionId, pub table_ids: Vec>>, - pub blob_file_ids: Vec<(BlobFileId, Checksum)>, - pub gc_stats: crate::blob_tree::FragmentationMap, } pub fn recover(folder: &Path) -> crate::Result { @@ -68,7 +62,7 @@ pub fn recover(folder: &Path) -> crate::Result { let table_count = reader.read_u32::()?; for _ in 0..table_count { - let id = reader.read_u64::()?; + let id = reader.read_u32::()?; let checksum_type = reader.read_u8()?; if checksum_type != 0 { @@ -94,67 +88,26 @@ pub fn recover(folder: &Path) -> crate::Result { } } - let blob_file_ids = { - let mut reader = toc - .section(b"blob_files") + let tree_type = { + let byte = toc + .section(b"tree_type") .ok_or(crate::Error::Unrecoverable) .inspect_err(|_| { - log::error!("blob_files section not found in version #{curr_version_id} - maybe the file is corrupted?"); - })? - .buf_reader(&version_file_path)?; - - let blob_file_count = reader.read_u32::()?; - let mut blob_file_ids = Vec::with_capacity(blob_file_count as usize); - - for _ in 0..blob_file_count { - let id = reader.read_u64::()?; - - let checksum_type = reader.read_u8()?; - - if checksum_type != 0 { - return Err(crate::Error::InvalidTag(("ChecksumType", checksum_type))); - } - - let checksum = reader.read_u128::()?; - let checksum = Checksum::from_raw(checksum); - - blob_file_ids.push((id, checksum)); - } - - blob_file_ids.sort_by_key(|(id, _)| *id); - blob_file_ids - }; - - debug_assert!(blob_file_ids.is_sorted_by_key(|(id, _)| id)); - - let gc_stats = { - let mut reader = toc - .section(b"blob_gc_stats") - .ok_or(crate::Error::Unrecoverable) - .inspect_err(|_| { - log::error!("blob_gc_stats section not found in version #{curr_version_id} - maybe the file is corrupted?"); - })? - .buf_reader(&version_file_path)?; - - crate::blob_tree::FragmentationMap::decode_from(&mut reader)? - }; - - Ok(Recovery { - tree_type: { - let byte = toc.section(b"tree_type").ok_or(crate::Error::Unrecoverable) - .inspect_err(|_|{ log::error!("tree_type section not found in version #{curr_version_id} - maybe the file is corrupted?"); })? - .buf_reader( - &version_file_path - )? + .buf_reader(&version_file_path)? .read_u8()?; - TreeType::try_from(byte).map_err(|()| crate::Error::InvalidHeader("TreeType"))? - }, + byte + }; + + if tree_type != 0 { + log::error!("Blob trees are not supported by this build"); + return Err(crate::Error::Unrecoverable); + } + + Ok(Recovery { curr_version_id, table_ids: levels, - blob_file_ids, - gc_stats, }) } diff --git a/crates/lsm-tree/src/version/run.rs b/crates/lsm-tree/src/version/run.rs index 686416ec9..fbf8ca651 100644 --- a/crates/lsm-tree/src/version/run.rs +++ b/crates/lsm-tree/src/version/run.rs @@ -174,11 +174,12 @@ impl Run { #[expect(clippy::unwrap_used)] mod tests { use super::*; + use crate::TableId; use test_log::test; #[derive(Clone)] struct FakeTable { - id: u64, + id: TableId, key_range: KeyRange, } @@ -188,7 +189,7 @@ mod tests { } } - fn s(id: u64, min: &str, max: &str) -> FakeTable { + fn s(id: TableId, min: &str, max: &str) -> FakeTable { FakeTable { id, key_range: KeyRange::new((min.as_bytes().into(), max.as_bytes().into())), @@ -283,15 +284,14 @@ mod tests { run.range_overlap_indexes(&(b"z" as &[u8]..=b"zzz")) ); assert_eq!(Some((3, 3)), run.range_overlap_indexes(&(b"z" as &[u8]..))); - assert!(run - .range_overlap_indexes(&(b"zzz" as &[u8]..=b"zzzzzzz")) - .is_none()); + assert!( + run.range_overlap_indexes(&(b"zzz" as &[u8]..=b"zzzzzzz")) + .is_none() + ); } #[test] fn run_range_contained() { - use crate::TableId; - let items = vec![ s(0, "a", "d"), s(1, "e", "j"), @@ -411,7 +411,7 @@ mod tests { ); assert_eq!( - &[] as &[u64], + &[] as &[TableId], &*run .get_overlapping(&KeyRange::new((b"zzz".into(), b"zzzz".into()))) .iter() diff --git a/crates/lsm-tree/src/version/super_version.rs b/crates/lsm-tree/src/version/super_version.rs index 2398a45ab..a76d219e9 100644 --- a/crates/lsm-tree/src/version/super_version.rs +++ b/crates/lsm-tree/src/version/super_version.rs @@ -3,10 +3,10 @@ // (found in the LICENSE-* files in the repository) use crate::{ + SeqNo, SequenceNumberCounter, memtable::Memtable, tree::sealed::SealedMemtables, - version::{persist_version, Version}, - SeqNo, SequenceNumberCounter, + version::{Version, persist_version}, }; use std::{collections::VecDeque, path::Path, sync::Arc}; @@ -216,19 +216,19 @@ mod tests { SuperVersion { active_memtable: Arc::new(Memtable::new(0)), sealed_memtables: Arc::default(), - version: Version::new(0, crate::TreeType::Standard), + version: Version::new(0), seqno: 0, }, SuperVersion { active_memtable: Arc::new(Memtable::new(0)), sealed_memtables: Arc::default(), - version: Version::new(0, crate::TreeType::Standard), + version: Version::new(0), seqno: 1, }, SuperVersion { active_memtable: Arc::new(Memtable::new(0)), sealed_memtables: Arc::default(), - version: Version::new(0, crate::TreeType::Standard), + version: Version::new(0), seqno: 2, }, ] @@ -249,19 +249,19 @@ mod tests { SuperVersion { active_memtable: Arc::new(Memtable::new(0)), sealed_memtables: Arc::default(), - version: Version::new(0, crate::TreeType::Standard), + version: Version::new(0), seqno: 0, }, SuperVersion { active_memtable: Arc::new(Memtable::new(0)), sealed_memtables: Arc::default(), - version: Version::new(0, crate::TreeType::Standard), + version: Version::new(0), seqno: 1, }, SuperVersion { active_memtable: Arc::new(Memtable::new(0)), sealed_memtables: Arc::default(), - version: Version::new(0, crate::TreeType::Standard), + version: Version::new(0), seqno: 2, }, ] @@ -282,25 +282,25 @@ mod tests { SuperVersion { active_memtable: Arc::new(Memtable::new(0)), sealed_memtables: Arc::default(), - version: Version::new(0, crate::TreeType::Standard), + version: Version::new(0), seqno: 0, }, SuperVersion { active_memtable: Arc::new(Memtable::new(0)), sealed_memtables: Arc::default(), - version: Version::new(0, crate::TreeType::Standard), + version: Version::new(0), seqno: 1, }, SuperVersion { active_memtable: Arc::new(Memtable::new(0)), sealed_memtables: Arc::default(), - version: Version::new(0, crate::TreeType::Standard), + version: Version::new(0), seqno: 2, }, SuperVersion { active_memtable: Arc::new(Memtable::new(0)), sealed_memtables: Arc::default(), - version: Version::new(0, crate::TreeType::Standard), + version: Version::new(0), seqno: 8, }, ] @@ -321,13 +321,13 @@ mod tests { SuperVersion { active_memtable: Arc::new(Memtable::new(0)), sealed_memtables: Arc::default(), - version: Version::new(0, crate::TreeType::Standard), + version: Version::new(0), seqno: 0, }, SuperVersion { active_memtable: Arc::new(Memtable::new(0)), sealed_memtables: Arc::default(), - version: Version::new(0, crate::TreeType::Standard), + version: Version::new(0), seqno: 8, }, ] @@ -348,13 +348,13 @@ mod tests { SuperVersion { active_memtable: Arc::new(Memtable::new(0)), sealed_memtables: Arc::default(), - version: Version::new(0, crate::TreeType::Standard), + version: Version::new(0), seqno: 0, }, SuperVersion { active_memtable: Arc::new(Memtable::new(0)), sealed_memtables: Arc::default(), - version: Version::new(0, crate::TreeType::Standard), + version: Version::new(0), seqno: 2, }, ] diff --git a/crates/lsm-tree/src/vlog/accessor.rs b/crates/lsm-tree/src/vlog/accessor.rs deleted file mode 100644 index f246ff7d7..000000000 --- a/crates/lsm-tree/src/vlog/accessor.rs +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright (c) 2024-present, fjall-rs -// This source code is licensed under both the Apache 2.0 and MIT License -// (found in the LICENSE-* files in the repository) - -use crate::{ - version::BlobFileList, - vlog::{blob_file::reader::Reader, ValueHandle}, - Cache, GlobalTableId, TreeId, UserValue, -}; -use std::{fs::File, path::Path, sync::Arc}; - -pub struct Accessor<'a>(&'a BlobFileList); - -impl<'a> Accessor<'a> { - pub fn new(blob_files: &'a BlobFileList) -> Self { - Self(blob_files) - } - - pub fn get( - &self, - tree_id: TreeId, - base_path: &Path, - key: &[u8], - vhandle: &ValueHandle, - cache: &Cache, - ) -> crate::Result> { - if let Some(value) = cache.get_blob(tree_id, vhandle) { - return Ok(Some(value)); - } - - let Some(blob_file) = self.0.get(vhandle.blob_file_id) else { - return Ok(None); - }; - - let bf_id = GlobalTableId::from((tree_id, blob_file.id())); - - let (file, fd_cache_miss) = - if let Some(cached_fd) = blob_file.file_accessor().access_for_blob_file(&bf_id) { - (cached_fd, false) - } else { - let file = Arc::new(File::open( - base_path.join(vhandle.blob_file_id.to_string()), - )?); - (file, true) - }; - - let value = Reader::new(blob_file, &file).get(key, vhandle)?; - cache.insert_blob(tree_id, vhandle, value.clone()); - - if fd_cache_miss { - blob_file.file_accessor().insert_for_blob_file(bf_id, file); - } - - Ok(Some(value)) - } -} diff --git a/crates/lsm-tree/src/vlog/blob_file/merge.rs b/crates/lsm-tree/src/vlog/blob_file/merge.rs deleted file mode 100644 index 9a87a420c..000000000 --- a/crates/lsm-tree/src/vlog/blob_file/merge.rs +++ /dev/null @@ -1,203 +0,0 @@ -// Copyright (c) 2024-present, fjall-rs -// This source code is licensed under both the Apache 2.0 and MIT License -// (found in the LICENSE-* files in the repository) - -use super::scanner::Scanner as BlobFileScanner; -use crate::vlog::{blob_file::scanner::ScanEntry, BlobFileId}; -use interval_heap::IntervalHeap; -use std::cmp::Reverse; - -type IteratorIndex = usize; - -#[derive(Debug)] -struct IteratorValue { - index: IteratorIndex, - scan_entry: ScanEntry, - blob_file_id: BlobFileId, -} - -impl PartialEq for IteratorValue { - fn eq(&self, other: &Self) -> bool { - self.scan_entry.key == other.scan_entry.key - } -} -impl Eq for IteratorValue {} - -impl PartialOrd for IteratorValue { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -impl Ord for IteratorValue { - fn cmp(&self, other: &Self) -> std::cmp::Ordering { - (&self.scan_entry.key, Reverse(&self.scan_entry.seqno)) - .cmp(&(&other.scan_entry.key, Reverse(&other.scan_entry.seqno))) - } -} - -/// Interleaves multiple blob file readers into a single, sorted stream -pub struct MergeScanner { - readers: Vec, - heap: IntervalHeap, -} - -impl MergeScanner { - /// Initializes a new merging reader - pub fn new(readers: Vec) -> Self { - let heap = IntervalHeap::with_capacity(readers.len()); - Self { readers, heap } - } - - fn advance_reader(&mut self, idx: usize) -> crate::Result<()> { - #[expect(clippy::indexing_slicing, reason = "we trust the caller")] - let reader = &mut self.readers[idx]; - - if let Some(value) = reader.next() { - let scan_entry = value?; - let blob_file_id = reader.blob_file_id; - - self.heap.push(IteratorValue { - index: idx, - blob_file_id, - scan_entry, - }); - } - - Ok(()) - } - - fn push_next(&mut self) -> crate::Result<()> { - for idx in 0..self.readers.len() { - self.advance_reader(idx)?; - } - - Ok(()) - } -} - -impl Iterator for MergeScanner { - type Item = crate::Result<(ScanEntry, BlobFileId)>; - - fn next(&mut self) -> Option { - if self.heap.is_empty() { - fail_iter!(self.push_next()); - } - - if let Some(head) = self.heap.pop_min() { - fail_iter!(self.advance_reader(head.index)); - return Some(Ok((head.scan_entry, head.blob_file_id))); - } - - None - } -} - -#[cfg(test)] -#[expect(clippy::unwrap_used)] -mod tests { - use super::super::scanner::Scanner; - use super::*; - use crate::{vlog::blob_file::writer::Writer as BlobFileWriter, Slice}; - use tempfile::tempdir; - use test_log::test; - - #[test] - fn blob_file_merger_seqno() -> crate::Result<()> { - let dir = tempdir()?; - - let blob_file_path = dir.path().join("0"); - { - { - let mut writer = BlobFileWriter::new(&blob_file_path, 0, 0)?; - - writer.write(b"a", 1, &b"1".repeat(100))?; - writer.write(b"a", 0, &b"0".repeat(100))?; - - writer.finish()?; - } - } - - { - let mut merger = MergeScanner::new(vec![Scanner::new(&blob_file_path, 0)?]); - - assert_eq!( - (Slice::from(b"a"), Slice::from(b"1".repeat(100))), - merger - .next() - .map(|result| result.map(|(entry, _)| (entry.key, entry.value))) - .unwrap()?, - ); - assert_eq!( - (Slice::from(b"a"), Slice::from(b"0".repeat(100))), - merger - .next() - .map(|result| result.map(|(entry, _)| (entry.key, entry.value))) - .unwrap()?, - ); - - assert!(merger.next().is_none()); - } - - Ok(()) - } - - #[test] - fn blob_file_merger() -> crate::Result<()> { - let dir = tempdir()?; - - let blob_file_0_path = dir.path().join("0"); - let blob_file_1_path = dir.path().join("1"); - - { - let keys = [b"a", b"c", b"e"]; - - { - let mut writer = BlobFileWriter::new(&blob_file_0_path, 0, 0)?; - - for key in keys { - writer.write(key, 0, &key.repeat(100))?; - } - - writer.finish()?; - } - } - - { - let keys = [b"b", b"d"]; - - { - let mut writer = BlobFileWriter::new(&blob_file_1_path, 1, 0)?; - - for key in keys { - writer.write(key, 1, &key.repeat(100))?; - } - - writer.finish()?; - } - } - - { - let mut merger = MergeScanner::new(vec![ - Scanner::new(&blob_file_0_path, 0)?, - Scanner::new(&blob_file_1_path, 1)?, - ]); - - let merged_keys = [b"a", b"b", b"c", b"d", b"e"]; - - for key in merged_keys { - assert_eq!( - (Slice::from(key), Slice::from(key.repeat(100))), - merger - .next() - .map(|result| result.map(|(entry, _)| (entry.key, entry.value))) - .unwrap()?, - ); - } - - assert!(merger.next().is_none()); - } - - Ok(()) - } -} diff --git a/crates/lsm-tree/src/vlog/blob_file/meta.rs b/crates/lsm-tree/src/vlog/blob_file/meta.rs deleted file mode 100644 index 821e77c37..000000000 --- a/crates/lsm-tree/src/vlog/blob_file/meta.rs +++ /dev/null @@ -1,186 +0,0 @@ -// Copyright (c) 2024-present, fjall-rs -// This source code is licensed under both the Apache 2.0 and MIT License -// (found in the LICENSE-* files in the repository) - -use crate::{ - checksum::ChecksumType, - coding::{Decode, Encode}, - table::{Block, DataBlock}, - vlog::BlobFileId, - CompressionType, InternalValue, KeyRange, SeqNo, Slice, -}; -use byteorder::{LittleEndian, ReadBytesExt}; -use std::io::{Read, Write}; - -macro_rules! read_u64 { - ($block:expr, $name:expr) => {{ - let bytes = $block - .point_read($name, SeqNo::MAX) - .unwrap_or_else(|| panic!("meta property {:?} should exist", $name)); - - let mut bytes = &bytes.value[..]; - bytes.read_u64::()? - }}; -} - -macro_rules! read_u128 { - ($block:expr, $name:expr) => {{ - let bytes = $block - .point_read($name, SeqNo::MAX) - .unwrap_or_else(|| panic!("meta property {:?} should exist", $name)); - - let mut bytes = &bytes.value[..]; - bytes.read_u128::()? - }}; -} - -pub const METADATA_HEADER_MAGIC: &[u8] = b"META"; - -#[derive(Debug, PartialEq, Eq)] -pub struct Metadata { - pub id: BlobFileId, - - pub created_at: u128, - - /// Number of KV-pairs in the blob file - pub item_count: u64, - - /// compressed size in bytes (on disk) (without metadata or trailer) - pub total_compressed_bytes: u64, - - /// true size in bytes (if no compression were used) - pub total_uncompressed_bytes: u64, - - /// Key range - pub key_range: KeyRange, - - /// Compression type used for all blobs in this file - pub compression: CompressionType, -} - -impl Metadata { - pub fn encode_into(&self, writer: &mut W) -> crate::Result<()> { - fn meta(key: &str, value: &[u8]) -> InternalValue { - InternalValue::from_components(key, value, 0, crate::ValueType::Value) - } - - // Write header - writer.write_all(METADATA_HEADER_MAGIC)?; - - #[rustfmt::skip] - let meta_items = [ - meta("blob_file_version", &[0x3]), - meta("checksum_type", &[u8::from(ChecksumType::Xxh3)]), - meta("compression", &self.compression.encode_into_vec()), - meta("crate_version", env!("CARGO_PKG_VERSION").as_bytes()), - meta("created_at", &self.created_at.to_le_bytes()), - meta("file_size", &self.total_compressed_bytes.to_le_bytes()), - meta("id", &self.id.to_le_bytes()), - meta("item_count", &self.item_count.to_le_bytes()), - meta("key#max", self.key_range.max()), - meta("key#min", self.key_range.min()), - meta("uncompressed_size", &self.total_uncompressed_bytes.to_le_bytes()), - ]; - - // NOTE: Just to make sure the items are definitely sorted - #[cfg(debug_assertions)] - { - let is_sorted = meta_items.iter().is_sorted_by_key(|kv| &kv.key); - assert!(is_sorted, "meta items not sorted correctly"); - } - - // TODO: no binary index - let buf = DataBlock::encode_into_vec(&meta_items, 1, 0.0)?; - - Block::write_into( - writer, - &buf, - crate::table::block::BlockType::Meta, - CompressionType::None, - )?; - - Ok(()) - } - - pub fn from_slice(slice: &Slice) -> crate::Result { - let reader = &mut &slice[..]; - - // Check header - let mut magic = [0u8; METADATA_HEADER_MAGIC.len()]; - reader.read_exact(&mut magic)?; - - if magic != METADATA_HEADER_MAGIC { - return Err(crate::Error::InvalidHeader("BlobFileMeta")); - } - - // TODO: Block::from_slice - let block = Block::from_reader(reader, CompressionType::None)?; - let block = DataBlock::new(block); - - let id = read_u64!(block, b"id"); - let created_at = read_u128!(block, b"created_at"); - let item_count = read_u64!(block, b"item_count"); - let file_size = read_u64!(block, b"file_size"); - let total_uncompressed_bytes = read_u64!(block, b"uncompressed_size"); - - let compression = { - #[expect(clippy::expect_used, reason = "compression is expected to exist")] - let bytes = block - .point_read(b"compression", SeqNo::MAX) - .expect("compression should exist"); - - let mut bytes = &bytes.value[..]; - CompressionType::decode_from(&mut bytes)? - }; - - let key_range = KeyRange::new(( - #[expect(clippy::expect_used, reason = "key min is expected to exist")] - block - .point_read(b"key#min", SeqNo::MAX) - .expect("key min should exist") - .value, - #[expect(clippy::expect_used, reason = "key max is expected to exist")] - block - .point_read(b"key#max", SeqNo::MAX) - .expect("key max should exist") - .value, - )); - - Ok(Self { - id, - created_at, - compression, - item_count, - total_compressed_bytes: file_size, - total_uncompressed_bytes, - key_range, - }) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use test_log::test; - - #[test] - #[expect(clippy::unwrap_used)] - fn test_blob_file_meta_roundtrip() { - let meta = Metadata { - id: 0, - created_at: 1_234_567_890, - compression: CompressionType::None, - item_count: 100, - total_compressed_bytes: 1024, - total_uncompressed_bytes: 2048, - key_range: KeyRange::new((b"a".into(), b"z".into())), - }; - - let mut buf = Vec::new(); - meta.encode_into(&mut buf).unwrap(); - let buf = Slice::from(buf); - - let meta2 = Metadata::from_slice(&buf).unwrap(); - assert_eq!(meta, meta2); - } -} diff --git a/crates/lsm-tree/src/vlog/blob_file/mod.rs b/crates/lsm-tree/src/vlog/blob_file/mod.rs deleted file mode 100644 index 138e5e142..000000000 --- a/crates/lsm-tree/src/vlog/blob_file/mod.rs +++ /dev/null @@ -1,156 +0,0 @@ -// Copyright (c) 2024-present, fjall-rs -// This source code is licensed under both the Apache 2.0 and MIT License -// (found in the LICENSE-* files in the repository) - -pub mod merge; -pub mod meta; -pub mod multi_writer; -pub mod reader; -pub mod scanner; -pub mod writer; - -use crate::{ - blob_tree::FragmentationMap, file_accessor::FileAccessor, vlog::BlobFileId, Checksum, - GlobalTableId, TreeId, -}; -pub use meta::Metadata; -use std::{ - path::{Path, PathBuf}, - sync::{atomic::AtomicBool, Arc}, -}; - -/// A blob file is an immutable, sorted, contiguous file that contains large key-value pairs (blobs) -#[derive(Debug)] -pub struct Inner { - /// Blob file ID - pub id: BlobFileId, - - pub tree_id: TreeId, - - /// File path - pub path: PathBuf, - - /// Statistics - pub meta: Metadata, - - /// Whether this blob file is deleted (logically) - pub is_deleted: AtomicBool, - - pub checksum: Checksum, - - pub(crate) file_accessor: FileAccessor, -} - -impl Inner { - fn global_id(&self) -> GlobalTableId { - GlobalTableId::from((self.tree_id, self.id)) - } -} - -impl Drop for Inner { - fn drop(&mut self) { - if self.is_deleted.load(std::sync::atomic::Ordering::Acquire) { - log::trace!( - "Cleanup deleted blob file {:?} at {}", - self.id, - self.path.display(), - ); - - if let Err(e) = std::fs::remove_file(&*self.path) { - log::warn!( - "Failed to cleanup deleted blob file {:?} at {}: {e:?}", - self.id, - self.path.display(), - ); - } - - self.file_accessor - .as_descriptor_table() - .inspect(|d| d.remove_for_blob_file(&self.global_id())); - } - } -} - -/// A blob file stores large values and is part of the value log -#[derive(Clone)] -pub struct BlobFile(pub(crate) Arc); - -impl Eq for BlobFile {} - -impl PartialEq for BlobFile { - fn eq(&self, other: &Self) -> bool { - self.id().eq(&other.id()) - } -} - -impl std::hash::Hash for BlobFile { - fn hash(&self, state: &mut H) { - self.id().hash(state); - } -} - -impl BlobFile { - pub(crate) fn mark_as_deleted(&self) { - self.0 - .is_deleted - .store(true, std::sync::atomic::Ordering::Release); - } - - /// Returns the blob file ID. - #[must_use] - pub fn id(&self) -> BlobFileId { - self.0.id - } - - /// Returns the full blob file checksum. - #[must_use] - pub fn checksum(&self) -> Checksum { - self.0.checksum - } - - /// Returns the blob file path. - #[must_use] - pub fn path(&self) -> &Path { - &self.0.path - } - - /// Returns the blob file accessor. - #[must_use] - pub(crate) fn file_accessor(&self) -> &FileAccessor { - &self.0.file_accessor - } - - /// Returns the number of items in the blob file. - #[must_use] - #[expect(clippy::len_without_is_empty)] - pub fn len(&self) -> u64 { - self.0.meta.item_count - } - - /// Returns `true` if the blob file is stale (based on the given staleness threshold). - pub(crate) fn is_stale(&self, frag_map: &FragmentationMap, threshold: f32) -> bool { - frag_map.get(&self.id()).is_some_and(|x| { - #[expect( - clippy::cast_precision_loss, - reason = "ok to lose precision as this is an approximate calculation" - )] - let stale_bytes = x.bytes as f32; - #[expect( - clippy::cast_precision_loss, - reason = "ok to lose precision as this is an approximate calculation" - )] - let all_bytes = self.0.meta.total_uncompressed_bytes as f32; - let ratio = stale_bytes / all_bytes; - ratio >= threshold - }) - } - - /// Returns `true` if the blob file has no more incoming references, and can be safely removed from a Version. - pub(crate) fn is_dead(&self, frag_map: &FragmentationMap) -> bool { - frag_map.get(&self.id()).is_some_and(|x| { - let stale_bytes = x.bytes; - let all_bytes = self.0.meta.total_uncompressed_bytes; - stale_bytes == all_bytes - }) - } -} diff --git a/crates/lsm-tree/src/vlog/blob_file/multi_writer.rs b/crates/lsm-tree/src/vlog/blob_file/multi_writer.rs deleted file mode 100644 index 040a7a382..000000000 --- a/crates/lsm-tree/src/vlog/blob_file/multi_writer.rs +++ /dev/null @@ -1,242 +0,0 @@ -// Copyright (c) 2024-present, fjall-rs -// This source code is licensed under both the Apache 2.0 and MIT License -// (found in the LICENSE-* files in the repository) - -use super::writer::Writer; -use crate::{ - file_accessor::FileAccessor, - vlog::{ - blob_file::{Inner as BlobFileInner, Metadata}, - ValueHandle, - }, - BlobFile, CompressionType, DescriptorTable, SeqNo, SequenceNumberCounter, TreeId, -}; -use std::{ - fs::File, - path::{Path, PathBuf}, - sync::{atomic::AtomicBool, Arc}, -}; - -/// Blob file writer, may write multiple blob files -pub struct MultiWriter { - folder: PathBuf, - target_size: u64, - - active_writer: Writer, - - results: Vec, - - id_generator: SequenceNumberCounter, - - compression: CompressionType, - passthrough_compression: CompressionType, - - tree_id: TreeId, - descriptor_table: Option>, -} - -impl MultiWriter { - /// Initializes a new blob file writer. - /// - /// # Errors - /// - /// Will return `Err` if an IO error occurs. - #[doc(hidden)] - pub fn new>( - id_generator: SequenceNumberCounter, - folder: P, - tree_id: TreeId, - descriptor_table: Option>, - ) -> crate::Result { - let folder = folder.as_ref(); - - let blob_file_id = id_generator.next(); - let blob_file_path = folder.join(blob_file_id.to_string()); - - Ok(Self { - id_generator, - folder: folder.into(), - target_size: 64 * 1_024 * 1_024, - - active_writer: Writer::new(blob_file_path, blob_file_id, tree_id)?, - - results: Vec::new(), - - compression: CompressionType::None, - passthrough_compression: CompressionType::None, - - tree_id, - descriptor_table, - }) - } - - /// Sets the blob file target size. - #[must_use] - pub fn use_target_size(mut self, bytes: u64) -> Self { - self.target_size = bytes; - self - } - - /// Sets the compression method in blob file writer metadata, but does not actually compress blobs. - /// - /// This is used in garbage collection to pass through already-compressed blobs, but correctly - /// set the compression type in the metadata. - pub(crate) fn use_passthrough_compression(mut self, compression: CompressionType) -> Self { - assert_eq!(self.compression, CompressionType::None); - self.passthrough_compression = compression; - self.active_writer.passthrough_compression = compression; - self - } - - /// Sets the compression method. - #[must_use] - #[doc(hidden)] - pub fn use_compression(mut self, compression: CompressionType) -> Self { - self.compression.clone_from(&compression); - self.active_writer.compression = compression; - self - } - - /// Sets up a new writer for the next blob file. - fn rotate(&mut self) -> crate::Result<()> { - log::debug!("Rotating blob file writer"); - - let new_blob_file_id = self.id_generator.next(); - let blob_file_path = self.folder.join(new_blob_file_id.to_string()); - - let new_writer = Writer::new(blob_file_path, new_blob_file_id, self.tree_id)? - .use_compression(self.compression) - .use_passthrough_compression(self.passthrough_compression); - - let old_writer = std::mem::replace(&mut self.active_writer, new_writer); - let blob_file = Self::consume_writer(old_writer, self.descriptor_table.clone())?; - self.results.extend(blob_file); - - Ok(()) - } - - fn consume_writer( - writer: Writer, - descriptor_table: Option>, - ) -> crate::Result> { - if writer.item_count > 0 { - let blob_file_id = writer.blob_file_id; - let path = writer.path.clone(); - - log::debug!( - "Created blob file #{blob_file_id:?} ({} items, {} userdata bytes)", - writer.item_count, - writer.uncompressed_bytes, - ); - - let tree_id = writer.tree_id; - - let (metadata, checksum) = writer.finish()?; - - let file = Arc::new(File::open(&path)?); - let file_accessor = descriptor_table.map_or(FileAccessor::File(file.clone()), |dt| { - FileAccessor::DescriptorTable(dt) - }); - file_accessor.insert_for_blob_file((tree_id, blob_file_id).into(), file); - - let blob_file = BlobFile(Arc::new(BlobFileInner { - checksum, - tree_id, - path, - is_deleted: AtomicBool::new(false), - id: blob_file_id, - file_accessor, - meta: Metadata { - id: blob_file_id, - created_at: crate::time::unix_timestamp().as_nanos(), - item_count: metadata.item_count, - total_compressed_bytes: metadata.total_compressed_bytes, - total_uncompressed_bytes: metadata.total_uncompressed_bytes, - key_range: metadata.key_range.clone(), - compression: metadata.compression, - }, - })); - - Ok(Some(blob_file)) - } else { - log::debug!( - "Blob file writer at {} has written no data, deleting empty blob file", - writer.path.display(), - ); - - if let Err(e) = std::fs::remove_file(&writer.path) { - log::warn!( - "Could not delete empty blob file at {}: {e:?}", - writer.path.display(), - ); - } - - Ok(None) - } - } - - /// Writes an item. - /// - /// Returns the [`ValueHandle`] of the written blob. - /// - /// # Errors - /// - /// Will return `Err` if an IO error occurs. - pub fn write(&mut self, key: &[u8], seqno: SeqNo, value: &[u8]) -> crate::Result { - let target_size = self.target_size; - - // Write actual value into blob file - let writer = &mut self.active_writer; - - let offset = writer.offset(); - let on_disk_value_len = writer.write(key, seqno, value)?; - - let handle = ValueHandle { - blob_file_id: writer.blob_file_id(), - offset, - on_disk_size: on_disk_value_len, - }; - - // Check for blob file size target, maybe rotate to next writer - if writer.offset() >= target_size { - self.rotate()?; - } - - Ok(handle) - } - - pub(crate) fn write_raw( - &mut self, - key: &[u8], - seqno: SeqNo, - value: &[u8], - uncompressed_len: u32, - ) -> crate::Result { - let target_size = self.target_size; - - // Write actual value into blob file - let writer = &mut self.active_writer; - - let offset = writer.offset(); - let on_disk_value_len = writer.write_raw(key, seqno, value, uncompressed_len)?; - - let handle = ValueHandle { - blob_file_id: writer.blob_file_id(), - offset, - on_disk_size: on_disk_value_len, - }; - - // Check for blob file size target, maybe rotate to next writer - if writer.offset() >= target_size { - self.rotate()?; - } - - Ok(handle) - } - - pub(crate) fn finish(mut self) -> crate::Result> { - let blob_file = Self::consume_writer(self.active_writer, self.descriptor_table.clone())?; - self.results.extend(blob_file); - Ok(self.results) - } -} diff --git a/crates/lsm-tree/src/vlog/blob_file/reader.rs b/crates/lsm-tree/src/vlog/blob_file/reader.rs deleted file mode 100644 index 281b8625b..000000000 --- a/crates/lsm-tree/src/vlog/blob_file/reader.rs +++ /dev/null @@ -1,161 +0,0 @@ -// Copyright (c) 2024-present, fjall-rs -// This source code is licensed under both the Apache 2.0 and MIT License -// (found in the LICENSE-* files in the repository) - -use crate::{ - vlog::{ - blob_file::writer::{BLOB_HEADER_LEN, BLOB_HEADER_MAGIC}, - ValueHandle, - }, - BlobFile, Checksum, CompressionType, UserValue, -}; -use byteorder::{LittleEndian, ReadBytesExt}; -use std::{ - fs::File, - io::{Cursor, Read}, -}; - -/// Reads a single blob from a blob file -pub struct Reader<'a> { - blob_file: &'a BlobFile, - file: &'a File, -} - -impl<'a> Reader<'a> { - pub fn new(blob_file: &'a BlobFile, file: &'a File) -> Self { - Self { blob_file, file } - } - - pub fn get(&self, key: &'a [u8], vhandle: &'a ValueHandle) -> crate::Result { - debug_assert_eq!(vhandle.blob_file_id, self.blob_file.id()); - - let add_size = (BLOB_HEADER_LEN as u64) + (key.len() as u64); - - let value = crate::file::read_exact( - self.file, - vhandle.offset, - (u64::from(vhandle.on_disk_size) + add_size) as usize, - )?; - - let mut reader = Cursor::new(&value[..]); - - let mut magic = [0u8; 4]; - reader.read_exact(&mut magic)?; - - if magic != BLOB_HEADER_MAGIC { - return Err(crate::Error::InvalidHeader("Blob")); - } - - let expected_checksum = reader.read_u128::()?; - - let _seqno = reader.read_u64::()?; - let key_len = reader.read_u16::()?; - - let real_val_len = reader.read_u32::()?; - - let _on_disk_val_len = reader.read_u32::()? as usize; - - let key = crate::UserKey::from_reader(&mut reader, key_len.into())?; - - let raw_data = value.slice((add_size as usize)..); - - { - let checksum = { - let mut hasher = xxhash_rust::xxh3::Xxh3::default(); - hasher.update(&key); - hasher.update(&raw_data); - hasher.digest128() - }; - - if expected_checksum != checksum { - log::error!( - "Checksum mismatch for blob {vhandle:?}, got={checksum}, expected={expected_checksum}", - ); - - return Err(crate::Error::ChecksumMismatch { - got: Checksum::from_raw(checksum), - expected: Checksum::from_raw(expected_checksum), - }); - } - } - - #[warn(clippy::match_single_binding)] - let value = match &self.blob_file.0.meta.compression { - CompressionType::None => raw_data, - - #[cfg(feature = "lz4")] - CompressionType::Lz4 => { - #[warn(unsafe_code)] - let mut builder = unsafe { UserValue::builder_unzeroed(real_val_len as usize) }; - - lz4_flex::decompress_into(&raw_data, &mut builder) - .map_err(|_| crate::Error::Decompress(self.blob_file.0.meta.compression))?; - - builder.freeze().into() - } - }; - - debug_assert_eq!(real_val_len, { - #[expect(clippy::cast_possible_truncation, reason = "values are u32 length max")] - { - value.len() as u32 - } - }); - - Ok(value) - } -} - -#[cfg(test)] -#[expect(clippy::unwrap_used)] -mod tests { - use super::*; - use crate::SequenceNumberCounter; - use test_log::test; - - #[test] - fn blob_reader_roundtrip() -> crate::Result<()> { - let id_generator = SequenceNumberCounter::default(); - - let folder = tempfile::tempdir()?; - let mut writer = crate::vlog::BlobFileWriter::new(id_generator, folder.path(), 0, None)? - .use_target_size(u64::MAX); - - let handle = writer.write(b"a", 0, b"abcdef")?; - - let blob_file = writer.finish()?; - let blob_file = blob_file.first().unwrap(); - - let file = File::open(&blob_file.0.path)?; - let reader = Reader::new(blob_file, &file); - - assert_eq!(reader.get(b"a", &handle)?, b"abcdef"); - - Ok(()) - } - - #[test] - #[cfg(feature = "lz4")] - fn blob_reader_roundtrip_lz4() -> crate::Result<()> { - let id_generator = SequenceNumberCounter::default(); - - let folder = tempfile::tempdir()?; - let mut writer = crate::vlog::BlobFileWriter::new(id_generator, folder.path(), 0, None)? - .use_target_size(u64::MAX) - .use_compression(CompressionType::Lz4); - - let handle0 = writer.write(b"a", 0, b"abcdef")?; - let handle1 = writer.write(b"b", 0, b"ghi")?; - - let blob_file = writer.finish()?; - let blob_file = blob_file.first().unwrap(); - - let file = File::open(&blob_file.0.path)?; - let reader = Reader::new(blob_file, &file); - - assert_eq!(reader.get(b"a", &handle0)?, b"abcdef"); - assert_eq!(reader.get(b"b", &handle1)?, b"ghi"); - - Ok(()) - } -} diff --git a/crates/lsm-tree/src/vlog/blob_file/scanner.rs b/crates/lsm-tree/src/vlog/blob_file/scanner.rs deleted file mode 100644 index a4deb5def..000000000 --- a/crates/lsm-tree/src/vlog/blob_file/scanner.rs +++ /dev/null @@ -1,169 +0,0 @@ -// Copyright (c) 2024-present, fjall-rs -// This source code is licensed under both the Apache 2.0 and MIT License -// (found in the LICENSE-* files in the repository) - -use super::writer::BLOB_HEADER_MAGIC; -use crate::{ - vlog::{blob_file::meta::METADATA_HEADER_MAGIC, BlobFileId}, - Checksum, SeqNo, UserKey, UserValue, -}; -use byteorder::{LittleEndian, ReadBytesExt}; -use std::{ - fs::File, - io::{BufReader, Read, Seek}, - path::Path, -}; - -/// Reads through a blob file in order -pub struct Scanner { - pub(crate) blob_file_id: BlobFileId, // TODO: remove unused? - inner: BufReader, - is_terminated: bool, -} - -impl Scanner { - /// Initializes a new blob file reader. - /// - /// # Errors - /// - /// Will return `Err` if an IO error occurs. - pub fn new>(path: P, blob_file_id: BlobFileId) -> crate::Result { - let file_reader = BufReader::with_capacity(32_000, File::open(path)?); - Ok(Self::with_reader(blob_file_id, file_reader)) - } - - /// Initializes a new blob file reader. - #[must_use] - pub fn with_reader(blob_file_id: BlobFileId, file_reader: BufReader) -> Self { - Self { - blob_file_id, - inner: file_reader, - is_terminated: false, - } - } -} - -#[derive(Debug, PartialEq, Eq)] -pub struct ScanEntry { - pub key: UserKey, - pub seqno: SeqNo, - pub value: UserValue, - pub offset: u64, - pub uncompressed_len: u32, -} - -impl Iterator for Scanner { - type Item = crate::Result; - - fn next(&mut self) -> Option { - if self.is_terminated { - return None; - } - - let offset = fail_iter!(self.inner.stream_position()); - - { - let mut buf = [0; BLOB_HEADER_MAGIC.len()]; - fail_iter!(self.inner.read_exact(&mut buf)); - - if buf == METADATA_HEADER_MAGIC { - self.is_terminated = true; - return None; - } - - if buf != BLOB_HEADER_MAGIC { - return Some(Err(crate::Error::InvalidHeader("Blob"))); - } - } - - let expected_checksum = fail_iter!(self.inner.read_u128::()); - let seqno = fail_iter!(self.inner.read_u64::()); - - let key_len = fail_iter!(self.inner.read_u16::()); - - let real_val_len = fail_iter!(self.inner.read_u32::()); - - let on_disk_val_len = fail_iter!(self.inner.read_u32::()); - - let key = fail_iter!(UserKey::from_reader(&mut self.inner, key_len as usize)); - - let value = fail_iter!(UserValue::from_reader( - &mut self.inner, - on_disk_val_len as usize - )); - - { - let checksum = { - let mut hasher = xxhash_rust::xxh3::Xxh3::default(); - hasher.update(&key); - hasher.update(&value); - hasher.digest128() - }; - - if expected_checksum != checksum { - log::error!( - "Checksum mismatch for blob>{}@{offset}, got={checksum}, expected={expected_checksum}", - self.blob_file_id, - ); - - return Some(Err(crate::Error::ChecksumMismatch { - got: Checksum::from_raw(checksum), - expected: Checksum::from_raw(expected_checksum), - })); - } - } - - Some(Ok(ScanEntry { - key, - seqno, - value, - offset, - uncompressed_len: real_val_len, - })) - } -} - -#[cfg(test)] -#[expect(clippy::unwrap_used)] -mod tests { - use super::*; - use crate::{vlog::blob_file::writer::Writer as BlobFileWriter, Slice}; - use tempfile::tempdir; - use test_log::test; - - #[test] - fn blob_scanner() -> crate::Result<()> { - let dir = tempdir()?; - let blob_file_path = dir.path().join("0"); - - let keys = [b"a", b"b", b"c", b"d", b"e"]; - - { - let mut writer = BlobFileWriter::new(&blob_file_path, 0, 0)?; - - for key in keys { - writer.write(key, 0, &key.repeat(100))?; - } - - writer.finish()?; - } - - { - let mut scanner = Scanner::new(&blob_file_path, 0)?; - - for key in keys { - assert_eq!( - (Slice::from(key), Slice::from(key.repeat(100))), - scanner - .next() - .map(|result| result.map(|entry| { (entry.key, entry.value) })) - .unwrap()?, - ); - } - - assert!(scanner.next().is_none()); - } - - Ok(()) - } -} diff --git a/crates/lsm-tree/src/vlog/blob_file/writer.rs b/crates/lsm-tree/src/vlog/blob_file/writer.rs deleted file mode 100644 index 77298c451..000000000 --- a/crates/lsm-tree/src/vlog/blob_file/writer.rs +++ /dev/null @@ -1,247 +0,0 @@ -// Copyright (c) 2024-present, fjall-rs -// This source code is licensed under both the Apache 2.0 and MIT License -// (found in the LICENSE-* files in the repository) - -use super::meta::Metadata; -use crate::{ - checksum::ChecksummedWriter, time::unix_timestamp, vlog::BlobFileId, Checksum, CompressionType, - KeyRange, SeqNo, TreeId, UserKey, -}; -use byteorder::{LittleEndian, WriteBytesExt}; -use std::{ - fs::File, - io::{BufWriter, Write}, - path::{Path, PathBuf}, -}; - -pub const BLOB_HEADER_MAGIC: &[u8] = b"BLOB"; - -pub const BLOB_HEADER_LEN: usize = BLOB_HEADER_MAGIC.len() - + std::mem::size_of::() // Checksum - + std::mem::size_of::() // SeqNo - + std::mem::size_of::() // Key length - + std::mem::size_of::() // Real value length - + std::mem::size_of::(); // On-disk value length - -/// Blob file writer -pub struct Writer { - pub(crate) tree_id: TreeId, - pub path: PathBuf, - pub(crate) blob_file_id: BlobFileId, - - #[expect(clippy::struct_field_names)] - writer: sfa::Writer>>, - - offset: u64, - - pub(crate) item_count: u64, - pub(crate) written_blob_bytes: u64, - pub(crate) uncompressed_bytes: u64, - - pub(crate) first_key: Option, - pub(crate) last_key: Option, - - pub(crate) compression: CompressionType, - pub(crate) passthrough_compression: CompressionType, -} - -impl Writer { - /// Initializes a new blob file writer. - /// - /// # Errors - /// - /// Will return `Err` if an IO error occurs. - #[doc(hidden)] - pub fn new>( - path: P, - blob_file_id: BlobFileId, - tree_id: TreeId, - ) -> crate::Result { - let path = path.as_ref(); - - let writer = BufWriter::new(File::create(path)?); - let writer = ChecksummedWriter::new(writer); - let mut writer = sfa::Writer::from_writer(writer); - writer.start("data")?; - - Ok(Self { - tree_id, - path: path.into(), - blob_file_id, - - writer, - - offset: 0, - item_count: 0, - written_blob_bytes: 0, - uncompressed_bytes: 0, - - first_key: None, - last_key: None, - - compression: CompressionType::None, - passthrough_compression: CompressionType::None, - }) - } - - pub fn use_compression(mut self, compressor: CompressionType) -> Self { - self.compression = compressor; - self - } - - pub(crate) fn use_passthrough_compression(mut self, compressor: CompressionType) -> Self { - self.passthrough_compression = compressor; - self - } - - /// Returns the current offset in the file. - #[must_use] - pub(crate) fn offset(&self) -> u64 { - self.offset - } - - /// Returns the blob file ID. - #[must_use] - pub(crate) fn blob_file_id(&self) -> BlobFileId { - self.blob_file_id - } - - pub(crate) fn write_raw( - &mut self, - key: &[u8], - seqno: SeqNo, - value: &[u8], - uncompressed_len: u32, - ) -> crate::Result { - assert!(!key.is_empty()); - assert!(u16::try_from(key.len()).is_ok()); - assert!(u32::try_from(value.len()).is_ok()); - - if self.first_key.is_none() { - self.first_key = Some(key.into()); - } - self.last_key = Some(key.into()); - - self.uncompressed_bytes += u64::from(uncompressed_len); - - // NOTE: - // BLOB HEADER LAYOUT - // - // [MAGIC_BYTES; 4B] - // [Checksum; 16B] - // [Seqno; 8B] - // [key len; 2B] - // [real val len; 4B] - // [on-disk val len; 4B] - // [...key; ?] - // [...val; ?] - - // Write header - self.writer.write_all(BLOB_HEADER_MAGIC)?; - - let value = match &self.compression { - CompressionType::None => std::borrow::Cow::Borrowed(value), - - #[cfg(feature = "lz4")] - CompressionType::Lz4 => std::borrow::Cow::Owned(lz4_flex::compress(value)), - }; - - let checksum = { - let mut hasher = xxhash_rust::xxh3::Xxh3::default(); - hasher.update(key); - hasher.update(&value); - hasher.digest128() - }; - - // Write checksum - self.writer.write_u128::(checksum)?; - - // Write seqno - self.writer.write_u64::(seqno)?; - - #[expect(clippy::cast_possible_truncation, reason = "keys are u16 length max")] - self.writer.write_u16::(key.len() as u16)?; - - // Write uncompressed value length - self.writer.write_u32::(uncompressed_len)?; - - // Write compressed (on-disk) value length - #[expect(clippy::cast_possible_truncation, reason = "values are u32 length max")] - self.writer.write_u32::(value.len() as u32)?; - - self.writer.write_all(key)?; - self.writer.write_all(&value)?; - - // Update offset - self.offset += BLOB_HEADER_MAGIC.len() as u64; - self.offset += std::mem::size_of::() as u64; - self.offset += std::mem::size_of::() as u64; - - self.offset += std::mem::size_of::() as u64; - self.offset += std::mem::size_of::() as u64; - self.offset += std::mem::size_of::() as u64; - - self.offset += key.len() as u64; - self.offset += value.len() as u64; - - // Update metadata - self.written_blob_bytes += value.len() as u64; - self.item_count += 1; - - // TODO: if we store the offset before writing, we can return a vhandle here - // instead of needing to call offset() and blob_file_id() before write() - - #[expect(clippy::cast_possible_truncation, reason = "values are u32 length max")] - Ok(value.len() as u32) - } - - /// Writes an item into the file. - /// - /// Items need to be written in key order. - /// - /// # Errors - /// - /// Will return `Err` if an IO error occurs. - /// - /// # Panics - /// - /// Panics if the key length is empty or greater than 2^16, or the value length is greater than 2^32. - pub fn write(&mut self, key: &[u8], seqno: SeqNo, value: &[u8]) -> crate::Result { - #[expect(clippy::cast_possible_truncation, reason = "values are u32 max")] - self.write_raw(key, seqno, value, value.len() as u32) - } - - pub(crate) fn finish(mut self) -> crate::Result<(Metadata, Checksum)> { - self.writer.start("meta")?; - - // Write metadata - let metadata = Metadata { - id: self.blob_file_id, - created_at: unix_timestamp().as_nanos(), - item_count: self.item_count, - total_compressed_bytes: self.written_blob_bytes, - total_uncompressed_bytes: self.uncompressed_bytes, - #[expect(clippy::expect_used, reason = "should have written at least 1 item")] - key_range: KeyRange::new(( - self.first_key - .clone() - .expect("should have written at least 1 item"), - self.last_key - .clone() - .expect("should have written at least 1 item"), - )), - compression: if self.passthrough_compression == CompressionType::None { - self.compression - } else { - self.passthrough_compression - }, - }; - metadata.encode_into(&mut self.writer)?; - - let mut checksum = self.writer.into_inner()?; - checksum.inner_mut().get_mut().sync_all()?; - let checksum = checksum.checksum(); - - Ok((metadata, checksum)) - } -} diff --git a/crates/lsm-tree/src/vlog/handle.rs b/crates/lsm-tree/src/vlog/handle.rs deleted file mode 100644 index d0888147b..000000000 --- a/crates/lsm-tree/src/vlog/handle.rs +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright (c) 2024-present, fjall-rs -// This source code is licensed under both the Apache 2.0 and MIT License -// (found in the LICENSE-* files in the repository) - -use crate::{ - coding::{Decode, Encode}, - vlog::BlobFileId, -}; -use std::{ - hash::Hash, - io::{Read, Write}, -}; -use varint_rs::{VarintReader, VarintWriter}; - -/// A value handle points into the value log -#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)] -pub struct ValueHandle { - /// Blob file ID - pub blob_file_id: BlobFileId, - - /// Offset in file - pub offset: u64, - - /// On-disk size - pub on_disk_size: u32, -} - -impl Encode for ValueHandle { - fn encode_into(&self, writer: &mut W) -> Result<(), crate::Error> { - writer.write_u64_varint(self.offset)?; - writer.write_u64_varint(self.blob_file_id)?; - writer.write_u32_varint(self.on_disk_size)?; - Ok(()) - } -} - -impl Decode for ValueHandle { - fn decode_from(reader: &mut R) -> Result { - let offset = reader.read_u64_varint()?; - let blob_file_id = reader.read_u64_varint()?; - let on_disk_size = reader.read_u32_varint()?; - - Ok(Self { - blob_file_id, - offset, - on_disk_size, - }) - } -} diff --git a/crates/lsm-tree/src/vlog/mod.rs b/crates/lsm-tree/src/vlog/mod.rs deleted file mode 100644 index 5f59a0a7d..000000000 --- a/crates/lsm-tree/src/vlog/mod.rs +++ /dev/null @@ -1,150 +0,0 @@ -// Copyright (c) 2024-present, fjall-rs -// This source code is licensed under both the Apache 2.0 and MIT License -// (found in the LICENSE-* files in the repository) - -mod accessor; -pub mod blob_file; -mod handle; - -pub use { - accessor::Accessor, blob_file::merge::MergeScanner as BlobFileMergeScanner, - blob_file::multi_writer::MultiWriter as BlobFileWriter, - blob_file::scanner::Scanner as BlobFileScanner, blob_file::BlobFile, handle::ValueHandle, -}; - -use crate::{ - file_accessor::FileAccessor, - vlog::blob_file::{Inner as BlobFileInner, Metadata}, - Checksum, DescriptorTable, TreeId, -}; -use std::{ - path::{Path, PathBuf}, - sync::{atomic::AtomicBool, Arc}, -}; - -pub fn recover_blob_files( - folder: &Path, - ids: &[(BlobFileId, Checksum)], - tree_id: TreeId, - descriptor_table: Option<&Arc>, -) -> crate::Result<(Vec, Vec)> { - if !folder.try_exists()? { - return Ok((vec![], vec![])); - } - - let cnt = ids.len(); - - let progress_mod = match cnt { - _ if cnt <= 20 => 1, - _ if cnt <= 100 => 10, - _ => 100, - }; - - log::debug!("Recovering {cnt} blob files from {:?}", folder.display()); - - let mut blob_files = Vec::with_capacity(ids.len()); - let mut orphaned_blob_files = vec![]; - - for (idx, dirent) in std::fs::read_dir(folder)?.enumerate() { - let dirent = dirent?; - let file_name = dirent.file_name(); - - // https://en.wikipedia.org/wiki/.DS_Store - if file_name == ".DS_Store" { - continue; - } - - // https://en.wikipedia.org/wiki/AppleSingle_and_AppleDouble_formats - if file_name.to_string_lossy().starts_with("._") { - continue; - } - - let blob_file_name = file_name.to_str().ok_or_else(|| { - log::error!("invalid table file name {}", file_name.display()); - crate::Error::Unrecoverable - })?; - - let blob_file_id = blob_file_name.parse::().map_err(|e| { - log::error!("invalid table file name {blob_file_name:?}: {e:?}"); - crate::Error::Unrecoverable - })?; - - let blob_file_path = dirent.path(); - assert!(!blob_file_path.is_dir()); - - if let Some(&(_, checksum)) = ids.iter().find(|(id, _)| id == &blob_file_id) { - log::trace!( - "Recovering blob file #{blob_file_id:?} from {}", - blob_file_path.display(), - ); - - let file = std::fs::File::open(&blob_file_path)?; - - let meta = { - let reader = sfa::Reader::new(&blob_file_path)?; - let toc = reader.toc(); - - let metadata_section = toc.section(b"meta") - .ok_or(crate::Error::Unrecoverable) - .inspect_err(|_| { - log::error!("meta section in blob file #{blob_file_id} is missing - maybe the file is corrupted?"); - })?; - - let metadata_slice = crate::file::read_exact( - &file, - metadata_section.pos(), - metadata_section.len() as usize, - )?; - - Metadata::from_slice(&metadata_slice)? - }; - - let file_accessor = if let Some(dt) = descriptor_table.cloned() { - FileAccessor::DescriptorTable(dt) - } else { - FileAccessor::File(Arc::new(file)) - }; - - blob_files.push(BlobFile(Arc::new(BlobFileInner { - id: blob_file_id, - path: blob_file_path, - meta, - is_deleted: AtomicBool::new(false), - checksum, - file_accessor, - tree_id, - }))); - - if idx % progress_mod == 0 { - log::debug!("Recovered {idx}/{cnt} blob files"); - } - } else { - orphaned_blob_files.push(blob_file_path.clone()); - } - } - - if blob_files.len() < ids.len() { - return Err(crate::Error::Unrecoverable); - } - - log::debug!("Successfully recovered {} blob files", blob_files.len()); - - Ok((blob_files, orphaned_blob_files)) -} - -/// The unique identifier for a value log blob file. -pub type BlobFileId = u64; - -#[cfg(test)] -mod tests { - use super::*; - use test_log::test; - - #[test] - fn vlog_recovery_missing_blob_file() { - assert!(matches!( - recover_blob_files(Path::new("."), &[(0, Checksum::from_raw(0))], 0, None), - Err(crate::Error::Unrecoverable), - )); - } -} diff --git a/crates/lsm-tree/test_fixture/v1_tree/config b/crates/lsm-tree/test_fixture/v1_tree/config deleted file mode 100644 index 61e5bd120bd4eff9fc05bda9d16a304c1be34a9d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16 XcmZ?s^6_zYb2nsQWME(rU|HlGEx;%D^rVg6!P**faWI_W#*|f*fBIRrT}$m z1(%331OnBS}pPJUtv(C*A)g_OjS#H7UHRC^PK z4~iK;wb~gaB?ZM+`ugSN<$6F@mXs!?>Lusr>X#>$Bxl%{*``JYn^)v!1(~>IyTO7{ zjAi;_pk5dd*gaXD4xN6R=}~Lzuz^P=?_3X`=F` z{?n)238~mR=i5C!?K|`SgNzRLb%85}X<}fLSn=wj{A?(n1C53&gD`;hf&o(v1A_pF n#fWe|LHlGEx;%D^rVg6!P**faWI_W#*|f*fBIRrT}$m z1(%331OnBS}pPJUtv(C*A)g_OjS#H7TiRC^PK z4~iK;wb~gaB?ZM+`ugSN<$6F@mXs!?>Lusr>X#>$Bxl%{*``JYn^)v!1(~>IyTO7{ zjAi;_pk5dd*gaXD4xN6R=}~Lzuz^P=?_3X`=F` z{?n)238~mR=i5C!?K|`SgNzRLb%85}X<}fLSn=wj{A?(n1C53&gD`;hf&o(v1A_pF n#fWe|L lsm_tree::Result<()> { - use lsm_tree::{ - blob_tree::FragmentationEntry, AbstractTree, KvSeparationOptions, SeqNo, - SequenceNumberCounter, - }; - - let folder = tempfile::tempdir()?; - let path = folder.path(); - - let tree = lsm_tree::Config::new( - path, - SequenceNumberCounter::default(), - SequenceNumberCounter::default(), - ) - .with_kv_separation(Some( - KvSeparationOptions::default() - .compression(lsm_tree::CompressionType::Lz4) - .separation_threshold(1) - .staleness_threshold(0.0000001) - .age_cutoff(1.0), - )) - .open()?; - - let big_value = b"abc".repeat(50); - - tree.insert("a", &big_value, 0); - tree.insert("b", b"smol", 0); - tree.flush_active_memtable(0)?; - assert_eq!(1, tree.table_count()); - assert_eq!(1, tree.blob_file_count()); - - { - let value = tree.get("a", SeqNo::MAX)?.unwrap(); - assert_eq!(&*value, big_value); - - let value = tree.get("b", SeqNo::MAX)?.unwrap(); - assert_eq!(&*value, b"smol"); - } - - tree.remove("b", 1); - tree.flush_active_memtable(0)?; - assert_eq!(2, tree.table_count()); - assert_eq!(1, tree.blob_file_count()); - - { - let value = tree.get("a", SeqNo::MAX)?.unwrap(); - assert_eq!(&*value, big_value); - - assert!(!tree.contains_key("b", SeqNo::MAX)?); - } - - tree.major_compact(u64::MAX, 1_000)?; - assert_eq!(1, tree.table_count()); - assert_eq!(1, tree.blob_file_count()); - - { - let gc_stats = tree.current_version().gc_stats().clone(); - - assert_eq!( - &{ - let mut map = lsm_tree::HashMap::default(); - map.insert( - 0, - FragmentationEntry::new(1, b"smol".len().try_into().unwrap(), 5), - ); - map - }, - &*gc_stats, - ); - } - - { - let value = tree.get("a", SeqNo::MAX)?.unwrap(); - assert_eq!(&*value, big_value); - - assert!(!tree.contains_key("b", SeqNo::MAX)?); - } - - tree.major_compact(u64::MAX, 1_000)?; - assert_eq!(1, tree.table_count()); - assert_eq!(1, tree.blob_file_count()); - - { - let gc_stats = tree.current_version().gc_stats().clone(); - - assert_eq!(&lsm_tree::HashMap::default(), &*gc_stats); - } - - { - let value = tree.get("a", SeqNo::MAX)?.unwrap(); - assert_eq!(&*value, big_value); - - assert!(!tree.contains_key("b", SeqNo::MAX)?); - } - - Ok(()) -} diff --git a/crates/lsm-tree/tests/blob_empty_flush.rs b/crates/lsm-tree/tests/blob_empty_flush.rs deleted file mode 100644 index 56a72b053..000000000 --- a/crates/lsm-tree/tests/blob_empty_flush.rs +++ /dev/null @@ -1,30 +0,0 @@ -use lsm_tree::{get_tmp_folder, AbstractTree, SequenceNumberCounter}; -use test_log::test; - -#[test] -fn blob_tree_flush_empty() -> lsm_tree::Result<()> { - let folder = get_tmp_folder(); - let path = folder.path(); - - let medium_value = b"a".repeat(500); - - let tree = lsm_tree::Config::new( - path, - SequenceNumberCounter::default(), - SequenceNumberCounter::default(), - ) - .with_kv_separation(Some(Default::default())) - .open()?; - - tree.insert("med", &medium_value, 0); - tree.flush_active_memtable(0)?; - - assert_eq!(1, tree.table_count()); - assert_eq!(0, tree.blob_file_count()); - - // Blob writer should have cleaned up blob file because it was empty - let blob_file_count_on_disk = std::fs::read_dir(path.join("blobs"))?.flatten().count(); - assert_eq!(0, blob_file_count_on_disk); - - Ok(()) -} diff --git a/crates/lsm-tree/tests/blob_fifo_limit.rs b/crates/lsm-tree/tests/blob_fifo_limit.rs deleted file mode 100644 index 00ae4db59..000000000 --- a/crates/lsm-tree/tests/blob_fifo_limit.rs +++ /dev/null @@ -1,28 +0,0 @@ -use lsm_tree::{get_tmp_folder, AbstractTree, KvSeparationOptions, SequenceNumberCounter}; -use std::sync::Arc; -use test_log::test; - -#[test] -fn blob_tree_fifo_limit() -> lsm_tree::Result<()> { - let folder = get_tmp_folder(); - let path = folder.path(); - - let tree = lsm_tree::Config::new( - path, - SequenceNumberCounter::default(), - SequenceNumberCounter::default(), - ) - .with_kv_separation(Some(KvSeparationOptions::default().separation_threshold(1))) - .open()?; - - let compaction = Arc::new(lsm_tree::compaction::Fifo::new(10, None)); - - for _ in 0..100 { - tree.insert(nanoid::nanoid!(), "$", 0); - tree.flush_active_memtable(0)?; - tree.compact(compaction.clone(), 0)?; - assert!((0..10).contains(&tree.blob_file_count())); - } - - Ok(()) -} diff --git a/crates/lsm-tree/tests/blob_file_full_file_checksum.rs b/crates/lsm-tree/tests/blob_file_full_file_checksum.rs deleted file mode 100644 index d5cb8c07f..000000000 --- a/crates/lsm-tree/tests/blob_file_full_file_checksum.rs +++ /dev/null @@ -1,121 +0,0 @@ -use lsm_tree::{get_tmp_folder, AbstractTree, Config, KvSeparationOptions, SequenceNumberCounter}; -use test_log::test; -use xxhash_rust::xxh3::xxh3_128; - -#[test] -fn blob_file_full_file_checksum() -> lsm_tree::Result<()> { - let folder = get_tmp_folder(); - - { - let tree = Config::new( - &folder, - SequenceNumberCounter::default(), - SequenceNumberCounter::default(), - ) - .with_kv_separation(Some(KvSeparationOptions::default().separation_threshold(1))) - .open()?; - - for key in ('a'..='z').map(|c| c.to_string()) { - let value = nanoid::nanoid!(); - tree.insert(key, value.as_bytes(), 0); - } - tree.flush_active_memtable(0)?; - - let version = tree.current_version(); - let blob_file = version.blob_files.iter().next().unwrap(); - - let expected_checksum = blob_file.checksum().into_u128(); - let real_checksum = xxh3_128(&std::fs::read(blob_file.path())?); - assert_eq!( - real_checksum, expected_checksum, - "full file checksum mismatch", - ); - } - - { - let tree = Config::new( - &folder, - SequenceNumberCounter::default(), - SequenceNumberCounter::default(), - ) - .with_kv_separation(Some(KvSeparationOptions::default().separation_threshold(1))) - .open()?; - - let version = tree.current_version(); - let blob_file = version.blob_files.iter().next().unwrap(); - - let expected_checksum = blob_file.checksum().into_u128(); - let real_checksum = xxh3_128(&std::fs::read(blob_file.path())?); - assert_eq!( - real_checksum, expected_checksum, - "full file checksum mismatch", - ); - } - - Ok(()) -} - -#[test] -fn blob_file_full_file_detect_corruption() -> lsm_tree::Result<()> { - let folder = get_tmp_folder(); - - { - let tree = Config::new( - &folder, - SequenceNumberCounter::default(), - SequenceNumberCounter::default(), - ) - .with_kv_separation(Some(KvSeparationOptions::default().separation_threshold(1))) - .open()?; - - for key in ('a'..='z').map(|c| c.to_string()) { - let value = nanoid::nanoid!(); - tree.insert(key, value.as_bytes(), 0); - } - tree.flush_active_memtable(0)?; - - let version = tree.current_version(); - let blob_file = version.blob_files.iter().next().unwrap(); - - let expected_checksum = blob_file.checksum().into_u128(); - let real_checksum = xxh3_128(&std::fs::read(blob_file.path())?); - assert_eq!( - real_checksum, expected_checksum, - "full file checksum mismatch", - ); - } - - { - let tree = Config::new( - &folder, - SequenceNumberCounter::default(), - SequenceNumberCounter::default(), - ) - .with_kv_separation(Some(KvSeparationOptions::default().separation_threshold(1))) - .open()?; - - let version = tree.current_version(); - let blob_file = version.blob_files.iter().next().unwrap(); - - { - use std::io::{Seek, Write}; - - let mut f = std::fs::OpenOptions::new() - .write(true) - .open(blob_file.path())?; - - f.seek(std::io::SeekFrom::Start(100))?; - f.write_all(b"!")?; - f.sync_all()?; - } - - let expected_checksum = blob_file.checksum().into_u128(); - let real_checksum = xxh3_128(&std::fs::read(blob_file.path())?); - assert_ne!( - real_checksum, expected_checksum, - "full file checksum should not match", - ); - } - - Ok(()) -} diff --git a/crates/lsm-tree/tests/blob_flush_gc_stats.rs b/crates/lsm-tree/tests/blob_flush_gc_stats.rs deleted file mode 100644 index c88489c79..000000000 --- a/crates/lsm-tree/tests/blob_flush_gc_stats.rs +++ /dev/null @@ -1,71 +0,0 @@ -use lsm_tree::{get_tmp_folder, AbstractTree, SeqNo, SequenceNumberCounter}; -use test_log::test; - -#[test] -fn blob_tree_flush_gc_stats() -> lsm_tree::Result<()> { - let folder = get_tmp_folder(); - let path = folder.path(); - - let big_value = b"neptune!".repeat(128_000); - let new_big_value = b"winter!".repeat(128_000); - - { - let tree = lsm_tree::Config::new( - path, - SequenceNumberCounter::default(), - SequenceNumberCounter::default(), - ) - .with_kv_separation(Some(Default::default())) - .open()?; - - assert!(tree.get("big", SeqNo::MAX)?.is_none()); - tree.insert("big", &big_value, 0); - tree.insert("smol", "small value", 0); - - let value = tree.get("big", SeqNo::MAX)?.expect("should exist"); - assert_eq!(&*value, big_value); - - tree.insert("big", &new_big_value, 1); - - tree.flush_active_memtable(1_000)?; - - // NOTE: The first big_value is dropped, so it never arrives in a blob file - assert_eq!(2, tree.approximate_len()); - } - - Ok(()) -} - -#[test] -fn blob_tree_flush_gc_stats_tombstone() -> lsm_tree::Result<()> { - let folder = get_tmp_folder(); - let path = folder.path(); - - let big_value = b"neptune!".repeat(128_000); - - { - let tree = lsm_tree::Config::new( - path, - SequenceNumberCounter::default(), - SequenceNumberCounter::default(), - ) - .with_kv_separation(Some(Default::default())) - .open()?; - - assert!(tree.get("big", SeqNo::MAX)?.is_none()); - tree.insert("big", &big_value, 0); - tree.insert("smol", "small value", 0); - - let value = tree.get("big", SeqNo::MAX)?.expect("should exist"); - assert_eq!(&*value, big_value); - - tree.remove("big", 1); - - tree.flush_active_memtable(1_000)?; - - // NOTE: The first big_value is dropped, so it never arrives in a blob file - assert_eq!(2, tree.approximate_len()); - } - - Ok(()) -} diff --git a/crates/lsm-tree/tests/blob_gc_watermark.rs b/crates/lsm-tree/tests/blob_gc_watermark.rs deleted file mode 100644 index 5cb2c0282..000000000 --- a/crates/lsm-tree/tests/blob_gc_watermark.rs +++ /dev/null @@ -1,105 +0,0 @@ -use lsm_tree::{ - config::CompressionPolicy, get_tmp_folder, AbstractTree, Config, KvSeparationOptions, SeqNo, - SequenceNumberCounter, -}; -use test_log::test; - -// NOTE: This was a logic/MVCC error in v2 that could drop -// a blob file while it was maybe accessible by a snapshot read -// -// https://github.com/fjall-rs/lsm-tree/commit/79c6ead4b955051cbb4835913e21d08b8aeafba1 -#[test] -fn blob_gc_seqno_watermark() -> lsm_tree::Result<()> { - let folder = get_tmp_folder(); - - let seqno = SequenceNumberCounter::default(); - - let tree = Config::new(&folder, seqno.clone(), SequenceNumberCounter::default()) - .data_block_compression_policy(CompressionPolicy::all(lsm_tree::CompressionType::None)) - .with_kv_separation(Some( - KvSeparationOptions::default() - .staleness_threshold(0.01) - .age_cutoff(1.0) - .separation_threshold(50), - )) - .open()?; - - tree.insert("a", "neptune".repeat(50), seqno.next()); - - let snapshot_seqno = seqno.get(); - - assert_eq!( - &*tree.get("a", snapshot_seqno)?.unwrap(), - b"neptune".repeat(50), - ); - assert_eq!(&*tree.get("a", SeqNo::MAX)?.unwrap(), b"neptune".repeat(50),); - - tree.insert("a", "neptune2".repeat(50), seqno.next()); - assert_eq!( - &*tree.get("a", snapshot_seqno)?.unwrap(), - b"neptune".repeat(50), - ); - assert_eq!( - &*tree.get("a", SeqNo::MAX)?.unwrap(), - b"neptune2".repeat(50), - ); - - tree.insert("a", "neptune3".repeat(50), seqno.next()); - assert_eq!( - &*tree.get("a", snapshot_seqno)?.unwrap(), - b"neptune".repeat(50), - ); - assert_eq!( - &*tree.get("a", SeqNo::MAX)?.unwrap(), - b"neptune3".repeat(50), - ); - - tree.flush_active_memtable(0)?; - - assert_eq!( - &*tree.get("a", snapshot_seqno)?.unwrap(), - b"neptune".repeat(50), - ); - assert_eq!( - &*tree.get("a", SeqNo::MAX)?.unwrap(), - b"neptune3".repeat(50), - ); - - tree.major_compact(u64::MAX, 0)?; - tree.major_compact(u64::MAX, 0)?; - - // IMPORTANT: We cannot drop any blobs yet - // because the watermark is too low - // - // This would previously fail - - { - let gc_stats = tree.current_version().gc_stats().clone(); - assert_eq!(&lsm_tree::HashMap::default(), &*gc_stats); - } - - assert_eq!( - &*tree.get("a", snapshot_seqno)?.unwrap(), - b"neptune".repeat(50), - ); - assert_eq!( - &*tree.get("a", SeqNo::MAX)?.unwrap(), - b"neptune3".repeat(50), - ); - - tree.major_compact(u64::MAX, 1_000)?; - - { - let gc_stats = tree.current_version().gc_stats().clone(); - assert!(!gc_stats.is_empty()); - } - - tree.major_compact(u64::MAX, 1_000)?; - - { - let gc_stats = tree.current_version().gc_stats().clone(); - assert_eq!(&lsm_tree::HashMap::default(), &*gc_stats); - } - - Ok(()) -} diff --git a/crates/lsm-tree/tests/blob_ingest.rs b/crates/lsm-tree/tests/blob_ingest.rs deleted file mode 100644 index 93cef156e..000000000 --- a/crates/lsm-tree/tests/blob_ingest.rs +++ /dev/null @@ -1,61 +0,0 @@ -use lsm_tree::{ - blob_tree::FragmentationEntry, get_tmp_folder, AbstractTree, KvSeparationOptions, SeqNo, - SequenceNumberCounter, -}; -use test_log::test; - -#[test] -fn blob_ingest_gc_stats() -> lsm_tree::Result<()> { - let folder = get_tmp_folder(); - let path = folder.path(); - - let big_value = b"neptune!".repeat(128_000); - let new_big_value = b"winter!".repeat(128_000); - - { - let tree = lsm_tree::Config::new( - path, - SequenceNumberCounter::default(), - SequenceNumberCounter::default(), - ) - .with_kv_separation(Some( - KvSeparationOptions::default().compression(lsm_tree::CompressionType::None), - )) - .open()?; - - let mut ingestion = tree.ingestion()?; - ingestion.write("big", &big_value)?; - ingestion.write("smol", "small value")?; - ingestion.finish()?; - - let value = tree.get("big", SeqNo::MAX)?.expect("should exist"); - assert_eq!(&*value, big_value); - assert_eq!(1, tree.table_count()); - assert_eq!(1, tree.blob_file_count()); - - let mut ingestion = tree.ingestion()?; - ingestion.write("big", &new_big_value)?; - ingestion.finish()?; - - // Blob file has no fragmentation before compaction (in stats) - // so it is not rewritten - tree.major_compact(64_000_000, 1_000)?; - assert_eq!(1, tree.table_count()); - assert_eq!(2, tree.blob_file_count()); - - let gc_stats = tree.current_version().gc_stats().clone(); - - // "big":0 is expired - assert_eq!( - &{ - let mut map = lsm_tree::HashMap::default(); - let size = big_value.len() as u64; - map.insert(0, FragmentationEntry::new(1, size, size)); - map - }, - &*gc_stats, - ); - } - - Ok(()) -} diff --git a/crates/lsm-tree/tests/blob_ingest_relink.rs b/crates/lsm-tree/tests/blob_ingest_relink.rs deleted file mode 100644 index 619d0f451..000000000 --- a/crates/lsm-tree/tests/blob_ingest_relink.rs +++ /dev/null @@ -1,66 +0,0 @@ -use lsm_tree::{get_tmp_folder, AbstractTree, KvSeparationOptions, SeqNo, SequenceNumberCounter}; -use test_log::test; - -#[test] -fn blob_tree_ingest_relink() -> lsm_tree::Result<()> { - let folder = get_tmp_folder(); - let path = folder.path(); - - let big_value = b"neptune!".repeat(128_000); - - { - let tree = lsm_tree::Config::new( - path, - SequenceNumberCounter::default(), - SequenceNumberCounter::default(), - ) - .with_kv_separation(Some( - KvSeparationOptions::default().compression(lsm_tree::CompressionType::None), - )) - .open()?; - - let mut ingestion = tree.ingestion()?; - ingestion.write("big", &big_value)?; - ingestion.write("smol", "small value")?; - ingestion.finish()?; - - let value = tree.get("big", SeqNo::MAX)?.expect("should exist"); - assert_eq!(&*value, big_value); - assert_eq!(1, tree.table_count()); - assert_eq!(1, tree.blob_file_count()); - - assert_eq!( - Some(vec![lsm_tree::table::writer::LinkedFile { - blob_file_id: 0, - bytes: big_value.len() as u64, - on_disk_bytes: big_value.len() as u64, - len: 1, - }]), - tree.current_version() - .iter_tables() - .next() - .unwrap() - .list_blob_file_references()?, - ); - - tree.major_compact(64_000_000, 1_000)?; - assert_eq!(1, tree.table_count()); - assert_eq!(1, tree.blob_file_count()); - - assert_eq!( - Some(vec![lsm_tree::table::writer::LinkedFile { - blob_file_id: 0, - bytes: big_value.len() as u64, - on_disk_bytes: big_value.len() as u64, - len: 1, - }]), - tree.current_version() - .iter_tables() - .next() - .unwrap() - .list_blob_file_references()?, - ); - } - - Ok(()) -} diff --git a/crates/lsm-tree/tests/blob_major_compact_drop_dead_files.rs b/crates/lsm-tree/tests/blob_major_compact_drop_dead_files.rs deleted file mode 100644 index 5509fb3a6..000000000 --- a/crates/lsm-tree/tests/blob_major_compact_drop_dead_files.rs +++ /dev/null @@ -1,100 +0,0 @@ -use lsm_tree::{ - blob_tree::FragmentationEntry, get_tmp_folder, AbstractTree, KvSeparationOptions, SeqNo, - SequenceNumberCounter, -}; -use test_log::test; - -#[test] -fn blob_tree_major_compact_drop_dead_files() -> lsm_tree::Result<()> { - let folder = get_tmp_folder(); - let path = folder.path(); - - let big_value = b"neptune!".repeat(128_000); - let new_big_value = b"winter!".repeat(128_000); - - { - let tree = lsm_tree::Config::new( - path, - SequenceNumberCounter::default(), - SequenceNumberCounter::default(), - ) - .with_kv_separation(Some( - KvSeparationOptions::default().compression(lsm_tree::CompressionType::None), - )) - .open()?; - - assert!(tree.get("big", SeqNo::MAX)?.is_none()); - - tree.insert("big", &big_value, 0); - tree.flush_active_memtable(0)?; - assert_eq!(1, tree.table_count()); - assert_eq!(1, tree.blob_file_count()); - - tree.insert("big", &big_value, 1); - tree.flush_active_memtable(0)?; - assert_eq!(2, tree.table_count()); - assert_eq!(2, tree.blob_file_count()); - - tree.insert("big", &big_value, 2); - tree.flush_active_memtable(0)?; - assert_eq!(3, tree.table_count()); - assert_eq!(3, tree.blob_file_count()); - - tree.insert("big", &big_value, 3); - tree.flush_active_memtable(0)?; - assert_eq!(4, tree.table_count()); - assert_eq!(4, tree.blob_file_count()); - - tree.insert("big", &new_big_value, 4); - tree.flush_active_memtable(0)?; - assert_eq!(5, tree.table_count()); - assert_eq!(5, tree.blob_file_count()); - - let value = tree.get("big", SeqNo::MAX)?.expect("should exist"); - assert_eq!(&*value, new_big_value); - - { - let gc_stats = tree.current_version().gc_stats().clone(); - - assert_eq!(&lsm_tree::HashMap::default(), &*gc_stats); - } - - tree.major_compact(64_000_000, 1_000)?; - assert_eq!(1, tree.table_count()); - assert_eq!(5, tree.blob_file_count()); - - let value = tree.get("big", SeqNo::MAX)?.expect("should exist"); - assert_eq!(&*value, new_big_value); - - { - let gc_stats = tree.current_version().gc_stats().clone(); - - assert_eq!( - &{ - let mut map = lsm_tree::HashMap::default(); - let size = big_value.len() as u64; - map.insert(0, FragmentationEntry::new(1, size, size)); - map.insert(1, FragmentationEntry::new(1, size, size)); - map.insert(2, FragmentationEntry::new(1, size, size)); - map.insert(3, FragmentationEntry::new(1, size, size)); - map - }, - &*gc_stats, - ); - } - - tree.major_compact(64_000_000, 1_000)?; - assert_eq!(1, tree.table_count()); - assert_eq!(1, tree.blob_file_count()); - - { - let gc_stats = tree.current_version().gc_stats().clone(); - assert_eq!(&lsm_tree::HashMap::default(), &*gc_stats); - } - - let value = tree.get("big", SeqNo::MAX)?.expect("should exist"); - assert_eq!(&*value, new_big_value); - } - - Ok(()) -} diff --git a/crates/lsm-tree/tests/blob_major_compact_gc_stats.rs b/crates/lsm-tree/tests/blob_major_compact_gc_stats.rs deleted file mode 100644 index 3f5d147f7..000000000 --- a/crates/lsm-tree/tests/blob_major_compact_gc_stats.rs +++ /dev/null @@ -1,205 +0,0 @@ -use lsm_tree::{ - blob_tree::FragmentationEntry, get_tmp_folder, AbstractTree, KvSeparationOptions, SeqNo, - SequenceNumberCounter, -}; -use test_log::test; - -#[test] -fn blob_tree_major_compact_gc_stats() -> lsm_tree::Result<()> { - let folder = get_tmp_folder(); - let path = folder.path(); - - let big_value = b"neptune!".repeat(128_000); - let new_big_value = b"winter!".repeat(128_000); - - { - let tree = lsm_tree::Config::new( - path, - SequenceNumberCounter::default(), - SequenceNumberCounter::default(), - ) - .with_kv_separation(Some( - KvSeparationOptions::default().compression(lsm_tree::CompressionType::None), - )) - .open()?; - - assert!(tree.get("big", SeqNo::MAX)?.is_none()); - tree.insert("big", &big_value, 0); - tree.insert("smol", "small value", 0); - - let value = tree.get("big", SeqNo::MAX)?.expect("should exist"); - assert_eq!(&*value, big_value); - - tree.flush_active_memtable(0)?; - assert_eq!(1, tree.table_count()); - assert_eq!(1, tree.blob_file_count()); - - tree.insert("big", &new_big_value, 1); - - tree.flush_active_memtable(0)?; - - // Blob file has no fragmentation before compaction (in stats) - // so it is not rewritten - tree.major_compact(64_000_000, 1_000)?; - assert_eq!(1, tree.table_count()); - assert_eq!(2, tree.blob_file_count()); - - let gc_stats = tree.current_version().gc_stats().clone(); - - // "big":0 is expired - assert_eq!( - &{ - let mut map = lsm_tree::HashMap::default(); - let size = big_value.len() as u64; - map.insert(0, FragmentationEntry::new(1, size, size)); - map - }, - &*gc_stats, - ); - assert_eq!(big_value.len() as u64, tree.stale_blob_bytes()); - } - - Ok(()) -} - -#[test] -fn blob_tree_major_compact_gc_stats_2() -> lsm_tree::Result<()> { - let folder = get_tmp_folder(); - let path = folder.path(); - - let big_value = b"neptune!".repeat(128_000); - - { - let tree = lsm_tree::Config::new( - path, - SequenceNumberCounter::default(), - SequenceNumberCounter::default(), - ) - .with_kv_separation(Some( - KvSeparationOptions::default().compression(lsm_tree::CompressionType::None), - )) - .open()?; - - assert!(tree.get("big", SeqNo::MAX)?.is_none()); - tree.insert("big", &big_value, 0); - tree.insert("big", &big_value, 1); - tree.insert("big", &big_value, 2); - tree.insert("big", &big_value, 3); - tree.insert("big", &big_value, 4); - - let value = tree.get("big", SeqNo::MAX)?.expect("should exist"); - assert_eq!(&*value, big_value); - - tree.flush_active_memtable(0)?; - assert_eq!(1, tree.table_count()); - assert_eq!(1, tree.blob_file_count()); - - // Blob file has no fragmentation before compaction (in stats) - // so it is not rewritten - tree.major_compact(64_000_000, 1_000)?; - assert_eq!(1, tree.table_count()); - assert_eq!(1, tree.blob_file_count()); - - let gc_stats = tree.current_version().gc_stats().clone(); - - // "big":0,1,2,3 are expired - assert_eq!( - &{ - let mut map = lsm_tree::HashMap::default(); - let size = big_value.len() as u64; - map.insert(0, FragmentationEntry::new(4, size * 4, size * 4)); - map - }, - &*gc_stats, - ); - } - - Ok(()) -} - -#[test] -fn blob_tree_major_compact_gc_stats_tombstone() -> lsm_tree::Result<()> { - let folder = get_tmp_folder(); - let path = folder.path(); - - let big_value = b"neptune!".repeat(128_000); - - { - let tree = lsm_tree::Config::new( - path, - SequenceNumberCounter::default(), - SequenceNumberCounter::default(), - ) - .with_kv_separation(Some( - KvSeparationOptions::default().compression(lsm_tree::CompressionType::None), - )) - .open()?; - - assert!(tree.get("big", SeqNo::MAX)?.is_none()); - tree.insert("big", &big_value, 0); - tree.insert("another_big", &big_value, 0); - tree.insert("smol", "small value", 0); - - let value = tree.get("big", SeqNo::MAX)?.expect("should exist"); - assert_eq!(&*value, big_value); - - tree.flush_active_memtable(0)?; - assert_eq!(1, tree.table_count()); - assert_eq!(1, tree.blob_file_count()); - - tree.remove("big", 1); - - tree.flush_active_memtable(0)?; - assert_eq!(2, tree.table_count()); - assert_eq!(1, tree.blob_file_count()); - - assert_eq!( - Some(vec![lsm_tree::table::writer::LinkedFile { - blob_file_id: 0, - bytes: 2 * big_value.len() as u64, - on_disk_bytes: 2 * big_value.len() as u64, - len: 2, - }]), - tree.current_version() - .iter_tables() - .nth(1) - .unwrap() - .list_blob_file_references()?, - ); - - // Blob file has no fragmentation before compaction (in stats) - // so it is not rewritten - tree.major_compact(64_000_000, 1_000)?; - assert_eq!(1, tree.table_count()); - assert_eq!(1, tree.blob_file_count()); - - let gc_stats = tree.current_version().gc_stats().clone(); - - // "big":0 is expired - assert_eq!( - &{ - let mut map = lsm_tree::HashMap::default(); - let size = big_value.len() as u64; - map.insert(0, FragmentationEntry::new(1, size, size)); - map - }, - &*gc_stats, - ); - - assert_eq!( - Some(vec![lsm_tree::table::writer::LinkedFile { - blob_file_id: 0, - bytes: big_value.len() as u64, - on_disk_bytes: big_value.len() as u64, - len: 1, - }]), - tree.current_version() - .iter_tables() - .next() - .unwrap() - .list_blob_file_references()?, - ); - } - - Ok(()) -} diff --git a/crates/lsm-tree/tests/blob_major_compact_relink.rs b/crates/lsm-tree/tests/blob_major_compact_relink.rs deleted file mode 100644 index c2bdcba7e..000000000 --- a/crates/lsm-tree/tests/blob_major_compact_relink.rs +++ /dev/null @@ -1,69 +0,0 @@ -use lsm_tree::{get_tmp_folder, AbstractTree, KvSeparationOptions, SeqNo, SequenceNumberCounter}; -use test_log::test; - -#[test] -fn blob_tree_major_compact_relink() -> lsm_tree::Result<()> { - let folder = get_tmp_folder(); - let path = folder.path(); - - let big_value = b"neptune!".repeat(128_000); - - { - let tree = lsm_tree::Config::new( - path, - SequenceNumberCounter::default(), - SequenceNumberCounter::default(), - ) - .with_kv_separation(Some( - KvSeparationOptions::default().compression(lsm_tree::CompressionType::None), - )) - .open()?; - - assert!(tree.get("big", SeqNo::MAX)?.is_none()); - tree.insert("big", &big_value, 0); - tree.insert("smol", "small value", 0); - - let value = tree.get("big", SeqNo::MAX)?.expect("should exist"); - assert_eq!(&*value, big_value); - - tree.flush_active_memtable(0)?; - assert_eq!(1, tree.table_count()); - assert_eq!(1, tree.blob_file_count()); - - assert_eq!( - Some(vec![lsm_tree::table::writer::LinkedFile { - blob_file_id: 0, - bytes: big_value.len() as u64, - on_disk_bytes: big_value.len() as u64, - len: 1, - }]), - tree.current_version() - .iter_tables() - .next() - .unwrap() - .list_blob_file_references()?, - ); - - tree.flush_active_memtable(1)?; - - tree.major_compact(64_000_000, 1_000)?; - assert_eq!(1, tree.table_count()); - assert_eq!(1, tree.blob_file_count()); - - assert_eq!( - Some(vec![lsm_tree::table::writer::LinkedFile { - blob_file_id: 0, - bytes: big_value.len() as u64, - on_disk_bytes: big_value.len() as u64, - len: 1, - }]), - tree.current_version() - .iter_tables() - .next() - .unwrap() - .list_blob_file_references()?, - ); - } - - Ok(()) -} diff --git a/crates/lsm-tree/tests/blob_major_compact_relocation.rs b/crates/lsm-tree/tests/blob_major_compact_relocation.rs deleted file mode 100644 index e915fa8f6..000000000 --- a/crates/lsm-tree/tests/blob_major_compact_relocation.rs +++ /dev/null @@ -1,326 +0,0 @@ -use lsm_tree::{ - blob_tree::FragmentationEntry, get_tmp_folder, AbstractTree, KvSeparationOptions, SeqNo, - SequenceNumberCounter, -}; -use test_log::test; - -#[test] -fn blob_tree_major_compact_relocation_simple() -> lsm_tree::Result<()> { - let folder = get_tmp_folder(); - let path = folder.path(); - - let big_value = b"neptune!".repeat(128_000); - let new_big_value = b"winter!".repeat(128_000); - - { - let tree = lsm_tree::Config::new( - path, - SequenceNumberCounter::default(), - SequenceNumberCounter::default(), - ) - .with_kv_separation(Some( - KvSeparationOptions::default() - .compression(lsm_tree::CompressionType::None) - .age_cutoff(1.0), - )) - .open()?; - - assert!(tree.get("big", SeqNo::MAX)?.is_none()); - tree.insert("big", &big_value, 0); - tree.insert("big2", &big_value, 0); - tree.insert("smol", "smol", 0); - - let value = tree.get("big", SeqNo::MAX)?.expect("should exist"); - assert_eq!(&*value, big_value); - let value = tree.get("big2", SeqNo::MAX)?.expect("should exist"); - assert_eq!(&*value, big_value); - let value = tree.get("smol", SeqNo::MAX)?.expect("should exist"); - assert_eq!(&*value, b"smol"); - - tree.flush_active_memtable(0)?; - assert_eq!(1, tree.table_count()); - assert_eq!(1, tree.blob_file_count()); - - tree.insert("big", &new_big_value, 1); - - tree.flush_active_memtable(0)?; - - let value = tree.get("big", SeqNo::MAX)?.expect("should exist"); - assert_eq!(&*value, new_big_value); - let value = tree.get("big2", SeqNo::MAX)?.expect("should exist"); - assert_eq!(&*value, big_value); - let value = tree.get("smol", SeqNo::MAX)?.expect("should exist"); - assert_eq!(&*value, b"smol"); - - tree.major_compact(64_000_000, 1_000)?; - assert_eq!(1, tree.table_count()); - assert_eq!(2, tree.blob_file_count()); - - let value = tree.get("big", SeqNo::MAX)?.expect("should exist"); - assert_eq!(&*value, new_big_value); - let value = tree.get("big2", SeqNo::MAX)?.expect("should exist"); - assert_eq!(&*value, big_value); - let value = tree.get("smol", SeqNo::MAX)?.expect("should exist"); - assert_eq!(&*value, b"smol"); - - { - let gc_stats = tree.current_version().gc_stats().clone(); - - // "big":0 is expired - assert_eq!( - &{ - let mut map = lsm_tree::HashMap::default(); - let size = big_value.len() as u64; - map.insert(0, FragmentationEntry::new(1, size, size)); - map - }, - &*gc_stats, - ); - } - - tree.major_compact(64_000_000, 1_000)?; - assert_eq!(1, tree.table_count()); - assert_eq!(2, tree.blob_file_count()); - - { - let gc_stats = tree.current_version().gc_stats().clone(); - - assert_eq!(&lsm_tree::HashMap::default(), &*gc_stats); - } - - let value = tree.get("big", SeqNo::MAX)?.expect("should exist"); - assert_eq!(&*value, new_big_value); - let value = tree.get("big2", SeqNo::MAX)?.expect("should exist"); - assert_eq!(&*value, big_value); - let value = tree.get("smol", SeqNo::MAX)?.expect("should exist"); - assert_eq!(&*value, b"smol"); - } - - Ok(()) -} - -#[test] -fn blob_tree_major_compact_relocation_repeated_key() -> lsm_tree::Result<()> { - let folder = get_tmp_folder(); - let path = folder.path(); - - let big_value = b"neptune!".repeat(2_000); - let very_big_value = b"winter!".repeat(128_000); - - { - let tree = lsm_tree::Config::new( - path, - SequenceNumberCounter::default(), - SequenceNumberCounter::default(), - ) - .with_kv_separation(Some( - KvSeparationOptions::default() - .compression(lsm_tree::CompressionType::None) - .age_cutoff(1.0), - )) - .open()?; - - assert!(tree.get("big", SeqNo::MAX)?.is_none()); - tree.insert("a", &big_value, 0); - tree.insert("b", &big_value, 0); - tree.insert("c", &very_big_value, 0); - tree.insert("d", &big_value, 0); - tree.insert("e", &big_value, 0); - - let value = tree.get("a", SeqNo::MAX)?.expect("should exist"); - assert_eq!(&*value, big_value); - let value = tree.get("b", SeqNo::MAX)?.expect("should exist"); - assert_eq!(&*value, big_value); - let value = tree.get("c", SeqNo::MAX)?.expect("should exist"); - assert_eq!(&*value, very_big_value); - let value = tree.get("d", SeqNo::MAX)?.expect("should exist"); - assert_eq!(&*value, big_value); - let value = tree.get("e", SeqNo::MAX)?.expect("should exist"); - assert_eq!(&*value, big_value); - - tree.flush_active_memtable(0)?; - assert_eq!(1, tree.table_count()); - assert_eq!(1, tree.blob_file_count()); - - tree.remove("c", 1); - - tree.flush_active_memtable(0)?; - assert_eq!(2, tree.table_count()); - assert_eq!(1, tree.blob_file_count()); - - let value = tree.get("a", SeqNo::MAX)?.expect("should exist"); - assert_eq!(&*value, big_value); - let value = tree.get("b", SeqNo::MAX)?.expect("should exist"); - assert_eq!(&*value, big_value); - let value = tree.get("c", SeqNo::MAX)?; - assert!(value.is_none()); - let value = tree.get("d", SeqNo::MAX)?.expect("should exist"); - assert_eq!(&*value, big_value); - let value = tree.get("e", SeqNo::MAX)?.expect("should exist"); - assert_eq!(&*value, big_value); - - tree.major_compact(64_000_000, 1_000)?; - assert_eq!(1, tree.table_count()); - assert_eq!(1, tree.blob_file_count()); - - let value = tree.get("a", SeqNo::MAX)?.expect("should exist"); - assert_eq!(&*value, big_value); - let value = tree.get("b", SeqNo::MAX)?.expect("should exist"); - assert_eq!(&*value, big_value); - let value = tree.get("c", SeqNo::MAX)?; - assert!(value.is_none()); - let value = tree.get("d", SeqNo::MAX)?.expect("should exist"); - assert_eq!(&*value, big_value); - let value = tree.get("e", SeqNo::MAX)?.expect("should exist"); - assert_eq!(&*value, big_value); - - { - let gc_stats = tree.current_version().gc_stats().clone(); - - assert_eq!( - &{ - let mut map = lsm_tree::HashMap::default(); - let size = very_big_value.len() as u64; - map.insert(0, FragmentationEntry::new(1, size, size)); - map - }, - &*gc_stats, - ); - } - - tree.major_compact(64_000_000, 1_000)?; - assert_eq!(1, tree.table_count()); - assert_eq!(1, tree.blob_file_count()); - - { - let gc_stats = tree.current_version().gc_stats().clone(); - - assert_eq!(&lsm_tree::HashMap::default(), &*gc_stats); - } - - let value = tree.get("a", SeqNo::MAX)?.expect("should exist"); - assert_eq!(&*value, big_value); - let value = tree.get("b", SeqNo::MAX)?.expect("should exist"); - assert_eq!(&*value, big_value); - let value = tree.get("c", SeqNo::MAX)?; - assert!(value.is_none()); - let value = tree.get("d", SeqNo::MAX)?.expect("should exist"); - assert_eq!(&*value, big_value); - let value = tree.get("e", SeqNo::MAX)?.expect("should exist"); - assert_eq!(&*value, big_value); - } - - Ok(()) -} - -#[test] -fn blob_tree_major_compact_relocation_interleaved() -> lsm_tree::Result<()> { - let folder = get_tmp_folder(); - let path = folder.path(); - - let big_value = b"neptune!".repeat(2_000); - - { - let tree = lsm_tree::Config::new( - path, - SequenceNumberCounter::default(), - SequenceNumberCounter::default(), - ) - .with_kv_separation(Some( - KvSeparationOptions::default() - .compression(lsm_tree::CompressionType::None) - .age_cutoff(1.0), - )) - .open()?; - - assert!(tree.get("big", SeqNo::MAX)?.is_none()); - tree.insert("a", b"smol", 0); - tree.insert("b", &big_value, 0); - tree.insert("c", b"smol", 0); - tree.insert("d", &big_value, 0); - tree.insert("e", b"smol", 0); - - let value = tree.get("a", SeqNo::MAX)?.expect("should exist"); - assert_eq!(&*value, b"smol"); - let value = tree.get("b", SeqNo::MAX)?.expect("should exist"); - assert_eq!(&*value, big_value); - let value = tree.get("c", SeqNo::MAX)?.expect("should exist"); - assert_eq!(&*value, b"smol"); - let value = tree.get("d", SeqNo::MAX)?.expect("should exist"); - assert_eq!(&*value, big_value); - let value = tree.get("e", SeqNo::MAX)?.expect("should exist"); - assert_eq!(&*value, b"smol"); - - tree.flush_active_memtable(0)?; - assert_eq!(1, tree.table_count()); - assert_eq!(1, tree.blob_file_count()); - - tree.remove("d", 1); - - tree.flush_active_memtable(0)?; - assert_eq!(2, tree.table_count()); - assert_eq!(1, tree.blob_file_count()); - - let value = tree.get("a", SeqNo::MAX)?.expect("should exist"); - assert_eq!(&*value, b"smol"); - let value = tree.get("b", SeqNo::MAX)?.expect("should exist"); - assert_eq!(&*value, big_value); - let value = tree.get("c", SeqNo::MAX)?.expect("should exist"); - assert_eq!(&*value, b"smol"); - let value = tree.get("d", SeqNo::MAX)?; - assert!(value.is_none()); - let value = tree.get("e", SeqNo::MAX)?.expect("should exist"); - assert_eq!(&*value, b"smol"); - - tree.major_compact(64_000_000, 1_000)?; - assert_eq!(1, tree.table_count()); - assert_eq!(1, tree.blob_file_count()); - - let value = tree.get("a", SeqNo::MAX)?.expect("should exist"); - assert_eq!(&*value, b"smol"); - let value = tree.get("b", SeqNo::MAX)?.expect("should exist"); - assert_eq!(&*value, big_value); - let value = tree.get("c", SeqNo::MAX)?.expect("should exist"); - assert_eq!(&*value, b"smol"); - let value = tree.get("d", SeqNo::MAX)?; - assert!(value.is_none()); - let value = tree.get("e", SeqNo::MAX)?.expect("should exist"); - assert_eq!(&*value, b"smol"); - { - let gc_stats = tree.current_version().gc_stats().clone(); - - assert_eq!( - &{ - let mut map = lsm_tree::HashMap::default(); - let size = big_value.len() as u64; - map.insert(0, FragmentationEntry::new(1, size, size)); - map - }, - &*gc_stats, - ); - } - - tree.major_compact(64_000_000, 1_000)?; - assert_eq!(1, tree.table_count()); - assert_eq!(1, tree.blob_file_count()); - - { - let gc_stats = tree.current_version().gc_stats().clone(); - - assert_eq!(&lsm_tree::HashMap::default(), &*gc_stats); - } - - let value = tree.get("a", SeqNo::MAX)?.expect("should exist"); - assert_eq!(&*value, b"smol"); - let value = tree.get("b", SeqNo::MAX)?.expect("should exist"); - assert_eq!(&*value, big_value); - let value = tree.get("c", SeqNo::MAX)?.expect("should exist"); - assert_eq!(&*value, b"smol"); - let value = tree.get("d", SeqNo::MAX)?; - assert!(value.is_none()); - let value = tree.get("e", SeqNo::MAX)?.expect("should exist"); - assert_eq!(&*value, b"smol"); - } - - Ok(()) -} diff --git a/crates/lsm-tree/tests/blob_major_compact_relocation_recovery.rs b/crates/lsm-tree/tests/blob_major_compact_relocation_recovery.rs deleted file mode 100644 index fd5b536f1..000000000 --- a/crates/lsm-tree/tests/blob_major_compact_relocation_recovery.rs +++ /dev/null @@ -1,130 +0,0 @@ -#[test_log::test] -fn blob_tree_major_compact_relocation_recovery_no_comp() -> lsm_tree::Result<()> { - use lsm_tree::{ - get_tmp_folder, AbstractTree, KvSeparationOptions, SeqNo, SequenceNumberCounter, - }; - - let folder = get_tmp_folder(); - let path = folder.path(); - - let big_value = b"neptune!".repeat(4_096); - - { - let tree = lsm_tree::Config::new( - path, - SequenceNumberCounter::default(), - SequenceNumberCounter::default(), - ) - .with_kv_separation(Some( - KvSeparationOptions::default() - .compression(lsm_tree::CompressionType::None) - .separation_threshold(1) - .staleness_threshold(0.0000001) - .age_cutoff(1.0), - )) - .open()?; - - tree.insert("a", &big_value, 0); - tree.insert("b", b"smol", 0); - tree.flush_active_memtable(0)?; - assert_eq!(1, tree.table_count()); - assert_eq!(1, tree.blob_file_count()); - - tree.remove("b", 1); - tree.flush_active_memtable(0)?; - - tree.major_compact(u64::MAX, 1_000)?; - tree.major_compact(u64::MAX, 1_000)?; - assert_eq!(1, tree.blob_file_count()); - - { - let gc_stats = tree.current_version().gc_stats().clone(); - - assert_eq!(&lsm_tree::HashMap::default(), &*gc_stats); - } - - let value = tree.get("a", SeqNo::MAX)?.unwrap(); - assert_eq!(&*value, big_value); - } - - { - let tree = lsm_tree::Config::new( - path, - SequenceNumberCounter::default(), - SequenceNumberCounter::default(), - ) - .with_kv_separation(Some(Default::default())) - .open()?; - - let value = tree.get("a", SeqNo::MAX)?.unwrap(); - assert_eq!(&*value, big_value); - } - - Ok(()) -} - -#[test_log::test] -#[cfg(feature = "lz4")] -fn blob_tree_major_compact_relocation_recovery() -> lsm_tree::Result<()> { - use lsm_tree::{ - get_tmp_folder, AbstractTree, KvSeparationOptions, SeqNo, SequenceNumberCounter, - }; - - let folder = get_tmp_folder(); - let path = folder.path(); - - let big_value = b"neptune!".repeat(4_096); - - { - let tree = lsm_tree::Config::new( - path, - SequenceNumberCounter::default(), - SequenceNumberCounter::default(), - ) - .with_kv_separation(Some( - KvSeparationOptions::default() - .compression(lsm_tree::CompressionType::Lz4) - .separation_threshold(1) - .staleness_threshold(0.0000001) - .age_cutoff(1.0), - )) - .open()?; - - tree.insert("a", &big_value, 0); - tree.insert("b", b"smol", 0); - tree.flush_active_memtable(0)?; - assert_eq!(1, tree.table_count()); - assert_eq!(1, tree.blob_file_count()); - - tree.remove("b", 1); - tree.flush_active_memtable(0)?; - - tree.major_compact(u64::MAX, 1_000)?; - tree.major_compact(u64::MAX, 1_000)?; - assert_eq!(1, tree.blob_file_count()); - - { - let gc_stats = tree.current_version().gc_stats().clone(); - - assert_eq!(&lsm_tree::HashMap::default(), &*gc_stats); - } - - let value = tree.get("a", SeqNo::MAX)?.unwrap(); - assert_eq!(&*value, big_value); - } - - { - let tree = lsm_tree::Config::new( - path, - SequenceNumberCounter::default(), - SequenceNumberCounter::default(), - ) - .with_kv_separation(Some(Default::default())) - .open()?; - - let value = tree.get("a", SeqNo::MAX)?.unwrap(); - assert_eq!(&*value, big_value); - } - - Ok(()) -} diff --git a/crates/lsm-tree/tests/blob_nuke_gc_stats.rs b/crates/lsm-tree/tests/blob_nuke_gc_stats.rs deleted file mode 100644 index faecd5788..000000000 --- a/crates/lsm-tree/tests/blob_nuke_gc_stats.rs +++ /dev/null @@ -1,110 +0,0 @@ -use lsm_tree::{ - blob_tree::FragmentationEntry, get_tmp_folder, AbstractTree, KvSeparationOptions, SeqNo, - SequenceNumberCounter, -}; -use test_log::test; - -#[test] -fn blob_tree_nuke_gc_stats() -> lsm_tree::Result<()> { - let folder = get_tmp_folder(); - let path = folder.path(); - - let big_value = b"neptune!".repeat(128_000); - - { - let tree = lsm_tree::Config::new( - path, - SequenceNumberCounter::default(), - SequenceNumberCounter::default(), - ) - .with_kv_separation(Some( - KvSeparationOptions::default().compression(lsm_tree::CompressionType::None), - )) - .open()?; - - assert!(tree.get("big", SeqNo::MAX)?.is_none()); - tree.insert("big", &big_value, 0); - - let value = tree.get("big", SeqNo::MAX)?.expect("should exist"); - assert_eq!(&*value, big_value); - - tree.flush_active_memtable(0)?; - assert_eq!(1, tree.table_count()); - assert_eq!(1, tree.blob_file_count()); - - tree.drop_range::<&[u8], _>(..)?; - - // NOTE: Because the blob does not have any incoming references anymore - // it is pruned from the Version - assert_eq!(0, tree.blob_file_count()); - assert_eq!(0, tree.table_count()); - - let gc_stats = tree.current_version().gc_stats().clone(); - - // "big":0 was dropped - assert_eq!( - &{ - let mut map = lsm_tree::HashMap::default(); - let size = big_value.len() as u64; - map.insert(0, FragmentationEntry::new(1, size, size)); - map - }, - &*gc_stats, - ); - } - - Ok(()) -} - -#[test] -fn blob_tree_nuke_gc_stats_multi() -> lsm_tree::Result<()> { - let folder = get_tmp_folder(); - let path = folder.path(); - - let big_value = b"neptune!".repeat(128_000); - - { - let tree = lsm_tree::Config::new( - path, - SequenceNumberCounter::default(), - SequenceNumberCounter::default(), - ) - .with_kv_separation(Some( - KvSeparationOptions::default().compression(lsm_tree::CompressionType::None), - )) - .open()?; - - assert!(tree.get("big", SeqNo::MAX)?.is_none()); - tree.insert("big", &big_value, 0); - tree.insert("big", &big_value, 1); - - let value = tree.get("big", SeqNo::MAX)?.expect("should exist"); - assert_eq!(&*value, big_value); - - tree.flush_active_memtable(0)?; - assert_eq!(1, tree.table_count()); - assert_eq!(1, tree.blob_file_count()); - - tree.drop_range::<&[u8], _>(..)?; - - // NOTE: Because the blob does not have any incoming references anymore - // it is pruned from the Version - assert_eq!(0, tree.blob_file_count()); - assert_eq!(0, tree.table_count()); - - let gc_stats = tree.current_version().gc_stats().clone(); - - // "big":0, big":1 were dropped - assert_eq!( - &{ - let mut map = lsm_tree::HashMap::default(); - let size = big_value.len() as u64; - map.insert(0, FragmentationEntry::new(2, size * 2, size * 2)); - map - }, - &*gc_stats, - ); - } - - Ok(()) -} diff --git a/crates/lsm-tree/tests/blob_pinned_fd.rs b/crates/lsm-tree/tests/blob_pinned_fd.rs deleted file mode 100644 index d793fe0cd..000000000 --- a/crates/lsm-tree/tests/blob_pinned_fd.rs +++ /dev/null @@ -1,58 +0,0 @@ -use lsm_tree::{get_tmp_folder, AbstractTree, KvSeparationOptions, SeqNo, SequenceNumberCounter}; -use test_log::test; - -#[test] -fn blob_tree_with_pinned_fd() -> lsm_tree::Result<()> { - let folder = get_tmp_folder(); - let path = folder.path(); - - let big_value = b"neptune!".repeat(128_000); - - { - let tree = lsm_tree::Config::new( - path, - SequenceNumberCounter::default(), - SequenceNumberCounter::default(), - ) - .use_descriptor_table(None) - .with_kv_separation(Some( - KvSeparationOptions::default().compression(lsm_tree::CompressionType::None), - )) - .open()?; - - tree.insert("big", &big_value, 0); - tree.insert("smol", "small value", 0); - - let value = tree.get("big", SeqNo::MAX)?.expect("should exist"); - assert_eq!(&*value, big_value); - - let value = tree.get("smol", SeqNo::MAX)?.expect("should exist"); - assert_eq!(&*value, b"small value"); - - tree.flush_active_memtable(0)?; - assert_eq!(1, tree.table_count()); - assert_eq!(1, tree.blob_file_count()); - } - - { - let tree = lsm_tree::Config::new( - path, - SequenceNumberCounter::default(), - SequenceNumberCounter::default(), - ) - .use_descriptor_table(None) - .with_kv_separation(Some(Default::default())) - .open()?; - - let value = tree.get("big", SeqNo::MAX)?.expect("should exist"); - assert_eq!(&*value, big_value); - - let value = tree.get("smol", SeqNo::MAX)?.expect("should exist"); - assert_eq!(&*value, b"small value"); - - assert_eq!(1, tree.table_count()); - assert_eq!(1, tree.blob_file_count()); - } - - Ok(()) -} diff --git a/crates/lsm-tree/tests/blob_recover_gc_stats.rs b/crates/lsm-tree/tests/blob_recover_gc_stats.rs deleted file mode 100644 index 40b618569..000000000 --- a/crates/lsm-tree/tests/blob_recover_gc_stats.rs +++ /dev/null @@ -1,82 +0,0 @@ -use lsm_tree::{ - blob_tree::FragmentationEntry, get_tmp_folder, AbstractTree, KvSeparationOptions, SeqNo, - SequenceNumberCounter, -}; -use test_log::test; - -#[test] -fn blob_tree_recover_gc_stats() -> lsm_tree::Result<()> { - let folder = get_tmp_folder(); - let path = folder.path(); - - let big_value = b"neptune!".repeat(128_000); - let new_big_value = b"winter!".repeat(128_000); - - { - let tree = lsm_tree::Config::new( - path, - SequenceNumberCounter::default(), - SequenceNumberCounter::default(), - ) - .with_kv_separation(Some( - KvSeparationOptions::default().compression(lsm_tree::CompressionType::None), - )) - .open()?; - - assert!(tree.get("big", SeqNo::MAX)?.is_none()); - tree.insert("big", &big_value, 0); - tree.insert("smol", "small value", 0); - - let value = tree.get("big", SeqNo::MAX)?.expect("should exist"); - assert_eq!(&*value, big_value); - - tree.flush_active_memtable(0)?; - assert_eq!(1, tree.table_count()); - assert_eq!(1, tree.blob_file_count()); - - tree.insert("big", &new_big_value, 1); - - tree.flush_active_memtable(0)?; - assert_eq!(2, tree.blob_file_count()); - - tree.major_compact(64_000_000, 1_000)?; - - let gc_stats = tree.current_version().gc_stats().clone(); - - // "big":0 is expired - assert_eq!( - &{ - let mut map = lsm_tree::HashMap::default(); - let size = big_value.len() as u64; - map.insert(0, FragmentationEntry::new(1, size, size)); - map - }, - &*gc_stats, - ); - } - - { - let tree = lsm_tree::Config::new( - path, - SequenceNumberCounter::default(), - SequenceNumberCounter::default(), - ) - .with_kv_separation(Some(Default::default())) - .open()?; - - let gc_stats = tree.current_version().gc_stats().clone(); - - // "big":0 is still expired - assert_eq!( - &{ - let mut map = lsm_tree::HashMap::default(); - let size = big_value.len() as u64; - map.insert(0, FragmentationEntry::new(1, size, size)); - map - }, - &*gc_stats, - ); - } - - Ok(()) -} diff --git a/crates/lsm-tree/tests/blob_recovery.rs b/crates/lsm-tree/tests/blob_recovery.rs deleted file mode 100644 index 391f36446..000000000 --- a/crates/lsm-tree/tests/blob_recovery.rs +++ /dev/null @@ -1,49 +0,0 @@ -use lsm_tree::{get_tmp_folder, AbstractTree, SeqNo, SequenceNumberCounter}; -use test_log::test; - -#[test] -fn blob_tree_recovery() -> lsm_tree::Result<()> { - let folder = get_tmp_folder(); - let path = folder.path(); - - let big_value = b"neptune!".repeat(4_096); - - { - let tree = lsm_tree::Config::new( - path, - SequenceNumberCounter::default(), - SequenceNumberCounter::default(), - ) - .with_kv_separation(Some(Default::default())) - .open()?; - - tree.insert("big", &big_value, 0); - tree.flush_active_memtable(0)?; - - tree.insert("big", &big_value, 0); - tree.flush_active_memtable(0)?; - - tree.insert("big", &big_value, 0); - tree.flush_active_memtable(0)?; - - tree.insert("big", &big_value, 0); - tree.flush_active_memtable(0)?; - - tree.insert("big", &big_value, 0); - tree.flush_active_memtable(0)?; - } - - { - let tree = lsm_tree::Config::new( - path, - SequenceNumberCounter::default(), - SequenceNumberCounter::default(), - ) - .with_kv_separation(Some(Default::default())) - .open()?; - - assert!(!tree.is_empty(SeqNo::MAX, None)?); - } - - Ok(()) -} diff --git a/crates/lsm-tree/tests/blob_register_table_rotation.rs b/crates/lsm-tree/tests/blob_register_table_rotation.rs deleted file mode 100644 index 64d13fb97..000000000 --- a/crates/lsm-tree/tests/blob_register_table_rotation.rs +++ /dev/null @@ -1,135 +0,0 @@ -use lsm_tree::{ - config::BlockSizePolicy, get_tmp_folder, AbstractTree, KvSeparationOptions, - SequenceNumberCounter, -}; -use test_log::test; - -// Force one block per table and one blob per block -// -// Then check if item_count in a table matches the number of referenced blobs (so 1). -// -// See https://github.com/fjall-rs/lsm-tree/commit/0d2d7b2071c65f2538bb01e4512907892991dcbe -#[test] -fn blob_register_table_rotation() -> lsm_tree::Result<()> { - let folder = get_tmp_folder(); - let path = folder.path(); - - let tree = lsm_tree::Config::new( - path, - SequenceNumberCounter::default(), - SequenceNumberCounter::default(), - ) - .data_block_size_policy(BlockSizePolicy::all(1)) - .with_kv_separation(Some( - KvSeparationOptions::default() - .separation_threshold(0) - .age_cutoff(1.0) - .staleness_threshold(0.0), - )) - .open()?; - - tree.insert("a", "a", 0); - tree.insert("b", "b", 0); - tree.insert("c", "c", 0); - tree.insert("d", "d", 0); - tree.insert("e", "e", 0); - - tree.flush_active_memtable(0)?; - tree.major_compact(1, 0)?; - - assert_eq!(5, tree.table_count()); - assert_eq!(1, tree.blob_file_count()); - - for table in tree.current_version().iter_tables() { - assert_eq!( - 1, - table - .list_blob_file_references()? - .unwrap() - .iter() - .map(|x| x.len) - .sum::(), - ); - assert_eq!( - 1, - table - .list_blob_file_references()? - .unwrap() - .iter() - .map(|x| x.bytes) - .sum::(), - ); - } - - Ok(()) -} - -#[test] -fn blob_register_table_rotation_relocation() -> lsm_tree::Result<()> { - let folder = get_tmp_folder(); - let path = folder.path(); - - let tree = lsm_tree::Config::new( - path, - SequenceNumberCounter::default(), - SequenceNumberCounter::default(), - ) - .data_block_size_policy(BlockSizePolicy::all(1)) - .with_kv_separation(Some( - KvSeparationOptions::default() - .separation_threshold(0) - .age_cutoff(1.0) - .staleness_threshold(0.0), - )) - .open()?; - - tree.insert("a", "a", 0); - tree.insert("b", "b", 0); - tree.insert("c", "c", 0); - tree.insert("d", "d", 0); - tree.insert("e", "e", 0); - tree.insert("f", "f", 0); // f is not overwritten - - tree.flush_active_memtable(0)?; - tree.major_compact(1, 0)?; - - tree.insert("a", "a", 1); - tree.insert("b", "b", 1); - tree.insert("c", "c", 1); - tree.insert("d", "d", 1); - tree.insert("e", "e", 1); - - tree.flush_active_memtable(0)?; - tree.major_compact(1, 10)?; - - assert_eq!(6, tree.table_count()); - assert_eq!(2, tree.blob_file_count()); - - tree.major_compact(1, 11)?; - - assert_eq!(6, tree.table_count()); - assert_eq!(2, tree.blob_file_count()); - - for table in tree.current_version().iter_tables() { - assert_eq!( - 1, - table - .list_blob_file_references()? - .unwrap() - .iter() - .map(|x| x.len) - .sum::(), - ); - assert_eq!( - 1, - table - .list_blob_file_references()? - .unwrap() - .iter() - .map(|x| x.bytes) - .sum::(), - ); - } - - Ok(()) -} diff --git a/crates/lsm-tree/tests/blob_sep_threshold.rs b/crates/lsm-tree/tests/blob_sep_threshold.rs deleted file mode 100644 index 81e50b109..000000000 --- a/crates/lsm-tree/tests/blob_sep_threshold.rs +++ /dev/null @@ -1,30 +0,0 @@ -use lsm_tree::{get_tmp_folder, AbstractTree, KvSeparationOptions, SeqNo, SequenceNumberCounter}; -use test_log::test; - -#[test] -fn blob_tree_separation_threshold() -> lsm_tree::Result<()> { - let folder = get_tmp_folder(); - let path = folder.path(); - - let tree = lsm_tree::Config::new( - path, - SequenceNumberCounter::default(), - SequenceNumberCounter::default(), - ) - .with_kv_separation(Some( - KvSeparationOptions::default().separation_threshold(1_024), - )) - .open()?; - - tree.insert("a", "a".repeat(1_023), 0); - tree.flush_active_memtable(0)?; - assert_eq!(0, tree.blob_file_count()); - - tree.insert("b", "b".repeat(1_024), 0); - tree.flush_active_memtable(0)?; - assert_eq!(1, tree.blob_file_count()); - - assert_eq!(2, tree.len(SeqNo::MAX, None)?); - - Ok(()) -} diff --git a/crates/lsm-tree/tests/blob_simple.rs b/crates/lsm-tree/tests/blob_simple.rs deleted file mode 100644 index 852cb2873..000000000 --- a/crates/lsm-tree/tests/blob_simple.rs +++ /dev/null @@ -1,77 +0,0 @@ -use lsm_tree::{get_tmp_folder, AbstractTree, Guard, SeqNo, SequenceNumberCounter}; -use test_log::test; - -#[test] -fn blob_tree_simple_flush_read() -> lsm_tree::Result<()> { - let folder = get_tmp_folder(); - let path = folder.path(); - - let big_value = b"neptune!".repeat(128_000); - let new_big_value = b"winter!".repeat(128_000); - - { - let tree = lsm_tree::Config::new( - path, - SequenceNumberCounter::default(), - SequenceNumberCounter::default(), - ) - .with_kv_separation(Some(Default::default())) - .open()?; - - assert!(tree.get("big", SeqNo::MAX)?.is_none()); - tree.insert("big", &big_value, 0); - tree.insert("smol", "small value", 0); - - let value = tree.get("big", SeqNo::MAX)?.expect("should exist"); - assert_eq!(&*value, big_value); - assert_eq!( - big_value.len() as u32, - tree.size_of("big", SeqNo::MAX)?.unwrap(), - ); - - tree.flush_active_memtable(0)?; - - assert_eq!(1, tree.table_count()); - assert_eq!(1, tree.blob_file_count()); - - let value = tree.get("smol", SeqNo::MAX)?.expect("should exist"); - assert_eq!(&*value, b"small value"); - - let value = tree.get("big", SeqNo::MAX)?.expect("should exist"); - assert_eq!(&*value, big_value); - - tree.insert("big", &new_big_value, 1); - - let value = tree.get("big", SeqNo::MAX)?.expect("should exist"); - assert_eq!(&*value, new_big_value); - - tree.flush_active_memtable(0)?; - - let value = tree.get("big", SeqNo::MAX)?.expect("should exist"); - assert_eq!(&*value, new_big_value); - - let value = tree.get("big", 1)?.expect("should exist"); - assert_eq!(&*value, big_value); - - let g = tree.prefix("big", 1, None).next().expect("should exist"); - assert_eq!(big_value.len() as u32, g.size()?); - } - - { - let tree = lsm_tree::Config::new( - path, - SequenceNumberCounter::default(), - SequenceNumberCounter::default(), - ) - .with_kv_separation(Some(Default::default())) - .open()?; - - let value = tree.get("smol", SeqNo::MAX)?.expect("should exist"); - assert_eq!(&*value, b"small value"); - - let value = tree.get("big", SeqNo::MAX)?.expect("should exist"); - assert_eq!(&*value, new_big_value); - } - - Ok(()) -} diff --git a/crates/lsm-tree/tests/blob_tree_guarded_size.rs b/crates/lsm-tree/tests/blob_tree_guarded_size.rs deleted file mode 100644 index 4a5d80244..000000000 --- a/crates/lsm-tree/tests/blob_tree_guarded_size.rs +++ /dev/null @@ -1,25 +0,0 @@ -use lsm_tree::{get_tmp_folder, AbstractTree, Config, Guard, SeqNo, SequenceNumberCounter}; -use test_log::test; - -#[test] -fn blob_tree_guarded_size() -> lsm_tree::Result<()> { - let folder = get_tmp_folder(); - - let tree = Config::new( - &folder, - SequenceNumberCounter::default(), - SequenceNumberCounter::default(), - ) - .with_kv_separation(Some(Default::default())) - .open()?; - - tree.insert("a".as_bytes(), "abc", 0); - tree.insert("b".as_bytes(), "a".repeat(10_000), 0); - - assert_eq!( - 10_003u32, - tree.iter(SeqNo::MAX, None).flat_map(Guard::size).sum(), - ); - - Ok(()) -} diff --git a/crates/lsm-tree/tests/blob_tree_reload_blob.rs b/crates/lsm-tree/tests/blob_tree_reload_blob.rs deleted file mode 100644 index 56605295f..000000000 --- a/crates/lsm-tree/tests/blob_tree_reload_blob.rs +++ /dev/null @@ -1,144 +0,0 @@ -use lsm_tree::{ - get_tmp_folder, AbstractTree, Config, Guard, SeqNo, SequenceNumberCounter, TreeType, -}; -use test_log::test; - -const ITEM_COUNT: usize = 10_000; - -#[test] -fn blob_tree_reload_empty() -> lsm_tree::Result<()> { - let folder = get_tmp_folder(); - - { - let tree = Config::new( - &folder, - SequenceNumberCounter::default(), - SequenceNumberCounter::default(), - ) - .with_kv_separation(Some(Default::default())) - .open()?; - - assert_eq!(tree.len(SeqNo::MAX, None)?, 0); - assert_eq!(tree.iter(SeqNo::MAX, None).flat_map(|x| x.key()).count(), 0); - assert_eq!( - tree.iter(SeqNo::MAX, None) - .map(|x| x.key()) - .rev() - .flatten() - .count(), - 0 - ); - assert_eq!(tree.tree_type(), TreeType::Blob); - } - - { - let tree = Config::new( - &folder, - SequenceNumberCounter::default(), - SequenceNumberCounter::default(), - ) - .with_kv_separation(Some(Default::default())) - .open()?; - - assert_eq!(tree.len(SeqNo::MAX, None)?, 0); - assert_eq!(tree.iter(SeqNo::MAX, None).flat_map(|x| x.key()).count(), 0); - assert_eq!( - tree.iter(SeqNo::MAX, None) - .map(|x| x.key()) - .rev() - .flatten() - .count(), - 0 - ); - assert_eq!(tree.tree_type(), TreeType::Blob); - - tree.flush_active_memtable(0)?; - } - - { - let tree = Config::new( - &folder, - SequenceNumberCounter::default(), - SequenceNumberCounter::default(), - ) - .with_kv_separation(Some(Default::default())) - .open()?; - - assert_eq!(tree.len(SeqNo::MAX, None)?, 0); - assert_eq!(tree.iter(SeqNo::MAX, None).flat_map(|x| x.key()).count(), 0); - assert_eq!( - tree.iter(SeqNo::MAX, None) - .map(|x| x.key()) - .rev() - .flatten() - .count(), - 0 - ); - assert_eq!(tree.tree_type(), TreeType::Blob); - } - - Ok(()) -} - -#[test] -fn blob_tree_reload() -> lsm_tree::Result<()> { - let folder = get_tmp_folder(); - - let seqno = SequenceNumberCounter::default(); - - { - let tree = Config::new(&folder, seqno.clone(), SequenceNumberCounter::default()) - .with_kv_separation(Some(Default::default())) - .open()?; - - for x in 0..ITEM_COUNT as u64 { - let key = x.to_be_bytes(); - let value = nanoid::nanoid!(); - tree.insert(key, value.as_bytes(), seqno.next()); - } - - tree.flush_active_memtable(0)?; - - for x in 0..ITEM_COUNT as u64 { - let key: [u8; 8] = (x + ITEM_COUNT as u64).to_be_bytes(); - let value = nanoid::nanoid!(); - tree.insert(key, value.as_bytes(), seqno.next()); - } - - assert_eq!(tree.len(SeqNo::MAX, None)?, ITEM_COUNT * 2); - assert_eq!( - tree.iter(SeqNo::MAX, None).flat_map(|x| x.key()).count(), - ITEM_COUNT * 2 - ); - assert_eq!( - tree.iter(SeqNo::MAX, None) - .rev() - .flat_map(|x| x.key()) - .count(), - ITEM_COUNT * 2 - ); - - tree.flush_active_memtable(0)?; - } - - { - let tree = Config::new(&folder, seqno.clone(), SequenceNumberCounter::default()) - .with_kv_separation(Some(Default::default())) - .open()?; - - assert_eq!(tree.len(SeqNo::MAX, None)?, ITEM_COUNT * 2); - assert_eq!( - tree.iter(SeqNo::MAX, None).flat_map(|x| x.key()).count(), - ITEM_COUNT * 2 - ); - assert_eq!( - tree.iter(SeqNo::MAX, None) - .rev() - .flat_map(|x| x.key()) - .count(), - ITEM_COUNT * 2 - ); - } - - Ok(()) -} diff --git a/crates/lsm-tree/tests/compaction_filter.rs b/crates/lsm-tree/tests/compaction_filter.rs index 0b7fdf682..ad9c468dc 100644 --- a/crates/lsm-tree/tests/compaction_filter.rs +++ b/crates/lsm-tree/tests/compaction_filter.rs @@ -1,8 +1,8 @@ +use lsm_tree::compaction::PullDown; use lsm_tree::compaction::filter::{ CompactionFilter, Context as CompactionFilterContext, Factory, ItemAccessor, Verdict, }; -use lsm_tree::compaction::PullDown; -use lsm_tree::{get_tmp_folder, AbstractTree, KvSeparationOptions, SeqNo, SequenceNumberCounter}; +use lsm_tree::{AbstractTree, SeqNo, SequenceNumberCounter, get_tmp_folder}; use std::sync::{Arc, Mutex}; use test_log::test; @@ -11,14 +11,13 @@ fn u32_s(n: u32) -> [u8; 4] { } fn u32_f(mut buf: &[u8]) -> u32 { - use byteorder::{ReadBytesExt, BE}; + use byteorder::{BE, ReadBytesExt}; buf.read_u32::().unwrap() } -fn filter_basic(blob: bool) -> lsm_tree::Result<()> { +fn filter_basic() -> lsm_tree::Result<()> { struct FilterState { - expect_blob: bool, disable: bool, do_rewrite: bool, saw_value: bool, @@ -28,7 +27,6 @@ fn filter_basic(blob: bool) -> lsm_tree::Result<()> { } let filter_state = Arc::new(Mutex::new(FilterState { - expect_blob: true, disable: true, do_rewrite: false, saw_value: false, @@ -59,7 +57,6 @@ fn filter_basic(blob: bool) -> lsm_tree::Result<()> { } else if key >= 0xff000000 { let value = u32_f(&item.value()?); assert_eq!(key & 0xff, value); - assert_eq!(item.is_indirection(), state.expect_blob); state.saw_value = true; if !state.do_rewrite { @@ -108,11 +105,6 @@ fn filter_basic(blob: bool) -> lsm_tree::Result<()> { SequenceNumberCounter::default(), SequenceNumberCounter::default(), ) - .with_kv_separation(if blob { - Some(KvSeparationOptions::default().separation_threshold(2)) - } else { - None - }) .with_compaction_filter_factory(Some(Arc::new(FilterFactory(filter_state.clone())))); config.level_count = 3; let tree = config.open()?; @@ -136,7 +128,6 @@ fn filter_basic(blob: bool) -> lsm_tree::Result<()> { let mut state = filter_state.lock().unwrap(); state.disable = false; - state.expect_blob = blob; state.expect_last_level = Some(false); drop(state); @@ -189,11 +180,6 @@ fn filter_basic(blob: bool) -> lsm_tree::Result<()> { } #[test] -fn compaction_filter_with_blob() -> lsm_tree::Result<()> { - filter_basic(true) -} - -#[test] -fn compaction_filter_no_blob() -> lsm_tree::Result<()> { - filter_basic(false) +fn compaction_filter() -> lsm_tree::Result<()> { + filter_basic() } diff --git a/crates/lsm-tree/tests/compaction_filter_gc_stats_change.rs b/crates/lsm-tree/tests/compaction_filter_gc_stats_change.rs deleted file mode 100644 index d9a146c6c..000000000 --- a/crates/lsm-tree/tests/compaction_filter_gc_stats_change.rs +++ /dev/null @@ -1,263 +0,0 @@ -use lsm_tree::{ - blob_tree::FragmentationEntry, - compaction::filter::{ - CompactionFilter, Context as CompactionFilterContext, Factory, ItemAccessor, Verdict, - }, - get_tmp_folder, AbstractTree, KvSeparationOptions, SeqNo, SequenceNumberCounter, -}; -use std::sync::Arc; -use test_log::test; - -#[test] -fn compaction_filter_gc_stats_change() -> lsm_tree::Result<()> { - struct MyFilter; - - impl CompactionFilter for MyFilter { - fn filter_item( - &mut self, - item: ItemAccessor<'_>, - _ctx: &CompactionFilterContext, - ) -> lsm_tree::Result { - if item.key() == b"changethis" { - Ok(Verdict::ReplaceValue(b"winter2".repeat(128_000).into())) - } else { - Ok(Verdict::Keep) - } - } - } - - struct MyFilterFactory; - - impl Factory for MyFilterFactory { - fn name(&self) -> &str { - "Test" - } - - fn make_filter(&self, _ctx: &CompactionFilterContext) -> Box { - Box::new(MyFilter) - } - } - - let folder = get_tmp_folder(); - let path = folder.path(); - - let big_value = b"neptune!".repeat(128_000); - let other_big_value = b"asdasd".repeat(128_000); - - { - let tree = lsm_tree::Config::new( - path, - SequenceNumberCounter::default(), - SequenceNumberCounter::default(), - ) - .with_kv_separation(Some( - KvSeparationOptions::default().compression(lsm_tree::CompressionType::None), - )) - .with_compaction_filter_factory(Some(Arc::new(MyFilterFactory))) - .open()?; - - assert!(tree.get("big", SeqNo::MAX)?.is_none()); - tree.insert("big", &big_value, 0); - tree.insert("changethis", &other_big_value, 0); - - let value = tree.get("big", SeqNo::MAX)?.expect("should exist"); - assert_eq!(&*value, big_value); - - tree.flush_active_memtable(0)?; - assert_eq!(1, tree.table_count()); - assert_eq!(1, tree.blob_file_count()); - - tree.major_compact(64_000_000, 1_000)?; - assert_eq!(1, tree.table_count()); - // Because we are creating a new value, a new blob file is created - assert_eq!(2, tree.blob_file_count()); - - let value = tree.get("changethis", SeqNo::MAX)?.expect("should exist"); - assert_eq!(&*value, b"winter2".repeat(128_000)); - - let gc_stats = tree.current_version().gc_stats().clone(); - - // "changethis":0 was changed - assert_eq!( - &{ - let mut map = lsm_tree::HashMap::default(); - let size = other_big_value.len() as u64; - map.insert(0, FragmentationEntry::new(1, size, size)); - map - }, - &*gc_stats, - ); - assert_eq!(other_big_value.len() as u64, tree.stale_blob_bytes()); - } - - Ok(()) -} - -#[test] -fn compaction_filter_gc_stats_change_non_blob() -> lsm_tree::Result<()> { - struct MyFilter; - - impl CompactionFilter for MyFilter { - fn filter_item( - &mut self, - item: ItemAccessor<'_>, - _ctx: &CompactionFilterContext, - ) -> lsm_tree::Result { - if item.key() == b"changethis" { - Ok(Verdict::ReplaceValue(b"winter2".into())) - } else { - Ok(Verdict::Keep) - } - } - } - - struct MyFilterFactory; - - impl Factory for MyFilterFactory { - fn name(&self) -> &str { - "Test" - } - - fn make_filter(&self, _ctx: &CompactionFilterContext) -> Box { - Box::new(MyFilter) - } - } - - let folder = get_tmp_folder(); - let path = folder.path(); - - let big_value = b"neptune!".repeat(128_000); - let small_value = b"asdasd"; - - { - let tree = lsm_tree::Config::new( - path, - SequenceNumberCounter::default(), - SequenceNumberCounter::default(), - ) - .with_kv_separation(Some( - KvSeparationOptions::default().compression(lsm_tree::CompressionType::None), - )) - .with_compaction_filter_factory(Some(Arc::new(MyFilterFactory))) - .open()?; - - assert!(tree.get("big", SeqNo::MAX)?.is_none()); - tree.insert("big", &big_value, 0); - tree.insert("changethis", small_value, 0); - - let value = tree.get("big", SeqNo::MAX)?.expect("should exist"); - assert_eq!(&*value, big_value); - - tree.flush_active_memtable(0)?; - assert_eq!(1, tree.table_count()); - assert_eq!(1, tree.blob_file_count()); - - tree.major_compact(64_000_000, 1_000)?; - assert_eq!(1, tree.table_count()); - // Because we are creating a new value, but a small one, a new blob file is not created - assert_eq!(1, tree.blob_file_count()); - - let value = tree.get("changethis", SeqNo::MAX)?.expect("should exist"); - assert_eq!(&*value, b"winter2"); - - let gc_stats = tree.current_version().gc_stats().clone(); - - // "changethis":0 was changed - but not a blob - assert_eq!(&lsm_tree::HashMap::default(), &*gc_stats,); - assert_eq!(0, tree.stale_blob_bytes()); - } - - Ok(()) -} - -#[test] -fn compaction_filter_gc_stats_change_blob_writer_rotation() -> lsm_tree::Result<()> { - struct MyFilter; - - impl CompactionFilter for MyFilter { - fn filter_item( - &mut self, - item: ItemAccessor<'_>, - _ctx: &CompactionFilterContext, - ) -> lsm_tree::Result { - if item.key().starts_with(b"changethis") { - Ok(Verdict::ReplaceValue(b"winter2".repeat(128_000).into())) - } else { - Ok(Verdict::Keep) - } - } - } - - struct MyFilterFactory; - - impl Factory for MyFilterFactory { - fn name(&self) -> &str { - "Test" - } - - fn make_filter(&self, _ctx: &CompactionFilterContext) -> Box { - Box::new(MyFilter) - } - } - - let folder = get_tmp_folder(); - let path = folder.path(); - - let big_value = b"neptune!".repeat(128_000); - let other_big_value = b"asdasd".repeat(128_000); - - { - let tree = lsm_tree::Config::new( - path, - SequenceNumberCounter::default(), - SequenceNumberCounter::default(), - ) - .with_kv_separation(Some( - KvSeparationOptions::default() - .file_target_size(1) - .compression(lsm_tree::CompressionType::None), - )) - .with_compaction_filter_factory(Some(Arc::new(MyFilterFactory))) - .open()?; - - assert!(tree.get("big", SeqNo::MAX)?.is_none()); - tree.insert("big", &big_value, 0); - tree.insert("changethis", &other_big_value, 0); - tree.insert("changethis2", &other_big_value, 0); - - let value = tree.get("big", SeqNo::MAX)?.expect("should exist"); - assert_eq!(&*value, big_value); - - tree.flush_active_memtable(0)?; - assert_eq!(1, tree.table_count()); - assert_eq!(3, tree.blob_file_count()); - - tree.major_compact(64_000_000, 1_000)?; - assert_eq!(1, tree.table_count()); - // Because we are creating a new value, a new blob file is created - assert_eq!(5, tree.blob_file_count()); - - let value = tree.get("changethis", SeqNo::MAX)?.expect("should exist"); - assert_eq!(&*value, b"winter2".repeat(128_000)); - - let value = tree.get("changethis2", SeqNo::MAX)?.expect("should exist"); - assert_eq!(&*value, b"winter2".repeat(128_000)); - - let gc_stats = tree.current_version().gc_stats().clone(); - - // "changethis":0,"changethis2":0 were changed - assert_eq!( - &{ - let mut map = lsm_tree::HashMap::default(); - let size = other_big_value.len() as u64; - map.insert(1, FragmentationEntry::new(1, size, size)); - map.insert(2, FragmentationEntry::new(1, size, size)); - map - }, - &*gc_stats, - ); - assert_eq!(2 * other_big_value.len() as u64, tree.stale_blob_bytes()); - } - - Ok(()) -} diff --git a/crates/lsm-tree/tests/compaction_filter_gc_stats_destroy.rs b/crates/lsm-tree/tests/compaction_filter_gc_stats_destroy.rs deleted file mode 100644 index f9f5b42b7..000000000 --- a/crates/lsm-tree/tests/compaction_filter_gc_stats_destroy.rs +++ /dev/null @@ -1,90 +0,0 @@ -use lsm_tree::{ - blob_tree::FragmentationEntry, - compaction::filter::{ - CompactionFilter, Context as CompactionFilterContext, Factory, ItemAccessor, Verdict, - }, - get_tmp_folder, AbstractTree, KvSeparationOptions, SeqNo, SequenceNumberCounter, -}; -use std::sync::Arc; -use test_log::test; - -struct MyFilter; - -impl CompactionFilter for MyFilter { - fn filter_item( - &mut self, - item: ItemAccessor<'_>, - _ctx: &CompactionFilterContext, - ) -> lsm_tree::Result { - if item.key() == b"removethis" { - Ok(Verdict::Destroy) - } else { - Ok(Verdict::Keep) - } - } -} - -struct MyFilterFactory; - -impl Factory for MyFilterFactory { - fn name(&self) -> &str { - "Test" - } - - fn make_filter(&self, _ctx: &CompactionFilterContext) -> Box { - Box::new(MyFilter) - } -} - -#[test] -fn compaction_filter_gc_stats_destroy() -> lsm_tree::Result<()> { - let folder = get_tmp_folder(); - let path = folder.path(); - - let big_value = b"neptune!".repeat(128_000); - let other_big_value = b"winter!".repeat(128_000); - - { - let tree = lsm_tree::Config::new( - path, - SequenceNumberCounter::default(), - SequenceNumberCounter::default(), - ) - .with_kv_separation(Some( - KvSeparationOptions::default().compression(lsm_tree::CompressionType::None), - )) - .with_compaction_filter_factory(Some(Arc::new(MyFilterFactory))) - .open()?; - - assert!(tree.get("big", SeqNo::MAX)?.is_none()); - tree.insert("big", &big_value, 0); - tree.insert("removethis", &other_big_value, 0); - - let value = tree.get("big", SeqNo::MAX)?.expect("should exist"); - assert_eq!(&*value, big_value); - - tree.flush_active_memtable(0)?; - assert_eq!(1, tree.table_count()); - assert_eq!(1, tree.blob_file_count()); - - tree.major_compact(64_000_000, 1_000)?; - assert_eq!(1, tree.table_count()); - assert_eq!(1, tree.blob_file_count()); - - let gc_stats = tree.current_version().gc_stats().clone(); - - // "removethis":0 is dropped/destroyed - assert_eq!( - &{ - let mut map = lsm_tree::HashMap::default(); - let size = other_big_value.len() as u64; - map.insert(0, FragmentationEntry::new(1, size, size)); - map - }, - &*gc_stats, - ); - assert_eq!(other_big_value.len() as u64, tree.stale_blob_bytes()); - } - - Ok(()) -} diff --git a/crates/lsm-tree/tests/compaction_filter_gc_stats_remove.rs b/crates/lsm-tree/tests/compaction_filter_gc_stats_remove.rs deleted file mode 100644 index 3cd7b3962..000000000 --- a/crates/lsm-tree/tests/compaction_filter_gc_stats_remove.rs +++ /dev/null @@ -1,90 +0,0 @@ -use lsm_tree::{ - blob_tree::FragmentationEntry, - compaction::filter::{ - CompactionFilter, Context as CompactionFilterContext, Factory, ItemAccessor, Verdict, - }, - get_tmp_folder, AbstractTree, KvSeparationOptions, SeqNo, SequenceNumberCounter, -}; -use std::sync::Arc; -use test_log::test; - -struct MyFilter; - -impl CompactionFilter for MyFilter { - fn filter_item( - &mut self, - item: ItemAccessor<'_>, - _ctx: &CompactionFilterContext, - ) -> lsm_tree::Result { - if item.key() == b"removethis" { - Ok(Verdict::Remove) - } else { - Ok(Verdict::Keep) - } - } -} - -struct MyFilterFactory; - -impl Factory for MyFilterFactory { - fn name(&self) -> &str { - "Test" - } - - fn make_filter(&self, _ctx: &CompactionFilterContext) -> Box { - Box::new(MyFilter) - } -} - -#[test] -fn compaction_filter_gc_stats_remove() -> lsm_tree::Result<()> { - let folder = get_tmp_folder(); - let path = folder.path(); - - let big_value = b"neptune!".repeat(128_000); - let other_big_value = b"winter!".repeat(128_000); - - { - let tree = lsm_tree::Config::new( - path, - SequenceNumberCounter::default(), - SequenceNumberCounter::default(), - ) - .with_kv_separation(Some( - KvSeparationOptions::default().compression(lsm_tree::CompressionType::None), - )) - .with_compaction_filter_factory(Some(Arc::new(MyFilterFactory))) - .open()?; - - assert!(tree.get("big", SeqNo::MAX)?.is_none()); - tree.insert("big", &big_value, 0); - tree.insert("removethis", &other_big_value, 0); - - let value = tree.get("big", SeqNo::MAX)?.expect("should exist"); - assert_eq!(&*value, big_value); - - tree.flush_active_memtable(0)?; - assert_eq!(1, tree.table_count()); - assert_eq!(1, tree.blob_file_count()); - - tree.major_compact(64_000_000, 1_000)?; - assert_eq!(1, tree.table_count()); - assert_eq!(1, tree.blob_file_count()); - - let gc_stats = tree.current_version().gc_stats().clone(); - - // "removethis":0 is removed - assert_eq!( - &{ - let mut map = lsm_tree::HashMap::default(); - let size = other_big_value.len() as u64; - map.insert(0, FragmentationEntry::new(1, size, size)); - map - }, - &*gc_stats, - ); - assert_eq!(other_big_value.len() as u64, tree.stale_blob_bytes()); - } - - Ok(()) -} diff --git a/crates/lsm-tree/tests/compaction_filter_gc_stats_remove_weak.rs b/crates/lsm-tree/tests/compaction_filter_gc_stats_remove_weak.rs deleted file mode 100644 index 0f7749fcf..000000000 --- a/crates/lsm-tree/tests/compaction_filter_gc_stats_remove_weak.rs +++ /dev/null @@ -1,90 +0,0 @@ -use lsm_tree::{ - blob_tree::FragmentationEntry, - compaction::filter::{ - CompactionFilter, Context as CompactionFilterContext, Factory, ItemAccessor, Verdict, - }, - get_tmp_folder, AbstractTree, KvSeparationOptions, SeqNo, SequenceNumberCounter, -}; -use std::sync::Arc; -use test_log::test; - -struct MyFilter; - -impl CompactionFilter for MyFilter { - fn filter_item( - &mut self, - item: ItemAccessor<'_>, - _ctx: &CompactionFilterContext, - ) -> lsm_tree::Result { - if item.key() == b"removethis" { - Ok(Verdict::RemoveWeak) - } else { - Ok(Verdict::Keep) - } - } -} - -struct MyFilterFactory; - -impl Factory for MyFilterFactory { - fn name(&self) -> &str { - "Test" - } - - fn make_filter(&self, _ctx: &CompactionFilterContext) -> Box { - Box::new(MyFilter) - } -} - -#[test] -fn compaction_filter_gc_stats_remove_weak() -> lsm_tree::Result<()> { - let folder = get_tmp_folder(); - let path = folder.path(); - - let big_value = b"neptune!".repeat(128_000); - let other_big_value = b"winter!".repeat(128_000); - - { - let tree = lsm_tree::Config::new( - path, - SequenceNumberCounter::default(), - SequenceNumberCounter::default(), - ) - .with_kv_separation(Some( - KvSeparationOptions::default().compression(lsm_tree::CompressionType::None), - )) - .with_compaction_filter_factory(Some(Arc::new(MyFilterFactory))) - .open()?; - - assert!(tree.get("big", SeqNo::MAX)?.is_none()); - tree.insert("big", &big_value, 0); - tree.insert("removethis", &other_big_value, 0); - - let value = tree.get("big", SeqNo::MAX)?.expect("should exist"); - assert_eq!(&*value, big_value); - - tree.flush_active_memtable(0)?; - assert_eq!(1, tree.table_count()); - assert_eq!(1, tree.blob_file_count()); - - tree.major_compact(64_000_000, 1_000)?; - assert_eq!(1, tree.table_count()); - assert_eq!(1, tree.blob_file_count()); - - let gc_stats = tree.current_version().gc_stats().clone(); - - // "removethis":0 is removed - assert_eq!( - &{ - let mut map = lsm_tree::HashMap::default(); - let size = other_big_value.len() as u64; - map.insert(0, FragmentationEntry::new(1, size, size)); - map - }, - &*gc_stats, - ); - assert_eq!(other_big_value.len() as u64, tree.stale_blob_bytes()); - } - - Ok(()) -} diff --git a/crates/lsm-tree/tests/compaction_filter_snapshot.rs b/crates/lsm-tree/tests/compaction_filter_snapshot.rs index d93d1f758..0944518ac 100644 --- a/crates/lsm-tree/tests/compaction_filter_snapshot.rs +++ b/crates/lsm-tree/tests/compaction_filter_snapshot.rs @@ -1,7 +1,7 @@ use lsm_tree::compaction::filter::{ CompactionFilter, Context as CompactionFilterContext, Factory, ItemAccessor, Verdict, }; -use lsm_tree::{get_tmp_folder, AbstractTree, SeqNo, SequenceNumberCounter}; +use lsm_tree::{AbstractTree, SeqNo, SequenceNumberCounter, get_tmp_folder}; use std::sync::Arc; use test_log::test; diff --git a/crates/lsm-tree/tests/compaction_filter_ttl.rs b/crates/lsm-tree/tests/compaction_filter_ttl.rs index cc2ac755d..80b0df7cb 100644 --- a/crates/lsm-tree/tests/compaction_filter_ttl.rs +++ b/crates/lsm-tree/tests/compaction_filter_ttl.rs @@ -1,12 +1,12 @@ use lsm_tree::{ + AbstractTree, Config, compaction::filter::{ CompactionFilter, Context as CompactionFilterContext, Factory, ItemAccessor, Verdict, }, - AbstractTree, Config, }; use std::sync::{ - atomic::{AtomicU64, Ordering::Relaxed}, Arc, + atomic::{AtomicU64, Ordering::Relaxed}, }; use test_log::test; diff --git a/crates/lsm-tree/tests/compaction_readers_grouping.rs b/crates/lsm-tree/tests/compaction_readers_grouping.rs index 2fc000bda..bb9cc0f58 100644 --- a/crates/lsm-tree/tests/compaction_readers_grouping.rs +++ b/crates/lsm-tree/tests/compaction_readers_grouping.rs @@ -1,4 +1,4 @@ -use lsm_tree::{get_tmp_folder, AbstractTree, Config, SeqNo, SequenceNumberCounter}; +use lsm_tree::{AbstractTree, Config, SeqNo, SequenceNumberCounter, get_tmp_folder}; use std::sync::Arc; use test_log::test; @@ -36,29 +36,35 @@ fn compaction_readers_grouping() -> lsm_tree::Result<()> { // breaking this tree.compact(Arc::new(lsm_tree::compaction::PullDown(2, 3)), 0)?; - assert!(!tree - .current_version() - .level(0) - .expect("level should exist") - .is_empty()); + assert!( + !tree + .current_version() + .level(0) + .expect("level should exist") + .is_empty() + ); - assert!(tree - .current_version() - .level(1) - .expect("level should exist") - .is_empty()); + assert!( + tree.current_version() + .level(1) + .expect("level should exist") + .is_empty() + ); - assert!(tree - .current_version() - .level(2) - .expect("level should exist") - .is_empty()); + assert!( + tree.current_version() + .level(2) + .expect("level should exist") + .is_empty() + ); - assert!(!tree - .current_version() - .level(3) - .expect("level should exist") - .is_empty()); + assert!( + !tree + .current_version() + .level(3) + .expect("level should exist") + .is_empty() + ); Ok(()) } diff --git a/crates/lsm-tree/tests/dirty_snapshot.rs b/crates/lsm-tree/tests/dirty_snapshot.rs deleted file mode 100644 index 018318eab..000000000 --- a/crates/lsm-tree/tests/dirty_snapshot.rs +++ /dev/null @@ -1,24 +0,0 @@ -use lsm_tree::{get_tmp_folder, AbstractTree, Config, SequenceNumberCounter}; -use test_log::test; - -#[test] -fn dirty_snapshot_after_drop_range() -> lsm_tree::Result<()> { - let dir = get_tmp_folder(); - - let seqno = SequenceNumberCounter::default(); - - let tree = Config::new(dir.path(), seqno.clone(), SequenceNumberCounter::default()).open()?; - - tree.insert("a", "a", seqno.next()); - tree.flush_active_memtable(0)?; - tree.insert("b", "b", seqno.next()); - tree.flush_active_memtable(0)?; - - let snapshot_seqno = seqno.get(); - assert_eq!(b"a", &*tree.get("a", snapshot_seqno)?.unwrap()); - - tree.drop_range("a"..="a")?; - assert_eq!(b"a", &*tree.get("a", snapshot_seqno)?.unwrap()); - - Ok(()) -} diff --git a/crates/lsm-tree/tests/expect_point_read_hits.rs b/crates/lsm-tree/tests/expect_point_read_hits.rs index f5e63ee5f..f1f68b430 100644 --- a/crates/lsm-tree/tests/expect_point_read_hits.rs +++ b/crates/lsm-tree/tests/expect_point_read_hits.rs @@ -1,4 +1,4 @@ -use lsm_tree::{get_tmp_folder, AbstractTree, Config, SequenceNumberCounter}; +use lsm_tree::{AbstractTree, Config, SequenceNumberCounter, get_tmp_folder}; use test_log::test; #[test] diff --git a/crates/lsm-tree/tests/fifo_ttl.rs b/crates/lsm-tree/tests/fifo_ttl.rs deleted file mode 100644 index 7b3e4533e..000000000 --- a/crates/lsm-tree/tests/fifo_ttl.rs +++ /dev/null @@ -1,134 +0,0 @@ -use lsm_tree::{ - compaction::Fifo, get_tmp_folder, AbstractTree, Config, KvSeparationOptions, - SequenceNumberCounter, -}; -use std::sync::Arc; - -#[test] -fn fifo_ttl_no_drop_when_recent_or_disabled() -> lsm_tree::Result<()> { - let dir = get_tmp_folder(); - let tree = Config::new( - dir.path(), - SequenceNumberCounter::default(), - SequenceNumberCounter::default(), - ) - .open()?; - - // Two quick tables (both recent) - tree.insert("a", "1", 0); - tree.flush_active_memtable(0)?; - tree.insert("b", "2", 1); - tree.flush_active_memtable(1)?; - - assert_eq!(2, tree.table_count()); - - // TTL enabled but not yet expired - let fifo_recent = Arc::new(Fifo::new(u64::MAX, Some(15))); - tree.compact(fifo_recent, 2)?; - assert_eq!(2, tree.table_count()); - - // TTL disabled explicitly - let fifo_disabled = Arc::new(Fifo::new(u64::MAX, None)); - tree.compact(fifo_disabled, 2)?; - assert_eq!(2, tree.table_count()); - - Ok(()) -} - -#[test] -fn fifo_below_limit_no_drop_standard() -> lsm_tree::Result<()> { - let dir = get_tmp_folder(); - let tree = Config::new( - dir.path(), - SequenceNumberCounter::default(), - SequenceNumberCounter::default(), - ) - .open()?; - - for i in 0..3u8 { - tree.insert([b'k', i].as_slice(), "v", i as u64); - tree.flush_active_memtable(i as u64)?; - } - - let fifo = Arc::new(Fifo::new(u64::MAX, None)); - tree.compact(fifo, 3)?; - - assert_eq!(3, tree.table_count()); - - Ok(()) -} - -#[test] -fn fifo_limit_considers_blob_bytes() -> lsm_tree::Result<()> { - let dir = get_tmp_folder(); - let tree = Config::new( - dir.path(), - SequenceNumberCounter::default(), - SequenceNumberCounter::default(), - ) - .with_kv_separation(Some(KvSeparationOptions::default().separation_threshold(1))) - .open()?; - - // Create multiple small tables referencing blob files - for i in 0..3u8 { - tree.insert([b'k', i].as_slice(), "$", i as u64); // value goes to blob - tree.flush_active_memtable(i as u64)?; - } - - let before = tree.table_count(); - assert_eq!(3, before); - - // Very small limit forces dropping based on (table + blob) size - let fifo = Arc::new(Fifo::new(10, None)); - tree.compact(fifo, 3)?; - - // Should have dropped at least one table due to blob bytes pressure - assert!(tree.table_count() < before); - - Ok(()) -} - -#[test] -fn fifo_compact_empty_tree_noop() -> lsm_tree::Result<()> { - let dir = get_tmp_folder(); - let tree = Config::new( - dir.path(), - SequenceNumberCounter::default(), - SequenceNumberCounter::default(), - ) - .open()?; - - let fifo = Arc::new(Fifo::new(1_000_000, Some(1))); - tree.compact(fifo, 0)?; - - assert_eq!(0, tree.table_count()); - - Ok(()) -} - -#[test] -fn fifo_idempotent_when_within_limits() -> lsm_tree::Result<()> { - let dir = get_tmp_folder(); - let tree = Config::new( - dir.path(), - SequenceNumberCounter::default(), - SequenceNumberCounter::default(), - ) - .open()?; - - for i in 0..3u8 { - tree.insert([b'k', i].as_slice(), "v", i as u64); - tree.flush_active_memtable(i as u64)?; - } - - let fifo = Arc::new(Fifo::new(u64::MAX, None)); - tree.compact(fifo.clone(), 3)?; - let after_first = tree.table_count(); - - tree.compact(fifo, 3)?; - let after_second = tree.table_count(); - - assert_eq!(after_first, after_second); - - Ok(()) -} diff --git a/crates/lsm-tree/tests/format_v5.rs b/crates/lsm-tree/tests/format_v5.rs new file mode 100644 index 000000000..05d9375d5 --- /dev/null +++ b/crates/lsm-tree/tests/format_v5.rs @@ -0,0 +1,92 @@ +use lsm_tree::{AbstractTree, Config, Error, SequenceNumberCounter, get_tmp_folder}; +use std::{ + fs::{File, OpenOptions}, + io::{Read, Seek, Write}, +}; + +#[test] +fn v4_manifest_is_rejected_before_table_recovery() -> lsm_tree::Result<()> { + let folder = get_tmp_folder(); + + { + let _tree = Config::new( + folder.path(), + SequenceNumberCounter::default(), + SequenceNumberCounter::default(), + ) + .open()?; + } + + let mut current = File::open(folder.path().join("current"))?; + let mut version_bytes = [0; size_of::()]; + current.read_exact(&mut version_bytes)?; + let manifest_path = folder + .path() + .join(format!("v{}", u64::from_le_bytes(version_bytes))); + + let reader = sfa::Reader::new(&manifest_path)?; + let version_offset = reader + .toc() + .section(b"format_version") + .expect("format version should exist") + .pos(); + + let mut manifest = OpenOptions::new().write(true).open(manifest_path)?; + manifest.seek(std::io::SeekFrom::Start(version_offset))?; + manifest.write_all(&[4])?; + drop(manifest); + + assert!(matches!( + Config::new( + folder.path(), + SequenceNumberCounter::default(), + SequenceNumberCounter::default(), + ) + .open(), + Err(Error::InvalidVersion(4)), + )); + Ok(()) +} + +#[test] +fn v4_table_is_rejected_before_metadata_recovery() -> lsm_tree::Result<()> { + let folder = get_tmp_folder(); + + { + let tree = Config::new( + folder.path(), + SequenceNumberCounter::default(), + SequenceNumberCounter::default(), + ) + .open()?; + tree.insert("key", "value", 0); + tree.flush_active_memtable(0)?; + } + + let table_path = std::fs::read_dir(folder.path().join("tables"))? + .next() + .expect("table should exist")? + .path(); + let reader = sfa::Reader::new(&table_path)?; + let version_offset = reader + .toc() + .section(b"table_version") + .expect("table version should exist") + .pos(); + + let mut table = OpenOptions::new().write(true).open(table_path)?; + table.seek(std::io::SeekFrom::Start(version_offset))?; + table.write_all(&[4])?; + drop(table); + + assert!(matches!( + Config::new( + folder.path(), + SequenceNumberCounter::default(), + SequenceNumberCounter::default(), + ) + .open(), + Err(Error::InvalidVersion(4)), + )); + Ok(()) +} diff --git a/crates/lsm-tree/tests/guard_if.rs b/crates/lsm-tree/tests/guard_if.rs index 6072d5d0f..1f31fdf31 100644 --- a/crates/lsm-tree/tests/guard_if.rs +++ b/crates/lsm-tree/tests/guard_if.rs @@ -1,6 +1,4 @@ -use lsm_tree::{ - get_tmp_folder, AbstractTree, Config, Guard, KvSeparationOptions, SeqNo, SequenceNumberCounter, -}; +use lsm_tree::{AbstractTree, Config, Guard, SeqNo, SequenceNumberCounter, get_tmp_folder}; use test_log::test; #[test] @@ -35,37 +33,3 @@ fn guard_into_inner_if() -> lsm_tree::Result<()> { Ok(()) } - -#[test] -fn guard_into_inner_if_blob() -> lsm_tree::Result<()> { - let folder = get_tmp_folder(); - - { - let tree = Config::new( - &folder, - SequenceNumberCounter::default(), - SequenceNumberCounter::default(), - ) - .with_kv_separation(Some(KvSeparationOptions::default().separation_threshold(1))) - .open()?; - - tree.insert("earth#name", "earth", 0); - tree.insert("earth#color", "BLUE", 0); - - assert_eq!(2, tree.iter(SeqNo::MAX, None).count()); - - assert_eq!( - 1, - tree.iter(SeqNo::MAX, None) - .filter_map(|guard| { - guard - .into_inner_if(|key| key.ends_with(b"#name")) - .unwrap() - .1 - }) - .count(), - ); - } - - Ok(()) -} diff --git a/crates/lsm-tree/tests/ingest_dirty_snapshot.rs b/crates/lsm-tree/tests/ingest_dirty_snapshot.rs index 6bf1a6d7d..9eeb37cfb 100644 --- a/crates/lsm-tree/tests/ingest_dirty_snapshot.rs +++ b/crates/lsm-tree/tests/ingest_dirty_snapshot.rs @@ -1,4 +1,4 @@ -use lsm_tree::{get_tmp_folder, AbstractTree, Config, SequenceNumberCounter}; +use lsm_tree::{AbstractTree, Config, SequenceNumberCounter, get_tmp_folder}; #[test] fn ingestion_dirty_snapshot() -> lsm_tree::Result<()> { diff --git a/crates/lsm-tree/tests/ingestion_api.rs b/crates/lsm-tree/tests/ingestion_api.rs index 27ba70d1b..6bce63d70 100644 --- a/crates/lsm-tree/tests/ingestion_api.rs +++ b/crates/lsm-tree/tests/ingestion_api.rs @@ -1,6 +1,4 @@ -use lsm_tree::{ - get_tmp_folder, AbstractTree, Config, KvSeparationOptions, SeqNo, SequenceNumberCounter, -}; +use lsm_tree::{AbstractTree, Config, SeqNo, SequenceNumberCounter, get_tmp_folder}; #[test] fn tree_ingestion_tombstones_delete_existing_keys() -> lsm_tree::Result<()> { @@ -147,37 +145,6 @@ fn ingestion_enforces_order_standard_panics() { // Panics here let _ = ingest.write(b"k1", b"v"); } - -#[test] -fn blob_ingestion_out_of_order_panics_without_blob_write() -> lsm_tree::Result<()> { - let folder = get_tmp_folder(); - - let tree = lsm_tree::Config::new( - &folder, - SequenceNumberCounter::default(), - SequenceNumberCounter::default(), - ) - .with_kv_separation(Some(KvSeparationOptions::default().separation_threshold(8))) - .open()?; - - let before = tree.blob_file_count(); - - // Use a small value for the first write to avoid blob I/O - let result = std::panic::catch_unwind(|| { - let mut ingest = tree.ingestion().unwrap(); - ingest.write(b"k2", b"x").unwrap(); - - // Second write would require blob I/O, but ordering check should fire before any blob write - let _ = ingest.write(b"k1", [1u8; 16]); - }); - assert!(result.is_err()); - - let after = tree.blob_file_count(); - assert_eq!(before, after); - - Ok(()) -} - #[test] fn memtable_put_overrides_table_tombstone() -> lsm_tree::Result<()> { use lsm_tree::AbstractTree; @@ -212,40 +179,6 @@ fn memtable_put_overrides_table_tombstone() -> lsm_tree::Result<()> { ); Ok(()) } - -#[test] -fn blob_tree_ingestion_tombstones_delete_existing_keys() -> lsm_tree::Result<()> { - let folder = get_tmp_folder(); - - let tree = Config::new( - &folder, - SequenceNumberCounter::default(), - SequenceNumberCounter::default(), - ) - .with_kv_separation(Some(KvSeparationOptions::default().separation_threshold(1))) - .open()?; - - for i in 0..8u32 { - let key = format!("b{:03}", i); - tree.insert(key.as_bytes(), b"x", 0); - } - - let mut ingest = tree.ingestion()?; - for i in 0..8u32 { - let key = format!("b{:03}", i); - ingest.write_tombstone(key)?; - } - ingest.finish()?; - - for i in 0..8u32 { - let key = format!("b{:03}", i); - assert!(tree.get(key.as_bytes(), SeqNo::MAX)?.is_none()); - } - assert_eq!(tree.tombstone_count(), 8); - - Ok(()) -} - #[test] fn tree_ingestion_finish_no_writes_noop() -> lsm_tree::Result<()> { let folder = get_tmp_folder(); @@ -266,103 +199,3 @@ fn tree_ingestion_finish_no_writes_noop() -> lsm_tree::Result<()> { Ok(()) } - -#[test] -fn blob_ingestion_only_tombstones_does_not_create_blob_files() -> lsm_tree::Result<()> { - let folder = get_tmp_folder(); - - let tree = Config::new( - &folder, - SequenceNumberCounter::default(), - SequenceNumberCounter::default(), - ) - .with_kv_separation(Some(KvSeparationOptions::default().separation_threshold(1))) - .open()?; - - for i in 0..5u32 { - let key = format!("d{:03}", i); - tree.insert(key.as_bytes(), b"value", 0); - } - - let before_blobs = tree.blob_file_count(); - - let mut ingest = tree.ingestion()?; - for i in 0..5u32 { - let key = format!("d{:03}", i); - ingest.write_tombstone(key)?; - } - ingest.finish()?; - - let after_blobs = tree.blob_file_count(); - assert_eq!(before_blobs, after_blobs); - - for i in 0..5u32 { - let key = format!("d{:03}", i); - assert!(tree.get(key.as_bytes(), SeqNo::MAX)?.is_none()); - } - - Ok(()) -} - -#[test] -fn blob_ingestion_finish_no_writes_noop() -> lsm_tree::Result<()> { - let folder = get_tmp_folder(); - - let tree = Config::new( - &folder, - SequenceNumberCounter::default(), - SequenceNumberCounter::default(), - ) - .with_kv_separation(Some(KvSeparationOptions::default().separation_threshold(1))) - .open()?; - - let before_tables = tree.table_count(); - let before_blobs = tree.blob_file_count(); - - tree.ingestion()?.finish()?; - - let after_tables = tree.table_count(); - let after_blobs = tree.blob_file_count(); - - assert_eq!(before_tables, after_tables); - assert_eq!(before_blobs, after_blobs); - assert!(tree.is_empty(SeqNo::MAX, None)?); - - Ok(()) -} - -#[test] -fn blob_ingestion_separates_large_values_and_reads_ok() -> lsm_tree::Result<()> { - let folder = get_tmp_folder(); - - let tree = Config::new( - &folder, - SequenceNumberCounter::default(), - SequenceNumberCounter::default(), - ) - .with_kv_separation(Some(KvSeparationOptions::default().separation_threshold(8))) - .open()?; - - let mut ingest = tree.ingestion()?; - ingest.write("k_big1", [1u8; 16])?; - ingest.write("k_big2", [2u8; 32])?; - ingest.write("k_small", "abc")?; - ingest.finish()?; - - assert!(tree.blob_file_count() >= 1); - - assert_eq!( - tree.get("k_small", SeqNo::MAX)?, - Some(b"abc".as_slice().into()) - ); - assert_eq!( - tree.get("k_big1", SeqNo::MAX)?.as_deref().map(|s| s.len()), - Some(16) - ); - assert_eq!( - tree.get("k_big2", SeqNo::MAX)?.as_deref().map(|s| s.len()), - Some(32) - ); - - Ok(()) -} diff --git a/crates/lsm-tree/tests/ingestion_invariants.rs b/crates/lsm-tree/tests/ingestion_invariants.rs index 1c9a94127..d7204a940 100644 --- a/crates/lsm-tree/tests/ingestion_invariants.rs +++ b/crates/lsm-tree/tests/ingestion_invariants.rs @@ -1,6 +1,4 @@ -use lsm_tree::{ - get_tmp_folder, AbstractTree, Config, KvSeparationOptions, SeqNo, SequenceNumberCounter, -}; +use lsm_tree::{AbstractTree, Config, SeqNo, SequenceNumberCounter, get_tmp_folder}; use std::sync::mpsc; use std::thread; use std::time::Duration; @@ -129,53 +127,3 @@ fn ingestion_blocks_memtable_writes_until_finish() -> lsm_tree::Result<()> { Ok(()) } - -#[test] -fn blob_ingestion_honors_invariants_and_blocks_writes() -> lsm_tree::Result<()> { - let folder = get_tmp_folder(); - - let tree = Config::new( - &folder, - SequenceNumberCounter::default(), - SequenceNumberCounter::default(), - ) - .with_kv_separation(Some(KvSeparationOptions::default().separation_threshold(1))) - .open()?; - - // Write small values into memtable and then start blob ingestion - for i in 0..4u32 { - let k = format!("b{:03}", i); - tree.insert(k.as_bytes(), b"y", 1); - } - - let (started_tx, started_rx) = mpsc::channel(); - let (done_tx, done_rx) = mpsc::channel(); - let tree2 = tree.clone(); - - let ingest = tree.ingestion()?; - - let handle = thread::spawn(move || { - started_tx.send(()).ok(); - tree2.insert(b"b999", b"z", 31); - done_tx.send(()).ok(); - }); - - started_rx.recv().unwrap(); - thread::sleep(Duration::from_millis(100)); - assert!(done_rx.try_recv().is_ok()); - - handle.join().unwrap(); - ingest.finish()?; - - // Data visible after ingestion, including concurrent write - for i in 0..4u32 { - let k = format!("b{:03}", i); - assert_eq!( - tree.get(k.as_bytes(), SeqNo::MAX)?, - Some(b"y".as_slice().into()) - ); - } - assert_eq!(tree.get(b"b999", SeqNo::MAX)?, Some(b"z".as_slice().into())); - - Ok(()) -} diff --git a/crates/lsm-tree/tests/ingestion_seqno.rs b/crates/lsm-tree/tests/ingestion_seqno.rs index 105965ed9..28ea7077b 100644 --- a/crates/lsm-tree/tests/ingestion_seqno.rs +++ b/crates/lsm-tree/tests/ingestion_seqno.rs @@ -1,4 +1,4 @@ -use lsm_tree::{get_tmp_folder, AbstractTree, Config, SequenceNumberCounter}; +use lsm_tree::{AbstractTree, Config, SequenceNumberCounter, get_tmp_folder}; #[test] fn ingestion_persisted_seqno() -> lsm_tree::Result<()> { diff --git a/crates/lsm-tree/tests/latest_snapshot.rs b/crates/lsm-tree/tests/latest_snapshot.rs index 2ba104784..40d6f8d6e 100644 --- a/crates/lsm-tree/tests/latest_snapshot.rs +++ b/crates/lsm-tree/tests/latest_snapshot.rs @@ -1,4 +1,4 @@ -use lsm_tree::{get_tmp_folder, AbstractTree, Config, SequenceNumberCounter}; +use lsm_tree::{AbstractTree, Config, SequenceNumberCounter, get_tmp_folder}; use test_log::test; #[test] diff --git a/crates/lsm-tree/tests/leveled_trivial_move.rs b/crates/lsm-tree/tests/leveled_trivial_move.rs index 008c33ed5..cb264ba95 100644 --- a/crates/lsm-tree/tests/leveled_trivial_move.rs +++ b/crates/lsm-tree/tests/leveled_trivial_move.rs @@ -1,4 +1,4 @@ -use lsm_tree::{get_tmp_folder, AbstractTree, Config, SequenceNumberCounter}; +use lsm_tree::{AbstractTree, Config, SequenceNumberCounter, get_tmp_folder}; use std::sync::Arc; use test_log::test; diff --git a/crates/lsm-tree/tests/memtable_id.rs b/crates/lsm-tree/tests/memtable_id.rs index a6aa670ea..93852564f 100644 --- a/crates/lsm-tree/tests/memtable_id.rs +++ b/crates/lsm-tree/tests/memtable_id.rs @@ -1,4 +1,4 @@ -use lsm_tree::{get_tmp_folder, AbstractTree, Config, SequenceNumberCounter}; +use lsm_tree::{AbstractTree, Config, SequenceNumberCounter, get_tmp_folder}; use test_log::test; // https://github.com/fjall-rs/lsm-tree/issues/230 diff --git a/crates/lsm-tree/tests/model_1.rs b/crates/lsm-tree/tests/model_1.rs index 89108251e..1bdafa461 100644 --- a/crates/lsm-tree/tests/model_1.rs +++ b/crates/lsm-tree/tests/model_1.rs @@ -1,6 +1,6 @@ // Found by model testing -use lsm_tree::{get_tmp_folder, AbstractTree, Result, SequenceNumberCounter}; +use lsm_tree::{AbstractTree, Result, SequenceNumberCounter, get_tmp_folder}; use std::sync::Arc; use test_log::test; diff --git a/crates/lsm-tree/tests/model_2.rs b/crates/lsm-tree/tests/model_2.rs deleted file mode 100644 index 1c84296c9..000000000 --- a/crates/lsm-tree/tests/model_2.rs +++ /dev/null @@ -1,48 +0,0 @@ -// Found by model testing - -use lsm_tree::{get_tmp_folder, AbstractTree, KvSeparationOptions, Result, SequenceNumberCounter}; -use std::sync::Arc; -use test_log::test; - -#[test] -fn model_2() -> Result<()> { - let folder = get_tmp_folder(); - - let path = folder.path(); - - let tree = lsm_tree::Config::new( - path, - SequenceNumberCounter::default(), - SequenceNumberCounter::default(), - ) - .with_kv_separation(Some(KvSeparationOptions::default().separation_threshold(5))) - .open()?; - let compaction = Arc::new(lsm_tree::compaction::Leveled::default()); - - let value = b"hellohello"; - - tree.insert("a", value, 3); - tree.flush_active_memtable(0)?; - tree.compact(compaction.clone(), 0)?; - assert_eq!(1, tree.table_count()); - assert_eq!(1, tree.blob_file_count()); - - tree.insert("b", value, 4); - tree.flush_active_memtable(0)?; - tree.compact(compaction.clone(), 0)?; - assert_eq!(2, tree.table_count()); - assert_eq!(2, tree.blob_file_count()); - - tree.insert("a", value, 5); - tree.flush_active_memtable(0)?; - tree.compact(compaction.clone(), 0)?; - assert_eq!(3, tree.table_count()); - assert_eq!(3, tree.blob_file_count()); - - tree.drop_range::<&[u8], _>(..)?; - - assert_eq!(0, tree.table_count()); - assert_eq!(0, tree.blob_file_count()); - - Ok(()) -} diff --git a/crates/lsm-tree/tests/model_3.rs b/crates/lsm-tree/tests/model_3.rs deleted file mode 100644 index df6d8c55b..000000000 --- a/crates/lsm-tree/tests/model_3.rs +++ /dev/null @@ -1,70 +0,0 @@ -// Found by model testing - -use lsm_tree::{get_tmp_folder, AbstractTree, KvSeparationOptions, Result, SequenceNumberCounter}; -use std::sync::Arc; -use test_log::test; - -#[test] -fn model_3() -> Result<()> { - let folder = get_tmp_folder(); - - let path = folder.path(); - - let tree = lsm_tree::Config::new( - path, - SequenceNumberCounter::default(), - SequenceNumberCounter::default(), - ) - .with_kv_separation(Some(KvSeparationOptions::default().separation_threshold(5))) - .open()?; - let compaction = Arc::new(lsm_tree::compaction::Leveled::default()); - - let value = b"hellohello"; - - tree.insert("a", value, 1); - tree.insert("i", value, 1); - tree.flush_active_memtable(0)?; - - tree.insert("a", value, 2); - tree.insert("f", value, 2); - tree.flush_active_memtable(0)?; - - tree.insert("a", value, 3); - tree.insert("h", value, 3); - tree.flush_active_memtable(0)?; - - tree.insert("a", value, 4); - tree.insert("b", value, 4); - tree.flush_active_memtable(0)?; - - tree.insert("c", value, 5); - tree.insert("g", value, 5); - tree.flush_active_memtable(0)?; - - tree.insert("b", value, 6); - tree.insert("c", value, 6); - tree.insert("d", value, 6); - tree.insert("e", value, 6); - tree.flush_active_memtable(15)?; - tree.compact(compaction.clone(), 41)?; - - tree.insert("a", value, 7); - tree.flush_active_memtable(16)?; - - tree.insert("a", value, 8); - tree.flush_active_memtable(17)?; - - tree.insert("a", value, 9); - tree.flush_active_memtable(18)?; - - tree.insert("a", value, 10); - tree.flush_active_memtable(19)?; - tree.compact(compaction.clone(), 19)?; - - tree.drop_range::<&[u8], _>(..)?; - - assert_eq!(0, tree.table_count()); - assert_eq!(0, tree.blob_file_count()); - - Ok(()) -} diff --git a/crates/lsm-tree/tests/model_4.rs b/crates/lsm-tree/tests/model_4.rs deleted file mode 100644 index 4789f3e93..000000000 --- a/crates/lsm-tree/tests/model_4.rs +++ /dev/null @@ -1,112 +0,0 @@ -// Found by model testing - -use lsm_tree::{ - config::BlockSizePolicy, get_tmp_folder, AbstractTree, KvSeparationOptions, Result, - SequenceNumberCounter, -}; -use std::sync::Arc; -use test_log::test; - -#[test] -fn model_4() -> Result<()> { - let folder = get_tmp_folder(); - - let path = folder.path(); - - let tree = lsm_tree::Config::new( - path, - SequenceNumberCounter::default(), - SequenceNumberCounter::default(), - ) - .with_kv_separation(Some(KvSeparationOptions::default().separation_threshold(5))) - .data_block_size_policy(BlockSizePolicy::all(100)) - .open()?; - - let compaction = - Arc::new(lsm_tree::compaction::Leveled::default().with_table_target_size(1_000)); - - let value = b"hellohello"; - - tree.insert([0], value, 132); - tree.insert([0], value, 133); - tree.insert([1], value, 134); - tree.insert([2], value, 135); - tree.insert([3], value, 136); - tree.insert([4], value, 137); - tree.insert([5], value, 138); - tree.insert([6], value, 139); - tree.insert([7], value, 140); - tree.insert([0, 0, 0, 0, 0, 0, 9, 217], value, 141); - tree.insert([0, 0, 0, 0, 0, 0, 2, 77], value, 142); - tree.insert([0, 0, 0, 0, 0, 0, 33, 92], value, 143); - tree.insert([0, 0, 0, 0, 0, 0, 38, 41], value, 144); - tree.insert([0, 0, 0, 0, 0, 0, 22, 143], value, 145); - tree.insert([0, 0, 0, 0, 0, 0, 22, 161], value, 146); - tree.insert([0, 0, 0, 0, 0, 0, 9, 143], value, 148); - tree.insert([0, 0, 0, 0, 0, 0, 25, 222], value, 149); - tree.insert([0, 0, 0, 0, 0, 0, 11, 144], value, 150); - tree.insert([0, 0, 0, 0, 0, 0, 8, 208], value, 151); - tree.insert([0, 0, 0, 0, 0, 0, 31, 195], value, 152); - tree.insert([0, 0, 0, 0, 0, 0, 27, 47], value, 153); - tree.insert([0, 0, 0, 0, 0, 0, 31, 104], value, 154); - tree.insert([0, 0, 0, 0, 0, 0, 14, 219], value, 155); - tree.insert([0, 0, 0, 0, 0, 0, 17, 125], value, 156); - tree.insert([0, 0, 0, 0, 0, 0, 15, 52], value, 157); - tree.insert([0, 0, 0, 0, 0, 0, 20, 230], value, 158); - tree.insert([0, 0, 0, 0, 0, 0, 16, 88], value, 159); - tree.insert([0, 0, 0, 0, 0, 0, 9, 26], value, 160); - tree.insert([0, 0, 0, 0, 0, 0, 20, 21], value, 161); - tree.insert([0, 0, 0, 0, 0, 0, 27, 86], value, 162); - tree.insert([0, 0, 0, 0, 0, 0, 4, 112], value, 163); - tree.insert([0, 0, 0, 0, 0, 0, 12, 60], value, 164); - tree.insert([0, 0, 0, 0, 0, 0, 8, 186], value, 165); - tree.insert([0, 0, 0, 0, 0, 0, 34, 18], value, 166); - tree.insert([0, 0, 0, 0, 0, 0, 15, 156], value, 167); - tree.insert([0, 0, 0, 0, 0, 0, 5, 91], value, 168); - tree.insert([0, 0, 0, 0, 0, 0, 36, 0], value, 169); - tree.insert([0, 0, 0, 0, 0, 0, 38, 249], value, 170); - tree.insert([0, 0, 0, 0, 0, 0, 23, 42], value, 171); - tree.insert([0, 0, 0, 0, 0, 0, 23, 14], value, 172); - tree.insert([0, 0, 0, 0, 0, 0, 32, 119], value, 173); - tree.insert([0, 0, 0, 0, 0, 0, 31, 9], value, 174); - tree.insert([0, 0, 0, 0, 0, 0, 4, 170], value, 175); - tree.insert([0, 0, 0, 0, 0, 0, 18, 119], value, 176); - tree.insert([0, 0, 0, 0, 0, 0, 4, 178], value, 177); - tree.insert([0, 0, 0, 0, 0, 0, 4, 36], value, 178); - tree.insert([0, 0, 0, 0, 0, 0, 36, 53], value, 179); - tree.insert([0, 0, 0, 0, 0, 0, 35, 157], value, 181); - tree.insert([0, 0, 0, 0, 0, 0, 22, 24], value, 182); - tree.insert([0, 0, 0, 0, 0, 0, 33, 247], value, 183); - tree.insert([0, 0, 0, 0, 0, 0, 26, 236], value, 185); - tree.flush_active_memtable(86)?; - - tree.insert([0], value, 186); - tree.flush_active_memtable(87)?; - - tree.insert([0, 0, 0, 0, 0, 0, 7, 49], value, 187); - tree.flush_active_memtable(88)?; - - tree.insert([0, 0, 0, 0, 0, 0, 18, 134], value, 188); - tree.flush_active_memtable(89)?; - tree.compact(compaction.clone(), 89)?; - - tree.insert([0], value, 189); - tree.flush_active_memtable(90)?; - - tree.insert([0], value, 190); - tree.flush_active_memtable(91)?; - - tree.insert([0], value, 191); - tree.flush_active_memtable(92)?; - - tree.insert([0], value, 192); - tree.flush_active_memtable(93)?; - tree.compact(compaction.clone(), 93)?; - - tree.drop_range::<&[u8], _>(..)?; - - assert_eq!(0, tree.table_count()); - assert_eq!(0, tree.blob_file_count()); - - Ok(()) -} diff --git a/crates/lsm-tree/tests/model_5.rs b/crates/lsm-tree/tests/model_5.rs deleted file mode 100644 index d683c387d..000000000 --- a/crates/lsm-tree/tests/model_5.rs +++ /dev/null @@ -1,87 +0,0 @@ -// Found by model testing - -use lsm_tree::{ - config::BlockSizePolicy, get_tmp_folder, AbstractTree, KvSeparationOptions, Result, - SequenceNumberCounter, -}; -use std::sync::Arc; -use test_log::test; - -#[test] -fn model_5() -> Result<()> { - let folder = get_tmp_folder(); - - let path = folder.path(); - - let tree = lsm_tree::Config::new( - path, - SequenceNumberCounter::default(), - SequenceNumberCounter::default(), - ) - .with_kv_separation(Some(KvSeparationOptions::default().separation_threshold(5))) - .data_block_size_policy(BlockSizePolicy::all(100)) - .open()?; - - let compaction = Arc::new(lsm_tree::compaction::Leveled::default().with_table_target_size(150)); - - let value = b"hellohello"; - - tree.insert([0], value, 753); - tree.insert([0, 0, 0, 0, 0, 0, 1, 0], value, 754); - tree.insert([0, 0, 0, 0, 0, 0, 0, 140], value, 756); - tree.insert([0, 0, 0, 0, 0, 0, 0, 127], value, 762); - tree.insert([0, 0, 0, 0, 0, 0, 0, 98], value, 763); - tree.insert([0, 0, 0, 0, 0, 0, 2, 138], value, 764); - tree.insert([0, 0, 0, 0, 0, 0, 3, 150], value, 765); - tree.insert([0, 0, 0, 0, 0, 0, 0, 23], value, 766); - tree.insert([0, 0, 0, 0, 0, 0, 3, 121], value, 767); - tree.insert([0, 0, 0, 0, 0, 0, 1, 212], value, 842); - tree.insert([0, 0, 0, 0, 0, 0, 2, 152], value, 843); - tree.insert([0, 0, 0, 0, 0, 0, 2, 241], value, 844); - tree.flush_active_memtable(798)?; - - tree.insert([0, 0, 0, 0, 0, 0, 3, 120], value, 898); - tree.flush_active_memtable(799)?; - - tree.insert([0, 0, 0, 0, 0, 0, 3, 89], value, 899); - tree.flush_active_memtable(800)?; - - tree.insert([0, 0, 0, 0, 0, 0, 1, 52], value, 901); - tree.insert([0, 0, 0, 0, 0, 0, 0, 177], value, 902); - tree.insert([0, 0, 0, 0, 0, 0, 3, 43], value, 903); - tree.insert([0, 0, 0, 0, 0, 0, 3, 41], value, 904); - tree.insert([0, 0, 0, 0, 0, 0, 3, 160], value, 905); - tree.insert([0, 0, 0, 0, 0, 0, 1, 182], value, 906); - tree.insert([0, 0, 0, 0, 0, 0, 0, 73], value, 907); - tree.insert([0, 0, 0, 0, 0, 0, 0, 78], value, 912); - tree.insert([0, 0, 0, 0, 0, 0, 2, 103], value, 913); - tree.insert([0, 0, 0, 0, 0, 0, 1, 39], value, 914); - tree.insert([0, 0, 0, 0, 0, 0, 1, 78], value, 927); - tree.insert([0, 0, 0, 0, 0, 0, 0, 244], value, 928); - tree.insert([0, 0, 0, 0, 0, 0, 2, 76], value, 929); - tree.insert([0, 0, 0, 0, 0, 0, 1, 202], value, 934); - tree.insert([0, 0, 0, 0, 0, 0, 2, 140], value, 936); - tree.insert([0, 0, 0, 0, 0, 0, 2, 152], value, 937); - tree.flush_active_memtable(886)?; - tree.compact(compaction.clone(), 886)?; - - tree.insert([0, 0, 0, 0, 0, 0, 3, 145], value, 989); - tree.flush_active_memtable(890)?; - - tree.insert([0, 0, 0, 0, 0, 0, 3, 99], value, 993); - tree.flush_active_memtable(894)?; - - tree.insert([0, 0, 0, 0, 0, 0, 1, 106], value, 997); - tree.flush_active_memtable(898)?; - - tree.insert([0, 0, 0, 0, 0, 0, 2, 99], value, 1001); - tree.flush_active_memtable(902)?; - tree.compact(compaction.clone(), 902)?; - - tree.drop_range::<&[u8], _>(..)?; - - assert_eq!(0, tree.table_count()); - assert_eq!(0, tree.blob_file_count()); - - Ok(()) -} diff --git a/crates/lsm-tree/tests/model_6.rs b/crates/lsm-tree/tests/model_6.rs deleted file mode 100644 index b75f4f7e7..000000000 --- a/crates/lsm-tree/tests/model_6.rs +++ /dev/null @@ -1,1293 +0,0 @@ -// Found by model testing - -#![cfg_attr(rustfmt, rustfmt_skip)] - -use lsm_tree::{ - get_tmp_folder, - AbstractTree, KvSeparationOptions, Result, SequenceNumberCounter, config::{BlockSizePolicy, CompressionPolicy} -}; -use std::sync::Arc; -use test_log::test; - -// Yes this file is very large, it's hard to condense it to a more minimal repro -#[test] -fn model_6() -> Result<()> { - let folder = get_tmp_folder(); - - let path = folder.path(); - - let tree = lsm_tree::Config::new(path, SequenceNumberCounter::default(), SequenceNumberCounter::default()) - .data_block_compression_policy(CompressionPolicy::disabled()) - .index_block_compression_policy(CompressionPolicy::disabled()) - .data_block_size_policy(BlockSizePolicy::all(100)) - .with_kv_separation(Some(KvSeparationOptions::default().separation_threshold(10))) - .open()?; - - let compaction = Arc::new( - lsm_tree::compaction::Leveled::default() - .with_table_target_size(1_024) - ); - - tree.insert([0, 0, 0, 0, 0, 0, 3, 152], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170598); -tree.insert([0, 0, 0, 0, 0, 0, 0, 9], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170599); -tree.insert([0, 0, 0, 0, 0, 0, 1, 184], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170600); -tree.flush_active_memtable(170501)?; -tree.insert([0, 0, 0, 0, 0, 0, 3, 1], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170601); -tree.insert([0, 0, 0, 0, 0, 0, 2, 31], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170602); -tree.insert([0, 0, 0, 0, 0, 0, 3, 95], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170603); -tree.insert([0, 0, 0, 0, 0, 0, 1, 53], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170604); -tree.insert([0, 0, 0, 0, 0, 0, 0, 40], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170605); -tree.insert([0, 0, 0, 0, 0, 0, 3, 112], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170606); -tree.insert([0, 0, 0, 0, 0, 0, 2, 71], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170607); -tree.insert([0, 0, 0, 0, 0, 0, 0, 4], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170608); -tree.insert([0, 0, 0, 0, 0, 0, 2, 151], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170609); -tree.insert([0, 0, 0, 0, 0, 0, 2, 4], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170610); -tree.insert([0, 0, 0, 0, 0, 0, 2, 99], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170611); -tree.insert([0, 0, 0, 0, 0, 0, 2, 133], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170612); -tree.insert([0, 0, 0, 0, 0, 0, 3, 74], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170613); -tree.insert([0, 0, 0, 0, 0, 0, 2, 189], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170614); -tree.insert([0, 0, 0, 0, 0, 0, 3, 170], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170615); -tree.insert([0, 0, 0, 0, 0, 0, 1, 157], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170616); -tree.insert([0, 0, 0, 0, 0, 0, 2, 102], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170617); -tree.insert([0, 0, 0, 0, 0, 0, 3, 119], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170619); -tree.insert([0, 0, 0, 0, 0, 0, 2, 60], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170620); -tree.insert([0, 0, 0, 0, 0, 0, 0, 188], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170621); -tree.insert([0, 0, 0, 0, 0, 0, 0, 194], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170624); -tree.insert([0, 0, 0, 0, 0, 0, 1, 140], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170625); -tree.insert([0, 0, 0, 0, 0, 0, 0, 255], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170626); -tree.insert([0, 0, 0, 0, 0, 0, 1, 97], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170628); -tree.insert([0, 0, 0, 0, 0, 0, 2, 14], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170629); -tree.insert([0, 0, 0, 0, 0, 0, 0, 103], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170630); -tree.insert([0, 0, 0, 0, 0, 0, 0, 225], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170631); -tree.insert([0, 0, 0, 0, 0, 0, 0, 78], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170633); -tree.insert([0, 0, 0, 0, 0, 0, 0, 242], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170634); -tree.insert([0, 0, 0, 0, 0, 0, 0, 122], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170635); -tree.insert([0, 0, 0, 0, 0, 0, 0, 153], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170636); -tree.insert([0, 0, 0, 0, 0, 0, 1, 142], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170638); -tree.insert([0, 0, 0, 0, 0, 0, 0, 182], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170639); -tree.insert([0, 0, 0, 0, 0, 0, 1, 213], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170641); -tree.insert([0, 0, 0, 0, 0, 0, 2, 150], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170643); -tree.insert([0, 0, 0, 0, 0, 0, 0, 118], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170644); -tree.insert([0, 0, 0, 0, 0, 0, 1, 48], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170645); -tree.insert([0, 0, 0, 0, 0, 0, 0, 28], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170647); -tree.insert([0, 0, 0, 0, 0, 0, 0, 142], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170648); -tree.insert([0, 0, 0, 0, 0, 0, 0, 73], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170649); -tree.insert([0, 0, 0, 0, 0, 0, 0, 68], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170650); -tree.insert([0, 0, 0, 0, 0, 0, 1, 1], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170651); -tree.insert([0, 0, 0, 0, 0, 0, 2, 64], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170653); -tree.insert([0, 0, 0, 0, 0, 0, 1, 204], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170654); -tree.insert([0, 0, 0, 0, 0, 0, 1, 170], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170655); -tree.insert([0, 0, 0, 0, 0, 0, 2, 172], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170656); -tree.insert([0, 0, 0, 0, 0, 0, 1, 28], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170657); -tree.insert([0, 0, 0, 0, 0, 0, 2, 80], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170658); -tree.insert([0, 0, 0, 0, 0, 0, 0, 147], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170659); -tree.insert([0, 0, 0, 0, 0, 0, 1, 248], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170660); -tree.insert([0, 0, 0, 0, 0, 0, 2, 30], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170661); -tree.insert([0, 0, 0, 0, 0, 0, 1, 113], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170662); -tree.insert([0, 0, 0, 0, 0, 0, 0, 79], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170664); -tree.insert([0, 0, 0, 0, 0, 0, 1, 216], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170665); -tree.insert([0, 0, 0, 0, 0, 0, 0, 48], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170666); -tree.insert([0, 0, 0, 0, 0, 0, 0, 42], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170669); -tree.insert([0, 0, 0, 0, 0, 0, 1, 14], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170671); -tree.insert([0, 0, 0, 0, 0, 0, 0, 38], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170675); -tree.insert([0, 0, 0, 0, 0, 0, 1, 156], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170676); -tree.insert([0, 0, 0, 0, 0, 0, 0, 123], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170677); -tree.insert([0, 0, 0, 0, 0, 0, 0, 238], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170679); -tree.insert([0, 0, 0, 0, 0, 0, 0, 164], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170680); -tree.insert([0, 0, 0, 0, 0, 0, 1, 244], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170681); -tree.insert([0, 0, 0, 0, 0, 0, 0, 30], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170687); -tree.insert([0, 0, 0, 0, 0, 0, 0, 201], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170689); -tree.insert([0, 0, 0, 0, 0, 0, 1, 131], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170690); -tree.insert([0, 0, 0, 0, 0, 0, 0, 247], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170692); -tree.insert([0, 0, 0, 0, 0, 0, 1, 29], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170694); -tree.insert([0, 0, 0, 0, 0, 0, 1, 195], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170695); -tree.insert([0, 0, 0, 0, 0, 0, 1, 158], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170699); -tree.insert([0, 0, 0, 0, 0, 0, 0, 236], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170702); -tree.insert([0, 0, 0, 0, 0, 0, 2, 11], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170705); -tree.insert([0, 0, 0, 0, 0, 0, 0, 107], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170707); -tree.insert([0, 0, 0, 0, 0, 0, 1, 167], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170709); -tree.insert([0, 0, 0, 0, 0, 0, 0, 180], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170710); -tree.insert([0, 0, 0, 0, 0, 0, 3, 171], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170711); -tree.insert([0, 0, 0, 0, 0, 0, 3, 80], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170712); -tree.insert([0, 0, 0, 0, 0, 0, 1, 68], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170713); -tree.insert([0, 0, 0, 0, 0, 0, 3, 66], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170714); -tree.insert([0, 0, 0, 0, 0, 0, 1, 90], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170715); -tree.insert([0, 0, 0, 0, 0, 0, 1, 89], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170716); -tree.insert([0, 0, 0, 0, 0, 0, 1, 166], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170717); -tree.insert([0, 0, 0, 0, 0, 0, 3, 222], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170718); -tree.insert([0, 0, 0, 0, 0, 0, 0, 64], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170719); -tree.insert([0, 0, 0, 0, 0, 0, 3, 209], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170720); -tree.insert([0, 0, 0, 0, 0, 0, 1, 114], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170721); -tree.insert([0, 0, 0, 0, 0, 0, 3, 89], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170722); -tree.insert([0, 0, 0, 0, 0, 0, 0, 117], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170723); -tree.insert([0, 0, 0, 0, 0, 0, 2, 96], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170724); -tree.insert([0, 0, 0, 0, 0, 0, 3, 44], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170725); -tree.insert([0, 0, 0, 0, 0, 0, 2, 158], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170726); -tree.insert([0, 0, 0, 0, 0, 0, 3, 67], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170727); -tree.insert([0, 0, 0, 0, 0, 0, 0, 152], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170728); -tree.insert([0, 0, 0, 0, 0, 0, 3, 54], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170729); -tree.insert([0, 0, 0, 0, 0, 0, 0, 75], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170730); -tree.insert([0, 0, 0, 0, 0, 0, 1, 33], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170731); -tree.insert([0, 0, 0, 0, 0, 0, 1, 22], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170732); -tree.insert([0, 0, 0, 0, 0, 0, 1, 143], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170733); -tree.insert([0, 0, 0, 0, 0, 0, 1, 254], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170735); -tree.insert([0, 0, 0, 0, 0, 0, 2, 183], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170736); -tree.insert([0, 0, 0, 0, 0, 0, 1, 236], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170737); -tree.insert([0, 0, 0, 0, 0, 0, 3, 13], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170738); -tree.insert([0, 0, 0, 0, 0, 0, 2, 6], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170739); -tree.insert([0, 0, 0, 0, 0, 0, 2, 7], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170740); -tree.insert([0, 0, 0, 0, 0, 0, 2, 34], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170741); -tree.insert([0, 0, 0, 0, 0, 0, 2, 70], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170742); -tree.insert([0, 0, 0, 0, 0, 0, 3, 29], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170743); -tree.insert([0, 0, 0, 0, 0, 0, 0, 101], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170744); -tree.insert([0, 0, 0, 0, 0, 0, 1, 226], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170745); -tree.insert([0, 0, 0, 0, 0, 0, 3, 182], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170746); -tree.insert([0, 0, 0, 0, 0, 0, 0, 120], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170747); -tree.insert([0, 0, 0, 0, 0, 0, 1, 215], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170748); -tree.insert([0, 0, 0, 0, 0, 0, 1, 3], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170749); -tree.insert([0, 0, 0, 0, 0, 0, 3, 65], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170751); -tree.insert([0, 0, 0, 0, 0, 0, 1, 74], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170752); -tree.insert([0, 0, 0, 0, 0, 0, 3, 115], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170754); -tree.insert([0, 0, 0, 0, 0, 0, 0, 102], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170755); -tree.insert([0, 0, 0, 0, 0, 0, 0, 50], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170756); -tree.insert([0, 0, 0, 0, 0, 0, 1, 100], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170757); -tree.insert([0, 0, 0, 0, 0, 0, 2, 134], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170758); -tree.insert([0, 0, 0, 0, 0, 0, 1, 86], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170759); -tree.insert([0, 0, 0, 0, 0, 0, 0, 95], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170760); -tree.insert([0, 0, 0, 0, 0, 0, 1, 176], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170761); -tree.insert([0, 0, 0, 0, 0, 0, 2, 174], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170764); -tree.insert([0, 0, 0, 0, 0, 0, 3, 49], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170765); -tree.insert([0, 0, 0, 0, 0, 0, 3, 124], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170766); -tree.insert([0, 0, 0, 0, 0, 0, 1, 11], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170767); -tree.insert([0, 0, 0, 0, 0, 0, 2, 248], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170768); -tree.insert([0, 0, 0, 0, 0, 0, 1, 129], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170769); -tree.insert([0, 0, 0, 0, 0, 0, 0, 89], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170770); -tree.insert([0, 0, 0, 0, 0, 0, 1, 34], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170771); -tree.insert([0, 0, 0, 0, 0, 0, 1, 71], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170772); -tree.insert([0, 0, 0, 0, 0, 0, 3, 210], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170773); -tree.insert([0, 0, 0, 0, 0, 0, 1, 81], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170774); -tree.insert([0, 0, 0, 0, 0, 0, 0, 125], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170775); -tree.insert([0, 0, 0, 0, 0, 0, 1, 179], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170776); -tree.insert([0, 0, 0, 0, 0, 0, 1, 186], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170777); -tree.insert([0, 0, 0, 0, 0, 0, 3, 75], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170778); -tree.insert([0, 0, 0, 0, 0, 0, 3, 93], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170779); -tree.insert([0, 0, 0, 0, 0, 0, 2, 131], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170780); -tree.insert([0, 0, 0, 0, 0, 0, 3, 46], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170781); -tree.insert([0, 0, 0, 0, 0, 0, 1, 172], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170782); -tree.insert([0, 0, 0, 0, 0, 0, 0, 144], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170784); -tree.insert([0, 0, 0, 0, 0, 0, 2, 47], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170785); -tree.insert([0, 0, 0, 0, 0, 0, 2, 236], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170786); -tree.insert([0, 0, 0, 0, 0, 0, 1, 58], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170787); -tree.insert([0, 0, 0, 0, 0, 0, 3, 18], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170788); -tree.insert([0, 0, 0, 0, 0, 0, 3, 175], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170789); -tree.insert([0, 0, 0, 0, 0, 0, 1, 17], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170790); -tree.insert([0, 0, 0, 0, 0, 0, 2, 255], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170791); -tree.insert([0, 0, 0, 0, 0, 0, 0, 109], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170793); -tree.insert([0, 0, 0, 0, 0, 0, 1, 148], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170794); -tree.insert([0, 0, 0, 0, 0, 0, 0, 155], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170795); -tree.insert([0, 0, 0, 0, 0, 0, 2, 154], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170796); -tree.insert([0, 0, 0, 0, 0, 0, 3, 91], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170797); -tree.insert([0, 0, 0, 0, 0, 0, 0, 199], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170798); -tree.insert([0, 0, 0, 0, 0, 0, 1, 247], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170799); -tree.flush_active_memtable(170701)?; -tree.insert([0, 0, 0, 0, 0, 0, 1, 192], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170801); -tree.insert([0, 0, 0, 0, 0, 0, 1, 165], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170802); -tree.insert([0, 0, 0, 0, 0, 0, 0, 116], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170804); -tree.insert([0, 0, 0, 0, 0, 0, 0, 32], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170805); -tree.insert([0, 0, 0, 0, 0, 0, 0, 96], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170809); -tree.insert([0, 0, 0, 0, 0, 0, 1, 124], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170812); -tree.insert([0, 0, 0, 0, 0, 0, 2, 145], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170813); -tree.insert([0, 0, 0, 0, 0, 0, 1, 251], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170814); -tree.insert([0, 0, 0, 0, 0, 0, 0, 138], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170815); -tree.insert([0, 0, 0, 0, 0, 0, 3, 45], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170816); -tree.insert([0, 0, 0, 0, 0, 0, 3, 189], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170817); -tree.insert([0, 0, 0, 0, 0, 0, 2, 98], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170818); -tree.insert([0, 0, 0, 0, 0, 0, 3, 145], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170819); -tree.insert([0, 0, 0, 0, 0, 0, 3, 43], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170820); -tree.insert([0, 0, 0, 0, 0, 0, 0, 5], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170822); -tree.insert([0, 0, 0, 0, 0, 0, 2, 153], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170823); -tree.insert([0, 0, 0, 0, 0, 0, 0, 211], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170824); -tree.insert([0, 0, 0, 0, 0, 0, 3, 92], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170825); -tree.insert([0, 0, 0, 0, 0, 0, 0, 45], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170826); -tree.insert([0, 0, 0, 0, 0, 0, 2, 171], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170827); -tree.insert([0, 0, 0, 0, 0, 0, 0, 133], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170828); -tree.insert([0, 0, 0, 0, 0, 0, 2, 167], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170829); -tree.insert([0, 0, 0, 0, 0, 0, 0, 226], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170830); -tree.insert([0, 0, 0, 0, 0, 0, 1, 242], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170831); -tree.insert([0, 0, 0, 0, 0, 0, 0, 169], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170832); -tree.insert([0, 0, 0, 0, 0, 0, 1, 93], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170833); -tree.insert([0, 0, 0, 0, 0, 0, 1, 240], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170834); -tree.insert([0, 0, 0, 0, 0, 0, 2, 84], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170836); -tree.insert([0, 0, 0, 0, 0, 0, 3, 185], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170837); -tree.insert([0, 0, 0, 0, 0, 0, 0, 145], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170838); -tree.insert([0, 0, 0, 0, 0, 0, 2, 68], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170840); -tree.insert([0, 0, 0, 0, 0, 0, 3, 143], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170841); -tree.insert([0, 0, 0, 0, 0, 0, 0, 227], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170842); -tree.insert([0, 0, 0, 0, 0, 0, 2, 5], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170843); -tree.insert([0, 0, 0, 0, 0, 0, 1, 151], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170844); -tree.insert([0, 0, 0, 0, 0, 0, 1, 118], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170846); -tree.insert([0, 0, 0, 0, 0, 0, 2, 240], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170847); -tree.insert([0, 0, 0, 0, 0, 0, 3, 98], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170848); -tree.insert([0, 0, 0, 0, 0, 0, 1, 178], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170850); -tree.insert([0, 0, 0, 0, 0, 0, 0, 53], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170851); -tree.insert([0, 0, 0, 0, 0, 0, 1, 101], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170852); -tree.insert([0, 0, 0, 0, 0, 0, 3, 199], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170853); -tree.insert([0, 0, 0, 0, 0, 0, 2, 76], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170855); -tree.insert([0, 0, 0, 0, 0, 0, 1, 234], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170856); -tree.insert([0, 0, 0, 0, 0, 0, 1, 50], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170857); -tree.insert([0, 0, 0, 0, 0, 0, 0, 43], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170859); -tree.insert([0, 0, 0, 0, 0, 0, 1, 228], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170860); -tree.insert([0, 0, 0, 0, 0, 0, 2, 159], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170861); -tree.insert([0, 0, 0, 0, 0, 0, 2, 233], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170862); -tree.insert([0, 0, 0, 0, 0, 0, 3, 69], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170863); -tree.insert([0, 0, 0, 0, 0, 0, 1, 95], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170864); -tree.insert([0, 0, 0, 0, 0, 0, 0, 254], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170865); -tree.insert([0, 0, 0, 0, 0, 0, 1, 5], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170866); -tree.insert([0, 0, 0, 0, 0, 0, 2, 61], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170868); -tree.insert([0, 0, 0, 0, 0, 0, 3, 4], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170869); -tree.insert([0, 0, 0, 0, 0, 0, 3, 151], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170871); -tree.insert([0, 0, 0, 0, 0, 0, 0, 239], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170873); -tree.insert([0, 0, 0, 0, 0, 0, 0, 113], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170874); -tree.insert([0, 0, 0, 0, 0, 0, 1, 137], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170875); -tree.insert([0, 0, 0, 0, 0, 0, 2, 36], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170877); -tree.insert([0, 0, 0, 0, 0, 0, 2, 45], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170879); -tree.insert([0, 0, 0, 0, 0, 0, 2, 230], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170880); -tree.insert([0, 0, 0, 0, 0, 0, 3, 172], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170881); -tree.insert([0, 0, 0, 0, 0, 0, 3, 137], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170882); -tree.insert([0, 0, 0, 0, 0, 0, 2, 53], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170883); -tree.insert([0, 0, 0, 0, 0, 0, 0, 229], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170885); -tree.insert([0, 0, 0, 0, 0, 0, 2, 182], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170886); -tree.insert([0, 0, 0, 0, 0, 0, 0, 31], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170887); -tree.insert([0, 0, 0, 0, 0, 0, 0, 13], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170888); -tree.insert([0, 0, 0, 0, 0, 0, 2, 91], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170889); -tree.insert([0, 0, 0, 0, 0, 0, 3, 60], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170890); -tree.insert([0, 0, 0, 0, 0, 0, 1, 222], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170891); -tree.insert([0, 0, 0, 0, 0, 0, 1, 136], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170892); -tree.insert([0, 0, 0, 0, 0, 0, 2, 44], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170894); -tree.insert([0, 0, 0, 0, 0, 0, 1, 180], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170896); -tree.insert([0, 0, 0, 0, 0, 0, 0, 22], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170897); -tree.insert([0, 0, 0, 0, 0, 0, 2, 107], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170898); -tree.insert([0, 0, 0, 0, 0, 0, 3, 27], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170899); -tree.insert([0, 0, 0, 0, 0, 0, 3, 135], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170900); -tree.insert([0, 0, 0, 0, 0, 0, 2, 251], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170901); -tree.insert([0, 0, 0, 0, 0, 0, 0, 167], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170902); -tree.insert([0, 0, 0, 0, 0, 0, 3, 32], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170903); -tree.insert([0, 0, 0, 0, 0, 0, 1, 104], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170904); -tree.insert([0, 0, 0, 0, 0, 0, 3, 204], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170905); -tree.insert([0, 0, 0, 0, 0, 0, 1, 232], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170906); -tree.insert([0, 0, 0, 0, 0, 0, 3, 134], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170907); -tree.insert([0, 0, 0, 0, 0, 0, 0, 119], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170908); -tree.insert([0, 0, 0, 0, 0, 0, 2, 179], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170909); -tree.insert([0, 0, 0, 0, 0, 0, 3, 117], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170911); -tree.insert([0, 0, 0, 0, 0, 0, 3, 198], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170914); -tree.insert([0, 0, 0, 0, 0, 0, 2, 97], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170915); -tree.insert([0, 0, 0, 0, 0, 0, 3, 20], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170918); -tree.insert([0, 0, 0, 0, 0, 0, 0, 231], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170919); -tree.insert([0, 0, 0, 0, 0, 0, 2, 213], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170920); -tree.insert([0, 0, 0, 0, 0, 0, 1, 141], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170921); -tree.insert([0, 0, 0, 0, 0, 0, 0, 128], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170922); -tree.insert([0, 0, 0, 0, 0, 0, 0, 49], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170923); -tree.insert([0, 0, 0, 0, 0, 0, 2, 17], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170924); -tree.insert([0, 0, 0, 0, 0, 0, 1, 40], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170926); -tree.insert([0, 0, 0, 0, 0, 0, 3, 173], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170927); -tree.insert([0, 0, 0, 0, 0, 0, 3, 106], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170928); -tree.insert([0, 0, 0, 0, 0, 0, 2, 59], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170929); -tree.insert([0, 0, 0, 0, 0, 0, 1, 26], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170930); -tree.insert([0, 0, 0, 0, 0, 0, 1, 205], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170932); -tree.insert([0, 0, 0, 0, 0, 0, 0, 154], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170933); -tree.insert([0, 0, 0, 0, 0, 0, 1, 51], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170935); -tree.insert([0, 0, 0, 0, 0, 0, 3, 26], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170937); -tree.insert([0, 0, 0, 0, 0, 0, 1, 246], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170938); -tree.insert([0, 0, 0, 0, 0, 0, 3, 186], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170939); -tree.insert([0, 0, 0, 0, 0, 0, 1, 160], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170940); -tree.insert([0, 0, 0, 0, 0, 0, 1, 115], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170943); -tree.insert([0, 0, 0, 0, 0, 0, 1, 194], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170945); -tree.insert([0, 0, 0, 0, 0, 0, 0, 54], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170946); -tree.insert([0, 0, 0, 0, 0, 0, 2, 204], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170947); -tree.insert([0, 0, 0, 0, 0, 0, 2, 216], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170949); -tree.insert([0, 0, 0, 0, 0, 0, 2, 41], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170953); -tree.insert([0, 0, 0, 0, 0, 0, 2, 94], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170956); -tree.insert([0, 0, 0, 0, 0, 0, 1, 128], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170957); -tree.insert([0, 0, 0, 0, 0, 0, 1, 15], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170958); -tree.insert([0, 0, 0, 0, 0, 0, 1, 60], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170959); -tree.insert([0, 0, 0, 0, 0, 0, 1, 99], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170961); -tree.insert([0, 0, 0, 0, 0, 0, 0, 17], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170964); -tree.insert([0, 0, 0, 0, 0, 0, 0, 150], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170965); -tree.insert([0, 0, 0, 0, 0, 0, 1, 52], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170966); -tree.insert([0, 0, 0, 0, 0, 0, 3, 120], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170967); -tree.insert([0, 0, 0, 0, 0, 0, 1, 57], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170968); -tree.insert([0, 0, 0, 0, 0, 0, 2, 188], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170970); -tree.insert([0, 0, 0, 0, 0, 0, 3, 23], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170972); -tree.insert([0, 0, 0, 0, 0, 0, 3, 193], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170973); -tree.insert([0, 0, 0, 0, 0, 0, 3, 17], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170974); -tree.insert([0, 0, 0, 0, 0, 0, 2, 212], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170976); -tree.insert([0, 0, 0, 0, 0, 0, 3, 16], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170978); -tree.insert([0, 0, 0, 0, 0, 0, 1, 98], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170980); -tree.insert([0, 0, 0, 0, 0, 0, 3, 52], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170981); -tree.insert([0, 0, 0, 0, 0, 0, 1, 214], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170982); -tree.insert([0, 0, 0, 0, 0, 0, 0, 19], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170983); -tree.insert([0, 0, 0, 0, 0, 0, 0, 246], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170985); -tree.insert([0, 0, 0, 0, 0, 0, 0, 77], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170986); -tree.insert([0, 0, 0, 0, 0, 0, 3, 96], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170987); -tree.insert([0, 0, 0, 0, 0, 0, 2, 162], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170988); -tree.insert([0, 0, 0, 0, 0, 0, 0, 198], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170989); -tree.insert([0, 0, 0, 0, 0, 0, 2, 127], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170990); -tree.insert([0, 0, 0, 0, 0, 0, 0, 170], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170991); -tree.insert([0, 0, 0, 0, 0, 0, 1, 173], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170993); -tree.insert([0, 0, 0, 0, 0, 0, 3, 114], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170994); -tree.insert([0, 0, 0, 0, 0, 0, 0, 192], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170995); -tree.insert([0, 0, 0, 0, 0, 0, 2, 43], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170996); -tree.insert([0, 0, 0, 0, 0, 0, 1, 38], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170998); -tree.insert([0, 0, 0, 0, 0, 0, 1, 174], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 170999); -tree.insert([0, 0, 0, 0, 0, 0, 0, 90], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171000); -tree.flush_active_memtable(170901)?; -tree.insert([0, 0, 0, 0, 0, 0, 2, 163], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171001); -tree.insert([0, 0, 0, 0, 0, 0, 2, 195], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171003); -tree.insert([0, 0, 0, 0, 0, 0, 3, 86], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171004); -tree.insert([0, 0, 0, 0, 0, 0, 1, 8], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171005); -tree.insert([0, 0, 0, 0, 0, 0, 0, 82], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171006); -tree.insert([0, 0, 0, 0, 0, 0, 3, 121], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171007); -tree.insert([0, 0, 0, 0, 0, 0, 1, 208], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171008); -tree.insert([0, 0, 0, 0, 0, 0, 0, 237], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171010); -tree.insert([0, 0, 0, 0, 0, 0, 1, 190], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171012); -tree.insert([0, 0, 0, 0, 0, 0, 2, 221], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171013); -tree.insert([0, 0, 0, 0, 0, 0, 2, 54], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171014); -tree.insert([0, 0, 0, 0, 0, 0, 1, 231], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171015); -tree.insert([0, 0, 0, 0, 0, 0, 0, 97], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171016); -tree.insert([0, 0, 0, 0, 0, 0, 1, 217], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171017); -tree.insert([0, 0, 0, 0, 0, 0, 2, 224], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171018); -tree.insert([0, 0, 0, 0, 0, 0, 0, 10], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171019); -tree.insert([0, 0, 0, 0, 0, 0, 3, 58], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171022); -tree.insert([0, 0, 0, 0, 0, 0, 0, 235], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171024); -tree.insert([0, 0, 0, 0, 0, 0, 1, 12], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171027); -tree.insert([0, 0, 0, 0, 0, 0, 1, 107], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171034); -tree.insert([0, 0, 0, 0, 0, 0, 1, 233], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171042); -tree.insert([0, 0, 0, 0, 0, 0, 1, 0], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171044); -tree.insert([0, 0, 0, 0, 0, 0, 2, 77], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171045); -tree.insert([0, 0, 0, 0, 0, 0, 0, 253], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171050); -tree.insert([0, 0, 0, 0, 0, 0, 0, 241], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171051); -tree.insert([0, 0, 0, 0, 0, 0, 2, 22], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171052); -tree.insert([0, 0, 0, 0, 0, 0, 0, 12], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171053); -tree.insert([0, 0, 0, 0, 0, 0, 0, 24], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171063); -tree.insert([0, 0, 0, 0, 0, 0, 1, 24], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171065); -tree.insert([0, 0, 0, 0, 0, 0, 3, 48], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171070); -tree.insert([0, 0, 0, 0, 0, 0, 3, 138], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171071); -tree.insert([0, 0, 0, 0, 0, 0, 3, 126], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171075); -tree.insert([0, 0, 0, 0, 0, 0, 3, 42], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171076); -tree.insert([0, 0, 0, 0, 0, 0, 1, 146], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171078); -tree.insert([0, 0, 0, 0, 0, 0, 3, 169], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171080); -tree.insert([0, 0, 0, 0, 0, 0, 0, 233], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171082); -tree.insert([0, 0, 0, 0, 0, 0, 0, 1], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171085); -tree.insert([0, 0, 0, 0, 0, 0, 2, 42], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171088); -tree.insert([0, 0, 0, 0, 0, 0, 0, 23], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171089); -tree.insert([0, 0, 0, 0, 0, 0, 1, 202], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171091); -tree.insert([0, 0, 0, 0, 0, 0, 1, 112], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171092); -tree.insert([0, 0, 0, 0, 0, 0, 2, 193], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171094); -tree.insert([0, 0, 0, 0, 0, 0, 1, 21], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171095); -tree.insert([0, 0, 0, 0, 0, 0, 1, 37], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171097); -tree.insert([0, 0, 0, 0, 0, 0, 2, 191], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171098); -tree.insert([0, 0, 0, 0, 0, 0, 0, 114], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171099); -tree.insert([0, 0, 0, 0, 0, 0, 2, 186], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171103); -tree.insert([0, 0, 0, 0, 0, 0, 1, 237], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171104); -tree.insert([0, 0, 0, 0, 0, 0, 0, 67], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171105); -tree.insert([0, 0, 0, 0, 0, 0, 0, 174], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171110); -tree.insert([0, 0, 0, 0, 0, 0, 2, 111], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171111); -tree.insert([0, 0, 0, 0, 0, 0, 1, 198], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171113); -tree.insert([0, 0, 0, 0, 0, 0, 2, 119], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171114); -tree.insert([0, 0, 0, 0, 0, 0, 1, 125], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171115); -tree.insert([0, 0, 0, 0, 0, 0, 1, 154], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171116); -tree.insert([0, 0, 0, 0, 0, 0, 0, 166], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171117); -tree.insert([0, 0, 0, 0, 0, 0, 2, 249], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171118); -tree.insert([0, 0, 0, 0, 0, 0, 2, 223], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171122); -tree.insert([0, 0, 0, 0, 0, 0, 0, 108], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171133); -tree.insert([0, 0, 0, 0, 0, 0, 1, 84], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171173); -tree.insert([0, 0, 0, 0, 0, 0, 0, 224], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171174); -tree.insert([0, 0, 0, 0, 0, 0, 1, 152], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171175); -tree.insert([0, 0, 0, 0, 0, 0, 2, 16], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171180); -tree.flush_active_memtable(171101)?; -tree.compact(compaction.clone(), 171101)?; -tree.insert([0, 0, 0, 0, 0, 0, 3, 6], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171201); -tree.insert([0, 0, 0, 0, 0, 0, 1, 217], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171202); -tree.insert([0, 0, 0, 0, 0, 0, 3, 80], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171203); -tree.insert([0, 0, 0, 0, 0, 0, 2, 33], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171204); -tree.insert([0, 0, 0, 0, 0, 0, 0, 232], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171205); -tree.insert([0, 0, 0, 0, 0, 0, 3, 89], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171206); -tree.insert([0, 0, 0, 0, 0, 0, 3, 41], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171207); -tree.insert([0, 0, 0, 0, 0, 0, 3, 216], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171208); -tree.insert([0, 0, 0, 0, 0, 0, 1, 161], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171209); -tree.insert([0, 0, 0, 0, 0, 0, 0, 106], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171210); -tree.insert([0, 0, 0, 0, 0, 0, 0, 6], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171211); -tree.insert([0, 0, 0, 0, 0, 0, 2, 69], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171212); -tree.insert([0, 0, 0, 0, 0, 0, 2, 229], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171213); -tree.insert([0, 0, 0, 0, 0, 0, 0, 210], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171214); -tree.insert([0, 0, 0, 0, 0, 0, 0, 65], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171215); -tree.insert([0, 0, 0, 0, 0, 0, 0, 120], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171216); -tree.insert([0, 0, 0, 0, 0, 0, 1, 203], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171217); -tree.insert([0, 0, 0, 0, 0, 0, 2, 194], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171218); -tree.insert([0, 0, 0, 0, 0, 0, 0, 209], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171219); -tree.insert([0, 0, 0, 0, 0, 0, 0, 64], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171220); -tree.insert([0, 0, 0, 0, 0, 0, 3, 162], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171221); -tree.insert([0, 0, 0, 0, 0, 0, 1, 46], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171223); -tree.insert([0, 0, 0, 0, 0, 0, 0, 12], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171224); -tree.insert([0, 0, 0, 0, 0, 0, 1, 179], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171225); -tree.insert([0, 0, 0, 0, 0, 0, 0, 37], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171226); -tree.insert([0, 0, 0, 0, 0, 0, 2, 200], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171227); -tree.insert([0, 0, 0, 0, 0, 0, 3, 200], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171228); -tree.insert([0, 0, 0, 0, 0, 0, 2, 46], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171229); -tree.insert([0, 0, 0, 0, 0, 0, 1, 21], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171230); -tree.insert([0, 0, 0, 0, 0, 0, 2, 12], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171231); -tree.insert([0, 0, 0, 0, 0, 0, 2, 187], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171232); -tree.insert([0, 0, 0, 0, 0, 0, 2, 235], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171233); -tree.insert([0, 0, 0, 0, 0, 0, 0, 178], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171234); -tree.insert([0, 0, 0, 0, 0, 0, 0, 63], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171235); -tree.insert([0, 0, 0, 0, 0, 0, 1, 89], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171236); -tree.insert([0, 0, 0, 0, 0, 0, 1, 171], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171237); -tree.insert([0, 0, 0, 0, 0, 0, 1, 242], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171238); -tree.insert([0, 0, 0, 0, 0, 0, 0, 142], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171240); -tree.insert([0, 0, 0, 0, 0, 0, 1, 22], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171241); -tree.insert([0, 0, 0, 0, 0, 0, 3, 143], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171242); -tree.insert([0, 0, 0, 0, 0, 0, 0, 131], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171243); -tree.insert([0, 0, 0, 0, 0, 0, 3, 37], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171244); -tree.insert([0, 0, 0, 0, 0, 0, 0, 187], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171245); -tree.insert([0, 0, 0, 0, 0, 0, 2, 226], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171246); -tree.insert([0, 0, 0, 0, 0, 0, 3, 112], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171247); -tree.insert([0, 0, 0, 0, 0, 0, 1, 135], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171248); -tree.insert([0, 0, 0, 0, 0, 0, 2, 103], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171249); -tree.insert([0, 0, 0, 0, 0, 0, 1, 3], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171250); -tree.insert([0, 0, 0, 0, 0, 0, 1, 245], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171251); -tree.insert([0, 0, 0, 0, 0, 0, 1, 33], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171252); -tree.insert([0, 0, 0, 0, 0, 0, 0, 206], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171253); -tree.insert([0, 0, 0, 0, 0, 0, 0, 195], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171254); -tree.insert([0, 0, 0, 0, 0, 0, 3, 26], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171255); -tree.insert([0, 0, 0, 0, 0, 0, 2, 153], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171256); -tree.insert([0, 0, 0, 0, 0, 0, 3, 172], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171257); -tree.insert([0, 0, 0, 0, 0, 0, 0, 140], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171258); -tree.insert([0, 0, 0, 0, 0, 0, 2, 59], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171259); -tree.insert([0, 0, 0, 0, 0, 0, 2, 171], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171260); -tree.insert([0, 0, 0, 0, 0, 0, 0, 82], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171262); -tree.insert([0, 0, 0, 0, 0, 0, 1, 184], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171263); -tree.insert([0, 0, 0, 0, 0, 0, 3, 187], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171264); -tree.insert([0, 0, 0, 0, 0, 0, 0, 161], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171265); -tree.insert([0, 0, 0, 0, 0, 0, 2, 34], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171266); -tree.insert([0, 0, 0, 0, 0, 0, 2, 21], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171267); -tree.insert([0, 0, 0, 0, 0, 0, 1, 182], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171268); -tree.insert([0, 0, 0, 0, 0, 0, 3, 34], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171269); -tree.insert([0, 0, 0, 0, 0, 0, 3, 169], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171270); -tree.insert([0, 0, 0, 0, 0, 0, 1, 241], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171271); -tree.insert([0, 0, 0, 0, 0, 0, 1, 60], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171272); -tree.insert([0, 0, 0, 0, 0, 0, 2, 104], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171273); -tree.insert([0, 0, 0, 0, 0, 0, 0, 28], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171274); -tree.insert([0, 0, 0, 0, 0, 0, 3, 23], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171275); -tree.insert([0, 0, 0, 0, 0, 0, 0, 16], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171276); -tree.insert([0, 0, 0, 0, 0, 0, 0, 101], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171277); -tree.insert([0, 0, 0, 0, 0, 0, 0, 250], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171278); -tree.insert([0, 0, 0, 0, 0, 0, 0, 41], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171279); -tree.insert([0, 0, 0, 0, 0, 0, 3, 161], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171280); -tree.insert([0, 0, 0, 0, 0, 0, 1, 122], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171281); -tree.insert([0, 0, 0, 0, 0, 0, 0, 217], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171282); -tree.insert([0, 0, 0, 0, 0, 0, 3, 121], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171283); -tree.insert([0, 0, 0, 0, 0, 0, 0, 9], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171284); -tree.insert([0, 0, 0, 0, 0, 0, 0, 185], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171285); -tree.insert([0, 0, 0, 0, 0, 0, 2, 56], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171286); -tree.insert([0, 0, 0, 0, 0, 0, 0, 179], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171287); -tree.insert([0, 0, 0, 0, 0, 0, 1, 216], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171288); -tree.insert([0, 0, 0, 0, 0, 0, 2, 185], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171289); -tree.insert([0, 0, 0, 0, 0, 0, 0, 80], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171290); -tree.insert([0, 0, 0, 0, 0, 0, 2, 53], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171291); -tree.insert([0, 0, 0, 0, 0, 0, 1, 236], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171292); -tree.insert([0, 0, 0, 0, 0, 0, 0, 40], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171293); -tree.insert([0, 0, 0, 0, 0, 0, 3, 123], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171294); -tree.insert([0, 0, 0, 0, 0, 0, 0, 238], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171295); -tree.insert([0, 0, 0, 0, 0, 0, 0, 171], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171296); -tree.insert([0, 0, 0, 0, 0, 0, 0, 78], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171297); -tree.insert([0, 0, 0, 0, 0, 0, 3, 81], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171298); -tree.insert([0, 0, 0, 0, 0, 0, 0, 76], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171300); -tree.insert([0, 0, 0, 0, 0, 0, 2, 47], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171301); -tree.insert([0, 0, 0, 0, 0, 0, 3, 74], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171302); -tree.insert([0, 0, 0, 0, 0, 0, 3, 217], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171303); -tree.insert([0, 0, 0, 0, 0, 0, 2, 47], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171304); -tree.insert([0, 0, 0, 0, 0, 0, 0, 218], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171305); -tree.insert([0, 0, 0, 0, 0, 0, 3, 142], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171306); -tree.insert([0, 0, 0, 0, 0, 0, 3, 12], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171307); -tree.insert([0, 0, 0, 0, 0, 0, 2, 94], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171308); -tree.insert([0, 0, 0, 0, 0, 0, 0, 207], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171309); -tree.insert([0, 0, 0, 0, 0, 0, 0, 92], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171310); -tree.insert([0, 0, 0, 0, 0, 0, 0, 143], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171311); -tree.insert([0, 0, 0, 0, 0, 0, 0, 155], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171312); -tree.insert([0, 0, 0, 0, 0, 0, 1, 168], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171313); -tree.insert([0, 0, 0, 0, 0, 0, 1, 232], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171314); -tree.insert([0, 0, 0, 0, 0, 0, 2, 122], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171315); -tree.insert([0, 0, 0, 0, 0, 0, 1, 108], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171316); -tree.insert([0, 0, 0, 0, 0, 0, 3, 126], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171317); -tree.insert([0, 0, 0, 0, 0, 0, 0, 153], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171318); -tree.insert([0, 0, 0, 0, 0, 0, 3, 106], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171320); -tree.insert([0, 0, 0, 0, 0, 0, 2, 154], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171321); -tree.insert([0, 0, 0, 0, 0, 0, 3, 106], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171322); -tree.insert([0, 0, 0, 0, 0, 0, 0, 91], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171323); -tree.insert([0, 0, 0, 0, 0, 0, 3, 102], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171324); -tree.insert([0, 0, 0, 0, 0, 0, 0, 73], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171325); -tree.insert([0, 0, 0, 0, 0, 0, 3, 157], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171326); -tree.insert([0, 0, 0, 0, 0, 0, 0, 26], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171327); -tree.insert([0, 0, 0, 0, 0, 0, 3, 14], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171328); -tree.insert([0, 0, 0, 0, 0, 0, 1, 59], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171329); -tree.insert([0, 0, 0, 0, 0, 0, 1, 96], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171330); -tree.insert([0, 0, 0, 0, 0, 0, 1, 54], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171331); -tree.insert([0, 0, 0, 0, 0, 0, 0, 69], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171332); -tree.insert([0, 0, 0, 0, 0, 0, 0, 93], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171333); -tree.insert([0, 0, 0, 0, 0, 0, 3, 136], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171334); -tree.insert([0, 0, 0, 0, 0, 0, 2, 61], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171336); -tree.insert([0, 0, 0, 0, 0, 0, 1, 251], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171337); -tree.insert([0, 0, 0, 0, 0, 0, 1, 243], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171338); -tree.insert([0, 0, 0, 0, 0, 0, 1, 64], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171339); -tree.insert([0, 0, 0, 0, 0, 0, 0, 30], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171340); -tree.insert([0, 0, 0, 0, 0, 0, 3, 111], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171341); -tree.insert([0, 0, 0, 0, 0, 0, 2, 139], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171343); -tree.insert([0, 0, 0, 0, 0, 0, 2, 162], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171344); -tree.insert([0, 0, 0, 0, 0, 0, 3, 182], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171345); -tree.insert([0, 0, 0, 0, 0, 0, 3, 111], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171346); -tree.insert([0, 0, 0, 0, 0, 0, 1, 235], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171347); -tree.insert([0, 0, 0, 0, 0, 0, 0, 245], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171348); -tree.insert([0, 0, 0, 0, 0, 0, 2, 49], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171349); -tree.insert([0, 0, 0, 0, 0, 0, 1, 24], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171350); -tree.insert([0, 0, 0, 0, 0, 0, 2, 253], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171351); -tree.insert([0, 0, 0, 0, 0, 0, 3, 75], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171352); -tree.insert([0, 0, 0, 0, 0, 0, 2, 177], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171353); -tree.insert([0, 0, 0, 0, 0, 0, 1, 115], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171354); -tree.insert([0, 0, 0, 0, 0, 0, 3, 186], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171355); -tree.insert([0, 0, 0, 0, 0, 0, 2, 147], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171356); -tree.insert([0, 0, 0, 0, 0, 0, 1, 61], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171357); -tree.insert([0, 0, 0, 0, 0, 0, 0, 251], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171358); -tree.insert([0, 0, 0, 0, 0, 0, 2, 221], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171359); -tree.insert([0, 0, 0, 0, 0, 0, 1, 147], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171360); -tree.insert([0, 0, 0, 0, 0, 0, 3, 82], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171361); -tree.insert([0, 0, 0, 0, 0, 0, 3, 226], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171362); -tree.insert([0, 0, 0, 0, 0, 0, 1, 31], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171363); -tree.insert([0, 0, 0, 0, 0, 0, 2, 155], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171364); -tree.insert([0, 0, 0, 0, 0, 0, 2, 17], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171365); -tree.insert([0, 0, 0, 0, 0, 0, 2, 179], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171366); -tree.insert([0, 0, 0, 0, 0, 0, 2, 30], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171367); -tree.insert([0, 0, 0, 0, 0, 0, 2, 88], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171368); -tree.insert([0, 0, 0, 0, 0, 0, 0, 199], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171369); -tree.insert([0, 0, 0, 0, 0, 0, 1, 142], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171370); -tree.insert([0, 0, 0, 0, 0, 0, 2, 16], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171371); -tree.insert([0, 0, 0, 0, 0, 0, 3, 97], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171372); -tree.insert([0, 0, 0, 0, 0, 0, 1, 251], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171373); -tree.insert([0, 0, 0, 0, 0, 0, 2, 207], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171374); -tree.insert([0, 0, 0, 0, 0, 0, 1, 127], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171375); -tree.insert([0, 0, 0, 0, 0, 0, 0, 193], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171376); -tree.insert([0, 0, 0, 0, 0, 0, 3, 174], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171377); -tree.insert([0, 0, 0, 0, 0, 0, 3, 170], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171378); -tree.insert([0, 0, 0, 0, 0, 0, 2, 10], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171379); -tree.insert([0, 0, 0, 0, 0, 0, 3, 176], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171380); -tree.insert([0, 0, 0, 0, 0, 0, 3, 179], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171381); -tree.insert([0, 0, 0, 0, 0, 0, 2, 230], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171382); -tree.insert([0, 0, 0, 0, 0, 0, 3, 31], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171383); -tree.insert([0, 0, 0, 0, 0, 0, 0, 129], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171385); -tree.insert([0, 0, 0, 0, 0, 0, 2, 201], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171387); -tree.insert([0, 0, 0, 0, 0, 0, 0, 111], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171388); -tree.insert([0, 0, 0, 0, 0, 0, 0, 192], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171389); -tree.insert([0, 0, 0, 0, 0, 0, 1, 210], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171390); -tree.insert([0, 0, 0, 0, 0, 0, 3, 92], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171391); -tree.insert([0, 0, 0, 0, 0, 0, 3, 131], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171392); -tree.insert([0, 0, 0, 0, 0, 0, 2, 91], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171393); -tree.insert([0, 0, 0, 0, 0, 0, 3, 43], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171394); -tree.insert([0, 0, 0, 0, 0, 0, 1, 30], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171395); -tree.insert([0, 0, 0, 0, 0, 0, 2, 10], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171396); -tree.insert([0, 0, 0, 0, 0, 0, 2, 198], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171397); -tree.insert([0, 0, 0, 0, 0, 0, 1, 93], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171398); -tree.insert([0, 0, 0, 0, 0, 0, 1, 128], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171400); -tree.flush_active_memtable(171301)?; -tree.insert([0, 0, 0, 0, 0, 0, 3, 199], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171401); -tree.insert([0, 0, 0, 0, 0, 0, 3, 2], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171402); -tree.insert([0, 0, 0, 0, 0, 0, 1, 139], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171403); -tree.insert([0, 0, 0, 0, 0, 0, 0, 210], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171404); -tree.insert([0, 0, 0, 0, 0, 0, 0, 183], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171405); -tree.insert([0, 0, 0, 0, 0, 0, 0, 170], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171406); -tree.insert([0, 0, 0, 0, 0, 0, 1, 98], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171407); -tree.insert([0, 0, 0, 0, 0, 0, 2, 249], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171408); -tree.insert([0, 0, 0, 0, 0, 0, 3, 27], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171409); -tree.insert([0, 0, 0, 0, 0, 0, 0, 16], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171410); -tree.insert([0, 0, 0, 0, 0, 0, 1, 240], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171411); -tree.insert([0, 0, 0, 0, 0, 0, 0, 224], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171412); -tree.insert([0, 0, 0, 0, 0, 0, 2, 161], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171413); -tree.insert([0, 0, 0, 0, 0, 0, 0, 219], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171414); -tree.insert([0, 0, 0, 0, 0, 0, 1, 200], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171415); -tree.insert([0, 0, 0, 0, 0, 0, 1, 73], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171417); -tree.insert([0, 0, 0, 0, 0, 0, 0, 115], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171418); -tree.insert([0, 0, 0, 0, 0, 0, 0, 119], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171419); -tree.insert([0, 0, 0, 0, 0, 0, 0, 3], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171420); -tree.insert([0, 0, 0, 0, 0, 0, 1, 48], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171421); -tree.insert([0, 0, 0, 0, 0, 0, 0, 40], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171422); -tree.insert([0, 0, 0, 0, 0, 0, 0, 142], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171425); -tree.insert([0, 0, 0, 0, 0, 0, 0, 152], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171426); -tree.insert([0, 0, 0, 0, 0, 0, 1, 58], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171427); -tree.insert([0, 0, 0, 0, 0, 0, 1, 242], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171428); -tree.insert([0, 0, 0, 0, 0, 0, 2, 177], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171431); -tree.insert([0, 0, 0, 0, 0, 0, 1, 87], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171433); -tree.insert([0, 0, 0, 0, 0, 0, 0, 176], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171434); -tree.insert([0, 0, 0, 0, 0, 0, 0, 250], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171436); -tree.insert([0, 0, 0, 0, 0, 0, 1, 15], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171437); -tree.insert([0, 0, 0, 0, 0, 0, 1, 110], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171438); -tree.insert([0, 0, 0, 0, 0, 0, 1, 197], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171439); -tree.insert([0, 0, 0, 0, 0, 0, 0, 33], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171440); -tree.insert([0, 0, 0, 0, 0, 0, 1, 45], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171443); -tree.insert([0, 0, 0, 0, 0, 0, 1, 49], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171444); -tree.insert([0, 0, 0, 0, 0, 0, 3, 174], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171445); -tree.insert([0, 0, 0, 0, 0, 0, 0, 125], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171446); -tree.insert([0, 0, 0, 0, 0, 0, 3, 31], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171449); -tree.insert([0, 0, 0, 0, 0, 0, 3, 90], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171455); -tree.insert([0, 0, 0, 0, 0, 0, 0, 194], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171456); -tree.insert([0, 0, 0, 0, 0, 0, 2, 157], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171457); -tree.insert([0, 0, 0, 0, 0, 0, 2, 12], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171458); -tree.insert([0, 0, 0, 0, 0, 0, 3, 187], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171459); -tree.insert([0, 0, 0, 0, 0, 0, 3, 64], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171460); -tree.insert([0, 0, 0, 0, 0, 0, 0, 163], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171461); -tree.insert([0, 0, 0, 0, 0, 0, 3, 32], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171462); -tree.insert([0, 0, 0, 0, 0, 0, 2, 195], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171463); -tree.insert([0, 0, 0, 0, 0, 0, 0, 179], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171464); -tree.insert([0, 0, 0, 0, 0, 0, 3, 5], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171465); -tree.insert([0, 0, 0, 0, 0, 0, 2, 117], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171466); -tree.insert([0, 0, 0, 0, 0, 0, 3, 88], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171467); -tree.insert([0, 0, 0, 0, 0, 0, 3, 192], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171468); -tree.insert([0, 0, 0, 0, 0, 0, 1, 148], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171469); -tree.insert([0, 0, 0, 0, 0, 0, 3, 48], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171470); -tree.insert([0, 0, 0, 0, 0, 0, 2, 194], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171471); -tree.insert([0, 0, 0, 0, 0, 0, 1, 63], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171472); -tree.insert([0, 0, 0, 0, 0, 0, 2, 14], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171473); -tree.insert([0, 0, 0, 0, 0, 0, 1, 84], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171474); -tree.insert([0, 0, 0, 0, 0, 0, 0, 84], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171475); -tree.insert([0, 0, 0, 0, 0, 0, 2, 225], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171476); -tree.insert([0, 0, 0, 0, 0, 0, 1, 41], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171478); -tree.insert([0, 0, 0, 0, 0, 0, 0, 134], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171479); -tree.insert([0, 0, 0, 0, 0, 0, 3, 30], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171480); -tree.insert([0, 0, 0, 0, 0, 0, 3, 50], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171481); -tree.insert([0, 0, 0, 0, 0, 0, 0, 22], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171482); -tree.insert([0, 0, 0, 0, 0, 0, 3, 61], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171483); -tree.insert([0, 0, 0, 0, 0, 0, 3, 24], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171484); -tree.insert([0, 0, 0, 0, 0, 0, 3, 141], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171485); -tree.insert([0, 0, 0, 0, 0, 0, 2, 174], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171486); -tree.insert([0, 0, 0, 0, 0, 0, 2, 90], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171488); -tree.insert([0, 0, 0, 0, 0, 0, 0, 255], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171489); -tree.insert([0, 0, 0, 0, 0, 0, 0, 48], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171491); -tree.insert([0, 0, 0, 0, 0, 0, 0, 234], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171492); -tree.insert([0, 0, 0, 0, 0, 0, 2, 130], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171493); -tree.insert([0, 0, 0, 0, 0, 0, 0, 63], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171494); -tree.insert([0, 0, 0, 0, 0, 0, 2, 34], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171495); -tree.insert([0, 0, 0, 0, 0, 0, 3, 113], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171496); -tree.insert([0, 0, 0, 0, 0, 0, 3, 38], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171497); -tree.insert([0, 0, 0, 0, 0, 0, 2, 48], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171498); -tree.insert([0, 0, 0, 0, 0, 0, 1, 154], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171499); -tree.insert([0, 0, 0, 0, 0, 0, 0, 172], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171500); -tree.insert([0, 0, 0, 0, 0, 0, 3, 21], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171502); -tree.insert([0, 0, 0, 0, 0, 0, 0, 30], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171503); -tree.insert([0, 0, 0, 0, 0, 0, 3, 132], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171504); -tree.insert([0, 0, 0, 0, 0, 0, 1, 33], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171505); -tree.insert([0, 0, 0, 0, 0, 0, 0, 20], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171506); -tree.insert([0, 0, 0, 0, 0, 0, 1, 198], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171507); -tree.insert([0, 0, 0, 0, 0, 0, 3, 100], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171508); -tree.insert([0, 0, 0, 0, 0, 0, 3, 121], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171509); -tree.insert([0, 0, 0, 0, 0, 0, 0, 242], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171511); -tree.insert([0, 0, 0, 0, 0, 0, 2, 230], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171512); -tree.insert([0, 0, 0, 0, 0, 0, 3, 125], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171514); -tree.insert([0, 0, 0, 0, 0, 0, 1, 239], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171515); -tree.insert([0, 0, 0, 0, 0, 0, 0, 83], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171516); -tree.insert([0, 0, 0, 0, 0, 0, 3, 72], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171517); -tree.insert([0, 0, 0, 0, 0, 0, 1, 108], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171518); -tree.insert([0, 0, 0, 0, 0, 0, 0, 41], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171519); -tree.insert([0, 0, 0, 0, 0, 0, 2, 239], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171520); -tree.insert([0, 0, 0, 0, 0, 0, 2, 3], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171521); -tree.insert([0, 0, 0, 0, 0, 0, 0, 111], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171522); -tree.insert([0, 0, 0, 0, 0, 0, 2, 215], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171524); -tree.insert([0, 0, 0, 0, 0, 0, 2, 74], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171525); -tree.insert([0, 0, 0, 0, 0, 0, 2, 209], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171526); -tree.insert([0, 0, 0, 0, 0, 0, 0, 227], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171527); -tree.insert([0, 0, 0, 0, 0, 0, 1, 116], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171528); -tree.insert([0, 0, 0, 0, 0, 0, 3, 172], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171530); -tree.insert([0, 0, 0, 0, 0, 0, 1, 214], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171531); -tree.insert([0, 0, 0, 0, 0, 0, 3, 177], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171532); -tree.insert([0, 0, 0, 0, 0, 0, 0, 147], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171533); -tree.insert([0, 0, 0, 0, 0, 0, 1, 79], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171534); -tree.insert([0, 0, 0, 0, 0, 0, 0, 186], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171535); -tree.insert([0, 0, 0, 0, 0, 0, 3, 176], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171536); -tree.insert([0, 0, 0, 0, 0, 0, 3, 86], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171537); -tree.insert([0, 0, 0, 0, 0, 0, 3, 26], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171538); -tree.insert([0, 0, 0, 0, 0, 0, 1, 173], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171539); -tree.insert([0, 0, 0, 0, 0, 0, 0, 174], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171540); -tree.insert([0, 0, 0, 0, 0, 0, 1, 157], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171541); -tree.insert([0, 0, 0, 0, 0, 0, 2, 41], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171542); -tree.insert([0, 0, 0, 0, 0, 0, 2, 33], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171545); -tree.insert([0, 0, 0, 0, 0, 0, 0, 254], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171546); -tree.insert([0, 0, 0, 0, 0, 0, 1, 179], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171548); -tree.insert([0, 0, 0, 0, 0, 0, 3, 123], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171550); -tree.insert([0, 0, 0, 0, 0, 0, 2, 158], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171551); -tree.insert([0, 0, 0, 0, 0, 0, 3, 54], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171552); -tree.insert([0, 0, 0, 0, 0, 0, 1, 149], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171553); -tree.insert([0, 0, 0, 0, 0, 0, 3, 142], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171555); -tree.insert([0, 0, 0, 0, 0, 0, 3, 155], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171556); -tree.insert([0, 0, 0, 0, 0, 0, 2, 42], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171557); -tree.insert([0, 0, 0, 0, 0, 0, 2, 21], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171561); -tree.insert([0, 0, 0, 0, 0, 0, 1, 28], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171563); -tree.insert([0, 0, 0, 0, 0, 0, 0, 69], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171564); -tree.insert([0, 0, 0, 0, 0, 0, 0, 193], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171565); -tree.insert([0, 0, 0, 0, 0, 0, 1, 17], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171568); -tree.insert([0, 0, 0, 0, 0, 0, 0, 71], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171570); -tree.insert([0, 0, 0, 0, 0, 0, 1, 178], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171571); -tree.insert([0, 0, 0, 0, 0, 0, 2, 118], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171572); -tree.insert([0, 0, 0, 0, 0, 0, 0, 165], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171573); -tree.insert([0, 0, 0, 0, 0, 0, 0, 141], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171574); -tree.insert([0, 0, 0, 0, 0, 0, 2, 119], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171575); -tree.insert([0, 0, 0, 0, 0, 0, 2, 99], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171576); -tree.insert([0, 0, 0, 0, 0, 0, 1, 250], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171577); -tree.insert([0, 0, 0, 0, 0, 0, 0, 11], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171578); -tree.insert([0, 0, 0, 0, 0, 0, 2, 50], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171581); -tree.insert([0, 0, 0, 0, 0, 0, 1, 114], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171582); -tree.insert([0, 0, 0, 0, 0, 0, 2, 207], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171583); -tree.insert([0, 0, 0, 0, 0, 0, 1, 82], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171584); -tree.insert([0, 0, 0, 0, 0, 0, 1, 231], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171585); -tree.insert([0, 0, 0, 0, 0, 0, 0, 189], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171586); -tree.insert([0, 0, 0, 0, 0, 0, 1, 71], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171589); -tree.insert([0, 0, 0, 0, 0, 0, 2, 92], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171592); -tree.insert([0, 0, 0, 0, 0, 0, 0, 73], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171593); -tree.insert([0, 0, 0, 0, 0, 0, 0, 60], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171595); -tree.insert([0, 0, 0, 0, 0, 0, 0, 52], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171596); -tree.insert([0, 0, 0, 0, 0, 0, 2, 47], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171597); -tree.insert([0, 0, 0, 0, 0, 0, 0, 32], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171599); -tree.flush_active_memtable(171501)?; -tree.insert([0, 0, 0, 0, 0, 0, 1, 224], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171601); -tree.insert([0, 0, 0, 0, 0, 0, 0, 77], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171602); -tree.insert([0, 0, 0, 0, 0, 0, 0, 93], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171604); -tree.insert([0, 0, 0, 0, 0, 0, 1, 94], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171606); -tree.insert([0, 0, 0, 0, 0, 0, 2, 68], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171607); -tree.insert([0, 0, 0, 0, 0, 0, 3, 102], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171608); -tree.insert([0, 0, 0, 0, 0, 0, 2, 11], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171609); -tree.insert([0, 0, 0, 0, 0, 0, 2, 114], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171613); -tree.insert([0, 0, 0, 0, 0, 0, 2, 96], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171614); -tree.insert([0, 0, 0, 0, 0, 0, 1, 199], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171615); -tree.insert([0, 0, 0, 0, 0, 0, 1, 50], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171616); -tree.insert([0, 0, 0, 0, 0, 0, 0, 112], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171618); -tree.insert([0, 0, 0, 0, 0, 0, 0, 249], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171621); -tree.insert([0, 0, 0, 0, 0, 0, 2, 82], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171622); -tree.insert([0, 0, 0, 0, 0, 0, 1, 156], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171623); -tree.insert([0, 0, 0, 0, 0, 0, 2, 73], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171624); -tree.insert([0, 0, 0, 0, 0, 0, 2, 19], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171628); -tree.insert([0, 0, 0, 0, 0, 0, 2, 13], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171629); -tree.insert([0, 0, 0, 0, 0, 0, 2, 49], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171633); -tree.insert([0, 0, 0, 0, 0, 0, 0, 169], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171635); -tree.insert([0, 0, 0, 0, 0, 0, 2, 23], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171636); -tree.insert([0, 0, 0, 0, 0, 0, 3, 80], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171637); -tree.insert([0, 0, 0, 0, 0, 0, 2, 63], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171638); -tree.insert([0, 0, 0, 0, 0, 0, 0, 206], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171639); -tree.insert([0, 0, 0, 0, 0, 0, 0, 29], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171643); -tree.insert([0, 0, 0, 0, 0, 0, 1, 133], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171644); -tree.insert([0, 0, 0, 0, 0, 0, 0, 13], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171645); -tree.insert([0, 0, 0, 0, 0, 0, 1, 120], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171646); -tree.insert([0, 0, 0, 0, 0, 0, 0, 9], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171647); -tree.insert([0, 0, 0, 0, 0, 0, 0, 166], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171649); -tree.insert([0, 0, 0, 0, 0, 0, 1, 168], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171651); -tree.insert([0, 0, 0, 0, 0, 0, 1, 202], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171653); -tree.insert([0, 0, 0, 0, 0, 0, 1, 229], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171657); -tree.insert([0, 0, 0, 0, 0, 0, 2, 121], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171658); -tree.insert([0, 0, 0, 0, 0, 0, 0, 68], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171659); -tree.insert([0, 0, 0, 0, 0, 0, 1, 6], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171662); -tree.insert([0, 0, 0, 0, 0, 0, 2, 71], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171663); -tree.insert([0, 0, 0, 0, 0, 0, 1, 126], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171666); -tree.insert([0, 0, 0, 0, 0, 0, 2, 52], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171667); -tree.insert([0, 0, 0, 0, 0, 0, 2, 105], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171675); -tree.insert([0, 0, 0, 0, 0, 0, 1, 155], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171678); -tree.insert([0, 0, 0, 0, 0, 0, 0, 58], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171679); -tree.insert([0, 0, 0, 0, 0, 0, 1, 118], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171681); -tree.insert([0, 0, 0, 0, 0, 0, 1, 69], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171686); -tree.insert([0, 0, 0, 0, 0, 0, 0, 81], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171687); -tree.insert([0, 0, 0, 0, 0, 0, 1, 254], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171692); -tree.insert([0, 0, 0, 0, 0, 0, 0, 35], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171695); -tree.insert([0, 0, 0, 0, 0, 0, 1, 102], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171699); -tree.insert([0, 0, 0, 0, 0, 0, 1, 247], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171700); -tree.insert([0, 0, 0, 0, 0, 0, 1, 174], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171703); -tree.insert([0, 0, 0, 0, 0, 0, 0, 215], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171705); -tree.insert([0, 0, 0, 0, 0, 0, 2, 112], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171707); -tree.insert([0, 0, 0, 0, 0, 0, 0, 253], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171717); -tree.insert([0, 0, 0, 0, 0, 0, 1, 70], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171723); -tree.insert([0, 0, 0, 0, 0, 0, 2, 131], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171724); -tree.insert([0, 0, 0, 0, 0, 0, 0, 156], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171725); -tree.insert([0, 0, 0, 0, 0, 0, 1, 52], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171728); -tree.insert([0, 0, 0, 0, 0, 0, 1, 74], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171733); -tree.insert([0, 0, 0, 0, 0, 0, 0, 96], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171735); -tree.insert([0, 0, 0, 0, 0, 0, 0, 72], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171740); -tree.insert([0, 0, 0, 0, 0, 0, 2, 26], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171742); -tree.insert([0, 0, 0, 0, 0, 0, 1, 10], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171746); -tree.insert([0, 0, 0, 0, 0, 0, 0, 14], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171747); -tree.insert([0, 0, 0, 0, 0, 0, 2, 166], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171751); -tree.insert([0, 0, 0, 0, 0, 0, 0, 162], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171752); -tree.insert([0, 0, 0, 0, 0, 0, 1, 26], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171754); -tree.insert([0, 0, 0, 0, 0, 0, 2, 138], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171763); -tree.insert([0, 0, 0, 0, 0, 0, 1, 248], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171764); -tree.insert([0, 0, 0, 0, 0, 0, 2, 4], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171765); -tree.insert([0, 0, 0, 0, 0, 0, 0, 7], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171767); -tree.insert([0, 0, 0, 0, 0, 0, 0, 198], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171768); -tree.insert([0, 0, 0, 0, 0, 0, 0, 226], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171770); -tree.insert([0, 0, 0, 0, 0, 0, 1, 180], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171771); -tree.insert([0, 0, 0, 0, 0, 0, 0, 27], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171772); -tree.insert([0, 0, 0, 0, 0, 0, 2, 98], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171777); -tree.insert([0, 0, 0, 0, 0, 0, 1, 112], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171782); -tree.insert([0, 0, 0, 0, 0, 0, 2, 151], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171785); -tree.insert([0, 0, 0, 0, 0, 0, 1, 117], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171786); -tree.insert([0, 0, 0, 0, 0, 0, 0, 53], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171787); -tree.insert([0, 0, 0, 0, 0, 0, 2, 186], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171788); -tree.insert([0, 0, 0, 0, 0, 0, 2, 134], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171792); -tree.insert([0, 0, 0, 0, 0, 0, 2, 89], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171793); -tree.insert([0, 0, 0, 0, 0, 0, 1, 123], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171794); -tree.insert([0, 0, 0, 0, 0, 0, 1, 193], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171797); -tree.insert([0, 0, 0, 0, 0, 0, 1, 125], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171799); -tree.flush_active_memtable(171701)?; -tree.insert([0, 0, 0, 0, 0, 0, 1, 8], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171805); -tree.insert([0, 0, 0, 0, 0, 0, 2, 111], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171806); -tree.insert([0, 0, 0, 0, 0, 0, 2, 27], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171809); -tree.insert([0, 0, 0, 0, 0, 0, 2, 102], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171810); -tree.insert([0, 0, 0, 0, 0, 0, 0, 228], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171811); -tree.insert([0, 0, 0, 0, 0, 0, 1, 67], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171812); -tree.insert([0, 0, 0, 0, 0, 0, 1, 189], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171813); -tree.insert([0, 0, 0, 0, 0, 0, 2, 84], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171814); -tree.insert([0, 0, 0, 0, 0, 0, 1, 172], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171815); -tree.insert([0, 0, 0, 0, 0, 0, 0, 204], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171819); -tree.insert([0, 0, 0, 0, 0, 0, 1, 40], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171821); -tree.insert([0, 0, 0, 0, 0, 0, 1, 212], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171823); -tree.insert([0, 0, 0, 0, 0, 0, 2, 164], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171824); -tree.insert([0, 0, 0, 0, 0, 0, 1, 78], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171825); -tree.insert([0, 0, 0, 0, 0, 0, 1, 104], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171831); -tree.insert([0, 0, 0, 0, 0, 0, 1, 131], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171833); -tree.insert([0, 0, 0, 0, 0, 0, 0, 237], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171834); -tree.insert([0, 0, 0, 0, 0, 0, 2, 184], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171836); -tree.insert([0, 0, 0, 0, 0, 0, 2, 173], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171837); -tree.insert([0, 0, 0, 0, 0, 0, 2, 144], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171838); -tree.insert([0, 0, 0, 0, 0, 0, 2, 28], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171848); -tree.insert([0, 0, 0, 0, 0, 0, 1, 187], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171850); -tree.insert([0, 0, 0, 0, 0, 0, 2, 168], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171856); -tree.insert([0, 0, 0, 0, 0, 0, 1, 121], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171862); -tree.insert([0, 0, 0, 0, 0, 0, 0, 203], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171863); -tree.insert([0, 0, 0, 0, 0, 0, 0, 105], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171865); -tree.insert([0, 0, 0, 0, 0, 0, 2, 116], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171866); -tree.insert([0, 0, 0, 0, 0, 0, 1, 164], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171868); -tree.insert([0, 0, 0, 0, 0, 0, 1, 138], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171873); -tree.insert([0, 0, 0, 0, 0, 0, 0, 247], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171875); -tree.insert([0, 0, 0, 0, 0, 0, 1, 16], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171877); -tree.insert([0, 0, 0, 0, 0, 0, 0, 190], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171882); -tree.insert([0, 0, 0, 0, 0, 0, 0, 90], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171885); -tree.insert([0, 0, 0, 0, 0, 0, 1, 11], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171890); -tree.insert([0, 0, 0, 0, 0, 0, 2, 66], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171891); -tree.insert([0, 0, 0, 0, 0, 0, 1, 132], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171892); -tree.insert([0, 0, 0, 0, 0, 0, 0, 36], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171895); -tree.insert([0, 0, 0, 0, 0, 0, 0, 122], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171898); -tree.insert([0, 0, 0, 0, 0, 0, 0, 50], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171899); -tree.insert([0, 0, 0, 0, 0, 0, 0, 62], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171901); -tree.insert([0, 0, 0, 0, 0, 0, 2, 129], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171902); -tree.insert([0, 0, 0, 0, 0, 0, 0, 139], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171905); -tree.insert([0, 0, 0, 0, 0, 0, 1, 166], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171907); -tree.insert([0, 0, 0, 0, 0, 0, 0, 145], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171908); -tree.insert([0, 0, 0, 0, 0, 0, 1, 20], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171911); -tree.insert([0, 0, 0, 0, 0, 0, 2, 100], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171917); -tree.insert([0, 0, 0, 0, 0, 0, 0, 99], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171918); -tree.insert([0, 0, 0, 0, 0, 0, 1, 124], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171921); -tree.insert([0, 0, 0, 0, 0, 0, 2, 78], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171922); -tree.insert([0, 0, 0, 0, 0, 0, 2, 149], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171925); -tree.insert([0, 0, 0, 0, 0, 0, 1, 246], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171926); -tree.insert([0, 0, 0, 0, 0, 0, 1, 95], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171928); -tree.insert([0, 0, 0, 0, 0, 0, 0, 76], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171931); -tree.insert([0, 0, 0, 0, 0, 0, 1, 9], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171936); -tree.insert([0, 0, 0, 0, 0, 0, 1, 152], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171937); -tree.insert([0, 0, 0, 0, 0, 0, 1, 158], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171947); -tree.insert([0, 0, 0, 0, 0, 0, 2, 51], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171948); -tree.insert([0, 0, 0, 0, 0, 0, 0, 51], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171950); -tree.insert([0, 0, 0, 0, 0, 0, 0, 144], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171958); -tree.insert([0, 0, 0, 0, 0, 0, 0, 244], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171959); -tree.insert([0, 0, 0, 0, 0, 0, 1, 109], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171963); -tree.insert([0, 0, 0, 0, 0, 0, 0, 59], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171965); -tree.insert([0, 0, 0, 0, 0, 0, 0, 76], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171975); -tree.insert([0, 0, 0, 0, 0, 0, 0, 98], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171976); -tree.insert([0, 0, 0, 0, 0, 0, 1, 163], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171977); -tree.insert([0, 0, 0, 0, 0, 0, 1, 97], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171985); -tree.insert([0, 0, 0, 0, 0, 0, 1, 42], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171987); -tree.insert([0, 0, 0, 0, 0, 0, 1, 162], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171993); -tree.insert([0, 0, 0, 0, 0, 0, 1, 208], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 171997); -tree.flush_active_memtable(171901)?; -tree.compact(compaction.clone(), 171901)?; - - -tree.insert([0, 0, 0, 0, 0, 0, 3, 78], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172007); -tree.insert([0, 0, 0, 0, 0, 0, 1, 37], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172008); -tree.insert([0, 0, 0, 0, 0, 0, 3, 71], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172009); -tree.insert([0, 0, 0, 0, 0, 0, 3, 14], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172010); -tree.insert([0, 0, 0, 0, 0, 0, 2, 39], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172011); -tree.insert([0, 0, 0, 0, 0, 0, 2, 147], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172012); -tree.insert([0, 0, 0, 0, 0, 0, 2, 23], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172013); -tree.insert([0, 0, 0, 0, 0, 0, 0, 121], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172014); -tree.insert([0, 0, 0, 0, 0, 0, 1, 68], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172015); -tree.insert([0, 0, 0, 0, 0, 0, 2, 57], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172016); -tree.insert([0, 0, 0, 0, 0, 0, 0, 127], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172017); -tree.insert([0, 0, 0, 0, 0, 0, 1, 72], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172018); -tree.insert([0, 0, 0, 0, 0, 0, 3, 0], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172020); -tree.insert([0, 0, 0, 0, 0, 0, 1, 213], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172023); -tree.insert([0, 0, 0, 0, 0, 0, 0, 51], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172024); -tree.insert([0, 0, 0, 0, 0, 0, 2, 139], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172025); -tree.insert([0, 0, 0, 0, 0, 0, 1, 235], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172026); -tree.insert([0, 0, 0, 0, 0, 0, 2, 160], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172027); -tree.insert([0, 0, 0, 0, 0, 0, 2, 27], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172028); -tree.insert([0, 0, 0, 0, 0, 0, 1, 194], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172029); -tree.insert([0, 0, 0, 0, 0, 0, 1, 105], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172031); -tree.insert([0, 0, 0, 0, 0, 0, 1, 59], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172033); -tree.insert([0, 0, 0, 0, 0, 0, 2, 85], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172035); -tree.insert([0, 0, 0, 0, 0, 0, 1, 19], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172036); -tree.insert([0, 0, 0, 0, 0, 0, 2, 29], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172037); -tree.insert([0, 0, 0, 0, 0, 0, 2, 50], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172038); -tree.insert([0, 0, 0, 0, 0, 0, 1, 18], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172039); -tree.insert([0, 0, 0, 0, 0, 0, 0, 0], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172043); -tree.insert([0, 0, 0, 0, 0, 0, 0, 144], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172044); -tree.insert([0, 0, 0, 0, 0, 0, 1, 169], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172045); -tree.insert([0, 0, 0, 0, 0, 0, 1, 74], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172046); -tree.insert([0, 0, 0, 0, 0, 0, 3, 58], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172047); -tree.insert([0, 0, 0, 0, 0, 0, 2, 64], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172048); -tree.insert([0, 0, 0, 0, 0, 0, 1, 47], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172049); -tree.insert([0, 0, 0, 0, 0, 0, 2, 103], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172050); -tree.insert([0, 0, 0, 0, 0, 0, 1, 231], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172051); -tree.insert([0, 0, 0, 0, 0, 0, 2, 179], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172052); -tree.insert([0, 0, 0, 0, 0, 0, 3, 17], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172053); -tree.insert([0, 0, 0, 0, 0, 0, 1, 217], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172054); -tree.insert([0, 0, 0, 0, 0, 0, 1, 82], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172056); -tree.insert([0, 0, 0, 0, 0, 0, 1, 184], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172057); -tree.insert([0, 0, 0, 0, 0, 0, 1, 212], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172058); -tree.insert([0, 0, 0, 0, 0, 0, 0, 74], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172059); -tree.insert([0, 0, 0, 0, 0, 0, 1, 114], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172061); -tree.insert([0, 0, 0, 0, 0, 0, 1, 173], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172062); -tree.insert([0, 0, 0, 0, 0, 0, 1, 237], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172064); -tree.insert([0, 0, 0, 0, 0, 0, 1, 25], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172065); -tree.insert([0, 0, 0, 0, 0, 0, 1, 125], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172066); -tree.insert([0, 0, 0, 0, 0, 0, 2, 78], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172067); -tree.insert([0, 0, 0, 0, 0, 0, 1, 245], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172068); -tree.insert([0, 0, 0, 0, 0, 0, 1, 35], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172069); -tree.insert([0, 0, 0, 0, 0, 0, 3, 4], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172070); -tree.insert([0, 0, 0, 0, 0, 0, 3, 120], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172071); -tree.insert([0, 0, 0, 0, 0, 0, 3, 10], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172072); -tree.insert([0, 0, 0, 0, 0, 0, 2, 74], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172073); -tree.insert([0, 0, 0, 0, 0, 0, 2, 15], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172074); -tree.insert([0, 0, 0, 0, 0, 0, 2, 109], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172075); -tree.insert([0, 0, 0, 0, 0, 0, 1, 39], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172077); -tree.insert([0, 0, 0, 0, 0, 0, 2, 221], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172078); -tree.insert([0, 0, 0, 0, 0, 0, 2, 250], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172081); -tree.insert([0, 0, 0, 0, 0, 0, 0, 180], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172082); -tree.insert([0, 0, 0, 0, 0, 0, 2, 5], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172083); -tree.insert([0, 0, 0, 0, 0, 0, 0, 223], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172085); -tree.insert([0, 0, 0, 0, 0, 0, 3, 109], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172087); -tree.insert([0, 0, 0, 0, 0, 0, 2, 175], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172088); -tree.insert([0, 0, 0, 0, 0, 0, 2, 228], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172089); -tree.insert([0, 0, 0, 0, 0, 0, 0, 126], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172090); -tree.insert([0, 0, 0, 0, 0, 0, 0, 56], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172091); -tree.insert([0, 0, 0, 0, 0, 0, 0, 157], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172092); -tree.insert([0, 0, 0, 0, 0, 0, 0, 99], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172093); -tree.insert([0, 0, 0, 0, 0, 0, 0, 96], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172094); -tree.insert([0, 0, 0, 0, 0, 0, 0, 76], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172095); -tree.insert([0, 0, 0, 0, 0, 0, 2, 253], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172096); -tree.insert([0, 0, 0, 0, 0, 0, 0, 210], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172097); -tree.insert([0, 0, 0, 0, 0, 0, 3, 60], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172098); -tree.insert([0, 0, 0, 0, 0, 0, 2, 169], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172101); -tree.insert([0, 0, 0, 0, 0, 0, 2, 118], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172102); -tree.insert([0, 0, 0, 0, 0, 0, 0, 85], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172104); -tree.insert([0, 0, 0, 0, 0, 0, 2, 53], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172105); -tree.insert([0, 0, 0, 0, 0, 0, 2, 142], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172108); -tree.insert([0, 0, 0, 0, 0, 0, 1, 249], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172110); -tree.insert([0, 0, 0, 0, 0, 0, 1, 243], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172111); -tree.insert([0, 0, 0, 0, 0, 0, 1, 48], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172112); -tree.insert([0, 0, 0, 0, 0, 0, 1, 16], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172113); -tree.insert([0, 0, 0, 0, 0, 0, 2, 121], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172114); -tree.insert([0, 0, 0, 0, 0, 0, 1, 2], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172116); -tree.insert([0, 0, 0, 0, 0, 0, 0, 8], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172117); -tree.insert([0, 0, 0, 0, 0, 0, 1, 128], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172118); -tree.insert([0, 0, 0, 0, 0, 0, 1, 104], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172119); -tree.insert([0, 0, 0, 0, 0, 0, 0, 12], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172122); -tree.insert([0, 0, 0, 0, 0, 0, 0, 122], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172123); -tree.insert([0, 0, 0, 0, 0, 0, 0, 123], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172124); -tree.insert([0, 0, 0, 0, 0, 0, 2, 140], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172125); -tree.insert([0, 0, 0, 0, 0, 0, 2, 30], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172126); -tree.insert([0, 0, 0, 0, 0, 0, 0, 235], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172127); -tree.insert([0, 0, 0, 0, 0, 0, 1, 126], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172128); -tree.insert([0, 0, 0, 0, 0, 0, 1, 149], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172149); -tree.insert([0, 0, 0, 0, 0, 0, 0, 9], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172151); -tree.insert([0, 0, 0, 0, 0, 0, 1, 78], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172152); -tree.insert([0, 0, 0, 0, 0, 0, 0, 64], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172153); -tree.insert([0, 0, 0, 0, 0, 0, 0, 161], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172157); -tree.insert([0, 0, 0, 0, 0, 0, 1, 200], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172158); -tree.insert([0, 0, 0, 0, 0, 0, 1, 234], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172163); -tree.insert([0, 0, 0, 0, 0, 0, 1, 196], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172164); -tree.insert([0, 0, 0, 0, 0, 0, 1, 153], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172167); -tree.flush_active_memtable(172101)?; -tree.compact(compaction.clone(), 172101)?; -tree.insert([0, 0, 0, 0, 0, 0, 0, 219], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172222); -tree.insert([0, 0, 0, 0, 0, 0, 0, 124], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172225); -tree.insert([0, 0, 0, 0, 0, 0, 2, 192], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172226); -tree.insert([0, 0, 0, 0, 0, 0, 0, 108], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172227); -tree.insert([0, 0, 0, 0, 0, 0, 0, 41], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172229); -tree.insert([0, 0, 0, 0, 0, 0, 0, 49], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172231); -tree.insert([0, 0, 0, 0, 0, 0, 1, 98], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172233); -tree.insert([0, 0, 0, 0, 0, 0, 2, 100], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172235); -tree.insert([0, 0, 0, 0, 0, 0, 3, 40], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172237); -tree.insert([0, 0, 0, 0, 0, 0, 3, 39], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172238); -tree.insert([0, 0, 0, 0, 0, 0, 2, 16], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172239); -tree.insert([0, 0, 0, 0, 0, 0, 1, 172], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172240); -tree.insert([0, 0, 0, 0, 0, 0, 0, 143], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172242); -tree.insert([0, 0, 0, 0, 0, 0, 1, 101], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172245); -tree.insert([0, 0, 0, 0, 0, 0, 0, 138], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172247); -tree.insert([0, 0, 0, 0, 0, 0, 0, 88], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172251); -tree.insert([0, 0, 0, 0, 0, 0, 0, 154], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172252); -tree.insert([0, 0, 0, 0, 0, 0, 2, 10], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172253); -tree.insert([0, 0, 0, 0, 0, 0, 1, 233], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172257); -tree.insert([0, 0, 0, 0, 0, 0, 0, 229], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172259); -tree.insert([0, 0, 0, 0, 0, 0, 1, 32], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172260); -tree.insert([0, 0, 0, 0, 0, 0, 1, 211], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172261); -tree.insert([0, 0, 0, 0, 0, 0, 2, 69], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172262); -tree.insert([0, 0, 0, 0, 0, 0, 2, 86], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172263); -tree.insert([0, 0, 0, 0, 0, 0, 2, 56], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172264); -tree.insert([0, 0, 0, 0, 0, 0, 2, 190], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172265); -tree.insert([0, 0, 0, 0, 0, 0, 2, 28], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172270); -tree.insert([0, 0, 0, 0, 0, 0, 2, 21], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172271); -tree.insert([0, 0, 0, 0, 0, 0, 1, 118], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172273); -tree.insert([0, 0, 0, 0, 0, 0, 1, 193], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172274); -tree.insert([0, 0, 0, 0, 0, 0, 0, 115], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172275); -tree.insert([0, 0, 0, 0, 0, 0, 2, 200], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172276); -tree.insert([0, 0, 0, 0, 0, 0, 0, 225], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172278); -tree.insert([0, 0, 0, 0, 0, 0, 1, 7], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172279); -tree.insert([0, 0, 0, 0, 0, 0, 0, 129], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172280); -tree.insert([0, 0, 0, 0, 0, 0, 0, 226], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172281); -tree.insert([0, 0, 0, 0, 0, 0, 2, 9], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172286); -tree.insert([0, 0, 0, 0, 0, 0, 0, 50], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172289); -tree.insert([0, 0, 0, 0, 0, 0, 2, 43], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172290); -tree.insert([0, 0, 0, 0, 0, 0, 0, 120], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172291); -tree.insert([0, 0, 0, 0, 0, 0, 1, 239], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172292); -tree.insert([0, 0, 0, 0, 0, 0, 0, 61], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172293); -tree.insert([0, 0, 0, 0, 0, 0, 0, 246], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172294); -tree.insert([0, 0, 0, 0, 0, 0, 0, 7], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172296); -tree.insert([0, 0, 0, 0, 0, 0, 0, 68], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172297); -tree.insert([0, 0, 0, 0, 0, 0, 3, 18], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172298); -tree.insert([0, 0, 0, 0, 0, 0, 0, 131], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172299); -tree.insert([0, 0, 0, 0, 0, 0, 2, 124], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172301); -tree.insert([0, 0, 0, 0, 0, 0, 0, 142], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172304); -tree.insert([0, 0, 0, 0, 0, 0, 2, 54], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172305); -tree.insert([0, 0, 0, 0, 0, 0, 1, 42], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172307); -tree.insert([0, 0, 0, 0, 0, 0, 0, 128], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172308); -tree.insert([0, 0, 0, 0, 0, 0, 3, 36], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172310); -tree.insert([0, 0, 0, 0, 0, 0, 0, 184], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172311); -tree.insert([0, 0, 0, 0, 0, 0, 1, 182], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172313); -tree.insert([0, 0, 0, 0, 0, 0, 1, 5], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172315); -tree.insert([0, 0, 0, 0, 0, 0, 2, 198], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172317); -tree.insert([0, 0, 0, 0, 0, 0, 0, 17], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172318); -tree.insert([0, 0, 0, 0, 0, 0, 0, 39], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172321); -tree.insert([0, 0, 0, 0, 0, 0, 2, 36], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172323); -tree.insert([0, 0, 0, 0, 0, 0, 0, 192], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172324); -tree.insert([0, 0, 0, 0, 0, 0, 1, 80], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172327); -tree.insert([0, 0, 0, 0, 0, 0, 1, 181], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172328); -tree.insert([0, 0, 0, 0, 0, 0, 0, 116], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172331); -tree.insert([0, 0, 0, 0, 0, 0, 0, 47], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172337); -tree.insert([0, 0, 0, 0, 0, 0, 1, 30], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172338); -tree.insert([0, 0, 0, 0, 0, 0, 0, 243], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172340); -tree.insert([0, 0, 0, 0, 0, 0, 0, 90], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172342); -tree.insert([0, 0, 0, 0, 0, 0, 0, 178], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172343); -tree.insert([0, 0, 0, 0, 0, 0, 1, 22], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172350); -tree.insert([0, 0, 0, 0, 0, 0, 0, 240], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172352); -tree.insert([0, 0, 0, 0, 0, 0, 2, 8], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172354); -tree.insert([0, 0, 0, 0, 0, 0, 2, 45], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172355); -tree.insert([0, 0, 0, 0, 0, 0, 2, 87], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172357); -tree.insert([0, 0, 0, 0, 0, 0, 1, 127], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172358); -tree.insert([0, 0, 0, 0, 0, 0, 0, 195], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172359); -tree.insert([0, 0, 0, 0, 0, 0, 2, 115], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172360); -tree.insert([0, 0, 0, 0, 0, 0, 2, 126], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172362); -tree.insert([0, 0, 0, 0, 0, 0, 2, 105], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172365); -tree.insert([0, 0, 0, 0, 0, 0, 2, 191], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172366); -tree.insert([0, 0, 0, 0, 0, 0, 1, 209], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172367); -tree.insert([0, 0, 0, 0, 0, 0, 0, 156], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172368); -tree.insert([0, 0, 0, 0, 0, 0, 2, 208], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172370); -tree.insert([0, 0, 0, 0, 0, 0, 2, 60], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172371); -tree.insert([0, 0, 0, 0, 0, 0, 1, 87], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172372); -tree.insert([0, 0, 0, 0, 0, 0, 1, 44], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172373); -tree.insert([0, 0, 0, 0, 0, 0, 0, 232], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172374); -tree.insert([0, 0, 0, 0, 0, 0, 2, 47], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172376); -tree.insert([0, 0, 0, 0, 0, 0, 1, 178], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172378); -tree.insert([0, 0, 0, 0, 0, 0, 2, 150], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172380); -tree.insert([0, 0, 0, 0, 0, 0, 2, 82], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172381); -tree.insert([0, 0, 0, 0, 0, 0, 1, 53], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172383); -tree.insert([0, 0, 0, 0, 0, 0, 1, 81], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172387); -tree.insert([0, 0, 0, 0, 0, 0, 1, 227], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172390); -tree.insert([0, 0, 0, 0, 0, 0, 0, 38], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172392); -tree.insert([0, 0, 0, 0, 0, 0, 2, 162], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172393); -tree.insert([0, 0, 0, 0, 0, 0, 1, 65], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172394); -tree.insert([0, 0, 0, 0, 0, 0, 1, 255], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172397); -tree.insert([0, 0, 0, 0, 0, 0, 0, 95], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172398); -tree.insert([0, 0, 0, 0, 0, 0, 3, 63], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172399); -tree.insert([0, 0, 0, 0, 0, 0, 0, 66], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172400); -tree.flush_active_memtable(172301)?; -tree.insert([0, 0, 0, 0, 0, 0, 1, 27], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172402); -tree.insert([0, 0, 0, 0, 0, 0, 0, 187], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172404); -tree.insert([0, 0, 0, 0, 0, 0, 2, 211], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172408); -tree.insert([0, 0, 0, 0, 0, 0, 1, 216], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172409); -tree.insert([0, 0, 0, 0, 0, 0, 0, 93], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172414); -tree.insert([0, 0, 0, 0, 0, 0, 0, 71], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172416); -tree.insert([0, 0, 0, 0, 0, 0, 3, 7], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172417); -tree.insert([0, 0, 0, 0, 0, 0, 1, 176], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172418); -tree.insert([0, 0, 0, 0, 0, 0, 0, 215], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172420); -tree.insert([0, 0, 0, 0, 0, 0, 1, 223], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172423); -tree.insert([0, 0, 0, 0, 0, 0, 0, 221], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172424); -tree.insert([0, 0, 0, 0, 0, 0, 0, 54], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172425); -tree.insert([0, 0, 0, 0, 0, 0, 0, 166], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172426); -tree.insert([0, 0, 0, 0, 0, 0, 1, 167], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172427); -tree.insert([0, 0, 0, 0, 0, 0, 2, 170], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172428); -tree.insert([0, 0, 0, 0, 0, 0, 2, 217], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172429); -tree.insert([0, 0, 0, 0, 0, 0, 1, 56], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172432); -tree.insert([0, 0, 0, 0, 0, 0, 0, 58], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172433); -tree.insert([0, 0, 0, 0, 0, 0, 2, 89], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172434); -tree.insert([0, 0, 0, 0, 0, 0, 1, 96], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172435); -tree.insert([0, 0, 0, 0, 0, 0, 1, 58], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172436); -tree.insert([0, 0, 0, 0, 0, 0, 2, 88], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172438); -tree.insert([0, 0, 0, 0, 0, 0, 0, 188], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172439); -tree.insert([0, 0, 0, 0, 0, 0, 1, 99], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172440); -tree.insert([0, 0, 0, 0, 0, 0, 2, 114], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172441); -tree.insert([0, 0, 0, 0, 0, 0, 1, 83], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172442); -tree.insert([0, 0, 0, 0, 0, 0, 2, 49], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172443); -tree.insert([0, 0, 0, 0, 0, 0, 1, 13], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172444); -tree.insert([0, 0, 0, 0, 0, 0, 0, 211], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172445); -tree.insert([0, 0, 0, 0, 0, 0, 0, 227], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172449); -tree.insert([0, 0, 0, 0, 0, 0, 3, 22], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172450); -tree.insert([0, 0, 0, 0, 0, 0, 0, 230], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172451); -tree.insert([0, 0, 0, 0, 0, 0, 1, 112], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172455); -tree.insert([0, 0, 0, 0, 0, 0, 0, 45], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172457); -tree.insert([0, 0, 0, 0, 0, 0, 2, 224], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172458); -tree.insert([0, 0, 0, 0, 0, 0, 1, 124], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172461); -tree.insert([0, 0, 0, 0, 0, 0, 1, 111], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172462); -tree.insert([0, 0, 0, 0, 0, 0, 0, 80], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172463); -tree.insert([0, 0, 0, 0, 0, 0, 0, 253], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172465); -tree.insert([0, 0, 0, 0, 0, 0, 1, 8], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172466); -tree.insert([0, 0, 0, 0, 0, 0, 1, 85], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172470); -tree.insert([0, 0, 0, 0, 0, 0, 1, 60], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172471); -tree.insert([0, 0, 0, 0, 0, 0, 1, 250], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172472); -tree.insert([0, 0, 0, 0, 0, 0, 1, 154], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172479); -tree.insert([0, 0, 0, 0, 0, 0, 1, 138], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172486); -tree.insert([0, 0, 0, 0, 0, 0, 0, 46], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172488); -tree.insert([0, 0, 0, 0, 0, 0, 2, 41], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172489); -tree.insert([0, 0, 0, 0, 0, 0, 1, 224], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172490); -tree.insert([0, 0, 0, 0, 0, 0, 1, 228], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172491); -tree.insert([0, 0, 0, 0, 0, 0, 2, 35], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172493); -tree.insert([0, 0, 0, 0, 0, 0, 0, 75], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172494); -tree.insert([0, 0, 0, 0, 0, 0, 1, 183], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172495); -tree.insert([0, 0, 0, 0, 0, 0, 1, 20], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172496); -tree.insert([0, 0, 0, 0, 0, 0, 2, 243], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172497); -tree.insert([0, 0, 0, 0, 0, 0, 3, 46], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172498); -tree.insert([0, 0, 0, 0, 0, 0, 3, 55], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172508); -tree.insert([0, 0, 0, 0, 0, 0, 1, 195], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172510); -tree.insert([0, 0, 0, 0, 0, 0, 2, 222], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172511); -tree.insert([0, 0, 0, 0, 0, 0, 1, 97], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172512); -tree.insert([0, 0, 0, 0, 0, 0, 0, 134], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172513); -tree.insert([0, 0, 0, 0, 0, 0, 0, 206], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172514); -tree.insert([0, 0, 0, 0, 0, 0, 1, 236], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172516); -tree.insert([0, 0, 0, 0, 0, 0, 3, 34], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172519); -tree.insert([0, 0, 0, 0, 0, 0, 2, 7], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172523); -tree.insert([0, 0, 0, 0, 0, 0, 2, 13], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172525); -tree.insert([0, 0, 0, 0, 0, 0, 1, 201], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172527); -tree.insert([0, 0, 0, 0, 0, 0, 1, 63], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172528); -tree.insert([0, 0, 0, 0, 0, 0, 1, 156], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172529); -tree.insert([0, 0, 0, 0, 0, 0, 1, 246], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172535); -tree.insert([0, 0, 0, 0, 0, 0, 0, 78], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172536); -tree.insert([0, 0, 0, 0, 0, 0, 1, 117], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172537); -tree.insert([0, 0, 0, 0, 0, 0, 1, 62], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172538); -tree.insert([0, 0, 0, 0, 0, 0, 0, 94], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172541); -tree.insert([0, 0, 0, 0, 0, 0, 2, 71], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172542); -tree.insert([0, 0, 0, 0, 0, 0, 1, 145], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172547); -tree.insert([0, 0, 0, 0, 0, 0, 2, 178], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172548); -tree.insert([0, 0, 0, 0, 0, 0, 0, 252], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172549); -tree.insert([0, 0, 0, 0, 0, 0, 2, 154], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172551); -tree.insert([0, 0, 0, 0, 0, 0, 1, 140], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172554); -tree.insert([0, 0, 0, 0, 0, 0, 2, 98], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172555); -tree.insert([0, 0, 0, 0, 0, 0, 0, 24], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172556); -tree.insert([0, 0, 0, 0, 0, 0, 2, 159], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172558); -tree.insert([0, 0, 0, 0, 0, 0, 2, 220], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172559); -tree.insert([0, 0, 0, 0, 0, 0, 3, 43], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172560); -tree.insert([0, 0, 0, 0, 0, 0, 0, 249], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172562); -tree.insert([0, 0, 0, 0, 0, 0, 0, 11], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172563); -tree.insert([0, 0, 0, 0, 0, 0, 3, 19], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172566); -tree.insert([0, 0, 0, 0, 0, 0, 1, 36], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172567); -tree.insert([0, 0, 0, 0, 0, 0, 0, 19], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172570); -tree.insert([0, 0, 0, 0, 0, 0, 2, 171], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172574); -tree.insert([0, 0, 0, 0, 0, 0, 0, 222], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172579); -tree.insert([0, 0, 0, 0, 0, 0, 0, 201], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172582); -tree.insert([0, 0, 0, 0, 0, 0, 1, 119], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172584); -tree.insert([0, 0, 0, 0, 0, 0, 2, 113], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172586); -tree.insert([0, 0, 0, 0, 0, 0, 1, 130], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172587); -tree.insert([0, 0, 0, 0, 0, 0, 1, 17], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172591); -tree.insert([0, 0, 0, 0, 0, 0, 1, 91], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172595); -tree.insert([0, 0, 0, 0, 0, 0, 0, 33], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172599); -tree.flush_active_memtable(172501)?; -tree.insert([0, 0, 0, 0, 0, 0, 0, 114], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172602); -tree.insert([0, 0, 0, 0, 0, 0, 1, 113], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172605); -tree.insert([0, 0, 0, 0, 0, 0, 0, 100], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172609); -tree.insert([0, 0, 0, 0, 0, 0, 0, 28], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172610); -tree.insert([0, 0, 0, 0, 0, 0, 2, 117], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172614); -tree.insert([0, 0, 0, 0, 0, 0, 2, 52], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172615); -tree.insert([0, 0, 0, 0, 0, 0, 3, 35], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172616); -tree.insert([0, 0, 0, 0, 0, 0, 0, 231], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172619); -tree.insert([0, 0, 0, 0, 0, 0, 0, 34], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172625); -tree.insert([0, 0, 0, 0, 0, 0, 1, 147], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172626); -tree.insert([0, 0, 0, 0, 0, 0, 3, 3], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172627); -tree.insert([0, 0, 0, 0, 0, 0, 1, 247], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172628); -tree.insert([0, 0, 0, 0, 0, 0, 1, 190], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172631); -tree.insert([0, 0, 0, 0, 0, 0, 1, 164], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172632); -tree.insert([0, 0, 0, 0, 0, 0, 1, 137], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172633); -tree.insert([0, 0, 0, 0, 0, 0, 1, 168], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172634); -tree.insert([0, 0, 0, 0, 0, 0, 3, 12], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172637); -tree.insert([0, 0, 0, 0, 0, 0, 2, 183], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172638); -tree.insert([0, 0, 0, 0, 0, 0, 0, 113], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172645); -tree.insert([0, 0, 0, 0, 0, 0, 0, 233], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172649); -tree.insert([0, 0, 0, 0, 0, 0, 2, 67], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172650); -tree.insert([0, 0, 0, 0, 0, 0, 1, 203], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172655); -tree.insert([0, 0, 0, 0, 0, 0, 2, 80], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172656); -tree.insert([0, 0, 0, 0, 0, 0, 0, 179], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172658); -tree.insert([0, 0, 0, 0, 0, 0, 1, 45], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172660); -tree.insert([0, 0, 0, 0, 0, 0, 0, 2], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172661); -tree.insert([0, 0, 0, 0, 0, 0, 1, 93], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172663); -tree.insert([0, 0, 0, 0, 0, 0, 1, 28], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172665); -tree.insert([0, 0, 0, 0, 0, 0, 0, 164], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172672); -tree.insert([0, 0, 0, 0, 0, 0, 0, 60], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172675); -tree.insert([0, 0, 0, 0, 0, 0, 0, 170], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172679); -tree.insert([0, 0, 0, 0, 0, 0, 0, 92], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172684); -tree.insert([0, 0, 0, 0, 0, 0, 1, 197], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172688); -tree.insert([0, 0, 0, 0, 0, 0, 0, 169], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172693); -tree.insert([0, 0, 0, 0, 0, 0, 3, 23], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172700); -tree.insert([0, 0, 0, 0, 0, 0, 0, 153], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172702); -tree.insert([0, 0, 0, 0, 0, 0, 2, 4], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172704); -tree.insert([0, 0, 0, 0, 0, 0, 0, 84], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172705); -tree.insert([0, 0, 0, 0, 0, 0, 0, 250], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172706); -tree.insert([0, 0, 0, 0, 0, 0, 1, 218], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172708); -tree.insert([0, 0, 0, 0, 0, 0, 0, 132], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172728); -tree.insert([0, 0, 0, 0, 0, 0, 1, 10], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172733); -tree.insert([0, 0, 0, 0, 0, 0, 0, 63], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172735); -tree.insert([0, 0, 0, 0, 0, 0, 0, 77], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172737); -tree.insert([0, 0, 0, 0, 0, 0, 0, 35], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172740); -tree.insert([0, 0, 0, 0, 0, 0, 0, 103], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172742); -tree.insert([0, 0, 0, 0, 0, 0, 0, 16], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172749); -tree.insert([0, 0, 0, 0, 0, 0, 1, 38], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172750); -tree.insert([0, 0, 0, 0, 0, 0, 0, 155], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172755); -tree.insert([0, 0, 0, 0, 0, 0, 0, 255], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172756); -tree.insert([0, 0, 0, 0, 0, 0, 1, 46], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172761); -tree.insert([0, 0, 0, 0, 0, 0, 0, 10], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172765); -tree.insert([0, 0, 0, 0, 0, 0, 0, 82], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172766); -tree.insert([0, 0, 0, 0, 0, 0, 0, 177], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172767); -tree.insert([0, 0, 0, 0, 0, 0, 0, 199], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172774); -tree.insert([0, 0, 0, 0, 0, 0, 0, 106], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172786); -tree.insert([0, 0, 0, 0, 0, 0, 1, 34], [104, 101, 108, 108, 111, 104, 101, 108, 108, 111], 172788); -tree.flush_active_memtable(172701)?; -tree.compact(compaction.clone(), 172701)?; - -tree.compact(compaction.clone(), 172901)?; - -tree.compact(compaction.clone(), 173101)?; - - tree.drop_range::<&[u8], _>(..)?; - - assert_eq!(0, tree.table_count()); - assert_eq!(0, tree.blob_file_count()); - - Ok(()) -} diff --git a/crates/lsm-tree/tests/multi_trees.rs b/crates/lsm-tree/tests/multi_trees.rs index 5e516b277..3a0dc889e 100644 --- a/crates/lsm-tree/tests/multi_trees.rs +++ b/crates/lsm-tree/tests/multi_trees.rs @@ -1,4 +1,4 @@ -use lsm_tree::{get_tmp_folder, AbstractTree, Config, SequenceNumberCounter}; +use lsm_tree::{AbstractTree, Config, SequenceNumberCounter, get_tmp_folder}; use test_log::test; #[test] diff --git a/crates/lsm-tree/tests/mvcc_slab.rs b/crates/lsm-tree/tests/mvcc_slab.rs index 223ac5659..7806f87d6 100644 --- a/crates/lsm-tree/tests/mvcc_slab.rs +++ b/crates/lsm-tree/tests/mvcc_slab.rs @@ -1,5 +1,5 @@ use lsm_tree::{ - config::BlockSizePolicy, get_tmp_folder, AbstractTree, Config, SequenceNumberCounter, + AbstractTree, Config, SequenceNumberCounter, config::BlockSizePolicy, get_tmp_folder, }; use test_log::test; @@ -38,40 +38,3 @@ fn table_reader_mvcc_slab() -> lsm_tree::Result<()> { Ok(()) } - -#[test] -fn table_reader_mvcc_slab_blob() -> lsm_tree::Result<()> { - const ITEM_COUNT: usize = 1_000; - - let folder = get_tmp_folder(); - - let seqno = SequenceNumberCounter::default(); - - let tree = Config::new(&folder, seqno.clone(), SequenceNumberCounter::default()) - .data_block_size_policy(BlockSizePolicy::all(1_024)) - // .index_block_size_policy(BlockSizePolicy::all(1_024)) - .with_kv_separation(Some(Default::default())) - .open()?; - - for _ in 0..ITEM_COUNT { - tree.insert("a", "neptune".repeat(10_000), seqno.next()); - } - tree.insert("b", "", 0); - - tree.flush_active_memtable(0)?; - - let version = tree.current_version(); - - let table = version - .level(0) - .expect("level should exist") - .first() - .expect("run should exist") - .first() - .expect("table should exist"); - - let reader = table.iter(); - assert_eq!(reader.count(), ITEM_COUNT + 1); - - Ok(()) -} diff --git a/crates/lsm-tree/tests/recover_cleanup_orphans.rs b/crates/lsm-tree/tests/recover_cleanup_orphans.rs index b9f1d2f71..52fd04c81 100644 --- a/crates/lsm-tree/tests/recover_cleanup_orphans.rs +++ b/crates/lsm-tree/tests/recover_cleanup_orphans.rs @@ -1,4 +1,4 @@ -use lsm_tree::{get_tmp_folder, AbstractTree, Config, KvSeparationOptions, SequenceNumberCounter}; +use lsm_tree::{AbstractTree, Config, SequenceNumberCounter, get_tmp_folder}; use test_log::test; #[test] @@ -38,53 +38,3 @@ fn tree_recovery_cleanup_orphans() -> lsm_tree::Result<()> { Ok(()) } - -#[test] -fn tree_recovery_cleanup_orphans_blob() -> lsm_tree::Result<()> { - let folder = get_tmp_folder(); - - { - let tree = Config::new( - &folder, - SequenceNumberCounter::default(), - SequenceNumberCounter::default(), - ) - .with_kv_separation(Some( - KvSeparationOptions::default() - .age_cutoff(1.0) - .staleness_threshold(0.01), - )) - .open()?; - - tree.insert("a", "a".repeat(10_000), 0); - tree.insert("a", "a".repeat(10_000), 1); - tree.flush_active_memtable(0)?; - - assert!(folder.path().join("blobs").join("0").try_exists()?); - - tree.major_compact(u64::MAX, 5)?; - - assert!(folder.path().join("blobs").join("0").try_exists()?); - - tree.major_compact(u64::MAX, 10)?; - - assert!(folder.path().join("blobs").join("1").try_exists()?); - } - - std::fs::File::create(folder.path().join("blobs").join("0"))?; - - { - let _tree = Config::new( - &folder, - SequenceNumberCounter::default(), - SequenceNumberCounter::default(), - ) - .with_kv_separation(Some(Default::default())) - .open()?; - - assert!(!folder.path().join("blobs").join("0").try_exists()?); - assert!(folder.path().join("blobs").join("1").try_exists()?); - } - - Ok(()) -} diff --git a/crates/lsm-tree/tests/recovery_mac_ds_store.rs b/crates/lsm-tree/tests/recovery_mac_ds_store.rs index b767b60dc..c9af5990c 100644 --- a/crates/lsm-tree/tests/recovery_mac_ds_store.rs +++ b/crates/lsm-tree/tests/recovery_mac_ds_store.rs @@ -1,4 +1,4 @@ -use lsm_tree::{get_tmp_folder, AbstractTree, Config, SequenceNumberCounter}; +use lsm_tree::{AbstractTree, Config, SequenceNumberCounter, get_tmp_folder}; use test_log::test; #[test] diff --git a/crates/lsm-tree/tests/recovery_mac_underscore.rs b/crates/lsm-tree/tests/recovery_mac_underscore.rs index b32315c1c..b2dcdaade 100644 --- a/crates/lsm-tree/tests/recovery_mac_underscore.rs +++ b/crates/lsm-tree/tests/recovery_mac_underscore.rs @@ -1,4 +1,4 @@ -use lsm_tree::{get_tmp_folder, AbstractTree, Config, SequenceNumberCounter}; +use lsm_tree::{AbstractTree, Config, SequenceNumberCounter, get_tmp_folder}; use test_log::test; #[test] diff --git a/crates/lsm-tree/tests/regression_286.rs b/crates/lsm-tree/tests/regression_286.rs deleted file mode 100644 index f692262b0..000000000 --- a/crates/lsm-tree/tests/regression_286.rs +++ /dev/null @@ -1,32 +0,0 @@ -use lsm_tree::AbstractTree; - -#[test_log::test] -fn regression_286() -> lsm_tree::Result<()> { - let folder = lsm_tree::get_tmp_folder(); - let seqno = lsm_tree::SequenceNumberCounter::default(); - - { - let tree = lsm_tree::Config::new( - &folder, - seqno.clone(), - lsm_tree::SequenceNumberCounter::default(), - ) - .with_kv_separation(Some(lsm_tree::KvSeparationOptions::default())) - .open()?; - - tree.insert("foo", b"1", 0); - tree.clear()?; - } - - { - let _tree = lsm_tree::Config::new( - &folder, - seqno.clone(), - lsm_tree::SequenceNumberCounter::default(), - ) - .with_kv_separation(Some(lsm_tree::KvSeparationOptions::default())) - .open()?; - } - - Ok(()) -} diff --git a/crates/lsm-tree/tests/snapshot_compact.rs b/crates/lsm-tree/tests/snapshot_compact.rs index 60fbef86b..e02db1c89 100644 --- a/crates/lsm-tree/tests/snapshot_compact.rs +++ b/crates/lsm-tree/tests/snapshot_compact.rs @@ -1,4 +1,4 @@ -use lsm_tree::{get_tmp_folder, AbstractTree, Config, SequenceNumberCounter}; +use lsm_tree::{AbstractTree, Config, SequenceNumberCounter, get_tmp_folder}; use test_log::test; #[test] diff --git a/crates/lsm-tree/tests/snapshot_len.rs b/crates/lsm-tree/tests/snapshot_len.rs index 636b8520e..434198c24 100644 --- a/crates/lsm-tree/tests/snapshot_len.rs +++ b/crates/lsm-tree/tests/snapshot_len.rs @@ -1,4 +1,4 @@ -use lsm_tree::{get_tmp_folder, AbstractTree, Config, SeqNo, SequenceNumberCounter}; +use lsm_tree::{AbstractTree, Config, SeqNo, SequenceNumberCounter, get_tmp_folder}; use test_log::test; const ITEM_COUNT: usize = 100; diff --git a/crates/lsm-tree/tests/snapshot_point_read.rs b/crates/lsm-tree/tests/snapshot_point_read.rs index 0a4f5aa7d..14c9ad865 100644 --- a/crates/lsm-tree/tests/snapshot_point_read.rs +++ b/crates/lsm-tree/tests/snapshot_point_read.rs @@ -1,5 +1,5 @@ use lsm_tree::{ - config::BlockSizePolicy, get_tmp_folder, AbstractTree, Config, SeqNo, SequenceNumberCounter, + AbstractTree, Config, SeqNo, SequenceNumberCounter, config::BlockSizePolicy, get_tmp_folder, }; use test_log::test; diff --git a/crates/lsm-tree/tests/snapshot_zombie.rs b/crates/lsm-tree/tests/snapshot_zombie.rs index 1605051ac..c8c152859 100644 --- a/crates/lsm-tree/tests/snapshot_zombie.rs +++ b/crates/lsm-tree/tests/snapshot_zombie.rs @@ -1,4 +1,4 @@ -use lsm_tree::{get_tmp_folder, AbstractTree, Config, SeqNo, SequenceNumberCounter}; +use lsm_tree::{AbstractTree, Config, SeqNo, SequenceNumberCounter, get_tmp_folder}; use test_log::test; const ITEM_COUNT: usize = 5; diff --git a/crates/lsm-tree/tests/static_iterators.rs b/crates/lsm-tree/tests/static_iterators.rs index 83b7e0a91..a11351500 100644 --- a/crates/lsm-tree/tests/static_iterators.rs +++ b/crates/lsm-tree/tests/static_iterators.rs @@ -2,7 +2,7 @@ // This source code is licensed under both the Apache 2.0 and MIT License // (found in the LICENSE-* files in the repository) -use lsm_tree::{get_tmp_folder, AbstractTree, Config, Guard, SequenceNumberCounter}; +use lsm_tree::{AbstractTree, Config, Guard, SequenceNumberCounter, get_tmp_folder}; use test_log::test; /// Test that iterators can be stored in a struct (proving they're 'static) @@ -177,42 +177,6 @@ fn static_iterator_with_segments() -> lsm_tree::Result<()> { Ok(()) } -/// Test BlobTree static iterator -#[test] -fn static_iterator_blob_tree() -> lsm_tree::Result<()> { - let folder = get_tmp_folder(); - - let tree = Config::new( - &folder, - SequenceNumberCounter::default(), - SequenceNumberCounter::default(), - ) - .with_kv_separation(Some(Default::default())) - .open()?; - - // Insert values that will be separated to blob files - let large_value = vec![b'X'; 2048]; - for i in 0..20 { - tree.insert(format!("blob_{:03}", i), large_value.clone(), i as u64); - } - - tree.flush_active_memtable(20)?; - - let iter = tree.range("blob_000"..="blob_019", 20, None); - drop(tree); - - let results: Vec<_> = iter - .map(|guard| guard.into_inner()) - .collect::>>()?; - - assert_eq!(results.len(), 20); - for (_, value) in results { - assert_eq!(value.len(), 2048); - } - - Ok(()) -} - /// Test that iterator sees a consistent snapshot #[test] fn static_iterator_snapshot_isolation() -> lsm_tree::Result<()> { diff --git a/crates/lsm-tree/tests/table_full_file_checksum.rs b/crates/lsm-tree/tests/table_full_file_checksum.rs index c8f328a8c..29af94e12 100644 --- a/crates/lsm-tree/tests/table_full_file_checksum.rs +++ b/crates/lsm-tree/tests/table_full_file_checksum.rs @@ -1,4 +1,4 @@ -use lsm_tree::{get_tmp_folder, AbstractTree, Config, SequenceNumberCounter}; +use lsm_tree::{AbstractTree, Config, SequenceNumberCounter, get_tmp_folder}; use test_log::test; use xxhash_rust::xxh3::xxh3_128; diff --git a/crates/lsm-tree/tests/table_point_reads.rs b/crates/lsm-tree/tests/table_point_reads.rs index 95e59cd17..d9fc1222e 100644 --- a/crates/lsm-tree/tests/table_point_reads.rs +++ b/crates/lsm-tree/tests/table_point_reads.rs @@ -1,6 +1,5 @@ use lsm_tree::{ - config::BlockSizePolicy, get_tmp_folder, AbstractTree, Config, KvSeparationOptions, SeqNo, - SequenceNumberCounter, + AbstractTree, Config, SeqNo, SequenceNumberCounter, config::BlockSizePolicy, get_tmp_folder, }; use test_log::test; @@ -120,53 +119,3 @@ fn table_point_reads_mvcc_slab() -> lsm_tree::Result<()> { Ok(()) } - -#[test] -fn blob_tree_table_point_reads_mvcc_slab() -> lsm_tree::Result<()> { - let folder = get_tmp_folder(); - - let tree = Config::new( - &folder, - SequenceNumberCounter::default(), - SequenceNumberCounter::default(), - ) - .data_block_size_policy(BlockSizePolicy::all(1_024)) - .with_kv_separation(Some(KvSeparationOptions::default().separation_threshold(1))) - .open()?; - - let keys = [0, 1, 2] - .into_iter() - .map(u64::to_be_bytes) - .collect::>(); - - for key in &keys { - for seqno in 0..ITEM_COUNT as u64 { - tree.insert(key, seqno.to_string(), seqno); - } - } - tree.flush_active_memtable(0)?; - - for key in &keys { - let item = tree.get(key, SeqNo::MAX)?.unwrap(); - assert_eq!( - String::from_utf8_lossy(&item).parse::().unwrap(), - ITEM_COUNT as u64 - 1 - ); - } - - for key in &keys { - // NOTE: Need to start at seqno=1 - for seqno in 1..ITEM_COUNT as u64 { - let value = tree.get(key, seqno)?.unwrap(); - - // NOTE: When snapshot is =1, it will read any items with - // seqno less than 1 - assert_eq!( - String::from_utf8_lossy(&value).parse::().unwrap(), - seqno - 1 - ); - } - } - - Ok(()) -} diff --git a/crates/lsm-tree/tests/table_range.rs b/crates/lsm-tree/tests/table_range.rs index dddf7e72c..684360cb8 100644 --- a/crates/lsm-tree/tests/table_range.rs +++ b/crates/lsm-tree/tests/table_range.rs @@ -1,6 +1,6 @@ use lsm_tree::{ - config::BlockSizePolicy, get_tmp_folder, AbstractTree, Config, Guard, SeqNo, - SequenceNumberCounter, + AbstractTree, Config, Guard, SeqNo, SequenceNumberCounter, config::BlockSizePolicy, + get_tmp_folder, }; use test_log::test; diff --git a/crates/lsm-tree/tests/table_range_oob.rs b/crates/lsm-tree/tests/table_range_oob.rs index 5082176ee..dc006b612 100644 --- a/crates/lsm-tree/tests/table_range_oob.rs +++ b/crates/lsm-tree/tests/table_range_oob.rs @@ -1,5 +1,5 @@ use lsm_tree::{ - config::BlockSizePolicy, get_tmp_folder, AbstractTree, Config, SeqNo, SequenceNumberCounter, + AbstractTree, Config, SeqNo, SequenceNumberCounter, config::BlockSizePolicy, get_tmp_folder, }; use test_log::test; diff --git a/crates/lsm-tree/tests/table_remove_weak.rs b/crates/lsm-tree/tests/table_remove_weak.rs index 59dabd8b1..40c84bc22 100644 --- a/crates/lsm-tree/tests/table_remove_weak.rs +++ b/crates/lsm-tree/tests/table_remove_weak.rs @@ -1,5 +1,5 @@ use lsm_tree::{ - config::BlockSizePolicy, get_tmp_folder, AbstractTree, Config, SeqNo, SequenceNumberCounter, + AbstractTree, Config, SeqNo, SequenceNumberCounter, config::BlockSizePolicy, get_tmp_folder, }; use test_log::test; diff --git a/crates/lsm-tree/tests/table_weak_tombstones.rs b/crates/lsm-tree/tests/table_weak_tombstones.rs index b49ef8fab..44b56f6e0 100644 --- a/crates/lsm-tree/tests/table_weak_tombstones.rs +++ b/crates/lsm-tree/tests/table_weak_tombstones.rs @@ -1,4 +1,4 @@ -use lsm_tree::{get_tmp_folder, AbstractTree, Config, SequenceNumberCounter}; +use lsm_tree::{AbstractTree, Config, SequenceNumberCounter, get_tmp_folder}; #[test] fn weak_tombstone_counts_single_pair() -> lsm_tree::Result<()> { diff --git a/crates/lsm-tree/tests/tree_approx_len.rs b/crates/lsm-tree/tests/tree_approx_len.rs index bf770fbb2..f873c5069 100644 --- a/crates/lsm-tree/tests/tree_approx_len.rs +++ b/crates/lsm-tree/tests/tree_approx_len.rs @@ -1,4 +1,4 @@ -use lsm_tree::{get_tmp_folder, AbstractTree, Config, SeqNo, SequenceNumberCounter}; +use lsm_tree::{AbstractTree, Config, SeqNo, SequenceNumberCounter, get_tmp_folder}; use test_log::test; #[test] @@ -36,44 +36,6 @@ fn tree_approx_len_sealed() -> lsm_tree::Result<()> { Ok(()) } - -#[test] -fn tree_approx_len_sealed_blob() -> lsm_tree::Result<()> { - let folder = get_tmp_folder(); - - let tree = Config::new( - &folder, - SequenceNumberCounter::default(), - SequenceNumberCounter::default(), - ) - .with_kv_separation(Some(Default::default())) - .open()?; - - assert_eq!(tree.len(SeqNo::MAX, None)?, 0); - assert!(tree.is_empty(SeqNo::MAX, None)?); - assert_eq!(tree.approximate_len(), 0); - - tree.insert("a", "", 0); - - assert_eq!(tree.len(SeqNo::MAX, None)?, 1); - assert!(!tree.is_empty(SeqNo::MAX, None)?); - assert_eq!(tree.approximate_len(), 1); - - tree.insert("b", "", 0); - - assert_eq!(tree.len(SeqNo::MAX, None)?, 2); - assert!(!tree.is_empty(SeqNo::MAX, None)?); - assert_eq!(tree.approximate_len(), 2); - - let _ = tree.rotate_memtable().unwrap(); - - assert_eq!(tree.len(SeqNo::MAX, None)?, 2); - assert!(!tree.is_empty(SeqNo::MAX, None)?); - assert_eq!(tree.approximate_len(), 2); - - Ok(()) -} - #[test] fn tree_approx_len() -> lsm_tree::Result<()> { let folder = get_tmp_folder(); @@ -141,72 +103,3 @@ fn tree_approx_len() -> lsm_tree::Result<()> { Ok(()) } - -#[test] -fn tree_approx_len_blob() -> lsm_tree::Result<()> { - let folder = get_tmp_folder(); - - let tree = Config::new( - &folder, - SequenceNumberCounter::default(), - SequenceNumberCounter::default(), - ) - .with_kv_separation(Some(Default::default())) - .open()?; - - assert_eq!(tree.len(SeqNo::MAX, None)?, 0); - assert!(tree.is_empty(SeqNo::MAX, None)?); - assert_eq!(tree.approximate_len(), 0); - - tree.insert("a", "", 0); - - assert_eq!(tree.len(SeqNo::MAX, None)?, 1); - assert!(!tree.is_empty(SeqNo::MAX, None)?); - assert_eq!(tree.approximate_len(), 1); - - tree.insert("b", "", 0); - - assert_eq!(tree.len(SeqNo::MAX, None)?, 2); - assert!(!tree.is_empty(SeqNo::MAX, None)?); - assert_eq!(tree.approximate_len(), 2); - - tree.insert("a", "", 1); - - // Approximate count diverges - assert_eq!(tree.len(SeqNo::MAX, None)?, 2); - assert!(!tree.is_empty(SeqNo::MAX, None)?); - assert_eq!(tree.approximate_len(), 3); - - tree.remove("a", 2); - - assert_eq!(tree.len(SeqNo::MAX, None)?, 1); - assert!(!tree.is_empty(SeqNo::MAX, None)?); - assert_eq!(tree.approximate_len(), 4); - - tree.flush_active_memtable(0)?; - - assert_eq!(tree.len(SeqNo::MAX, None)?, 1); - assert!(!tree.is_empty(SeqNo::MAX, None)?); - assert_eq!(tree.approximate_len(), 4); - - tree.remove("b", 4); - - assert_eq!(tree.len(SeqNo::MAX, None)?, 0); - assert!(tree.is_empty(SeqNo::MAX, None)?); - assert_eq!(tree.approximate_len(), 5); - - tree.flush_active_memtable(0)?; - - assert_eq!(tree.len(SeqNo::MAX, None)?, 0); - assert!(tree.is_empty(SeqNo::MAX, None)?); - assert_eq!(tree.approximate_len(), 5); - - tree.major_compact(u64::MAX, 5)?; - - // Approximate count converges - assert_eq!(tree.len(SeqNo::MAX, None)?, 0); - assert!(tree.is_empty(SeqNo::MAX, None)?); - assert_eq!(tree.approximate_len(), 0); - - Ok(()) -} diff --git a/crates/lsm-tree/tests/tree_bulk_ingest.rs b/crates/lsm-tree/tests/tree_bulk_ingest.rs index 3e8e51a4a..428531895 100644 --- a/crates/lsm-tree/tests/tree_bulk_ingest.rs +++ b/crates/lsm-tree/tests/tree_bulk_ingest.rs @@ -1,6 +1,4 @@ -use lsm_tree::{ - get_tmp_folder, AbstractTree, Config, Guard, KvSeparationOptions, SeqNo, SequenceNumberCounter, -}; +use lsm_tree::{AbstractTree, Config, Guard, SeqNo, SequenceNumberCounter, get_tmp_folder}; use test_log::test; const ITEM_COUNT: usize = 100_000; @@ -99,107 +97,3 @@ fn tree_copy() -> lsm_tree::Result<()> { Ok(()) } - -#[test] -fn blob_tree_bulk_ingest() -> lsm_tree::Result<()> { - let folder = get_tmp_folder(); - - let seqno = SequenceNumberCounter::default(); - let visible_seqno = SequenceNumberCounter::default(); - - let tree = Config::new(&folder, seqno.clone(), visible_seqno.clone()) - .with_kv_separation(Some(KvSeparationOptions::default().separation_threshold(1))) - .open()?; - - let mut ingestion = tree.ingestion()?; - for x in 0..ITEM_COUNT as u64 { - let k = x.to_be_bytes(); - let v = nanoid::nanoid!(); - ingestion.write(k, v)?; - } - ingestion.finish()?; - - assert_eq!(visible_seqno.get(), seqno.get()); - - assert_eq!(tree.len(SeqNo::MAX, None)?, ITEM_COUNT); - assert_eq!( - tree.iter(SeqNo::MAX, None).flat_map(|x| x.key()).count(), - ITEM_COUNT, - ); - assert_eq!( - tree.iter(SeqNo::MAX, None) - .rev() - .flat_map(|x| x.key()) - .count(), - ITEM_COUNT, - ); - assert_eq!(1, tree.blob_file_count()); - - Ok(()) -} - -#[test] -fn blob_tree_copy() -> lsm_tree::Result<()> { - let folder = get_tmp_folder(); - - let seqno = SequenceNumberCounter::default(); - let visible_seqno = SequenceNumberCounter::default(); - - let src = Config::new(&folder, seqno.clone(), visible_seqno.clone()) - .with_kv_separation(Some(KvSeparationOptions::default().separation_threshold(1))) - .open()?; - - let mut ingestion = src.ingestion()?; - for x in 0..ITEM_COUNT as u64 { - let k = x.to_be_bytes(); - let v = nanoid::nanoid!(); - ingestion.write(k, v)?; - } - ingestion.finish()?; - - assert_eq!(visible_seqno.get(), seqno.get()); - - assert_eq!(src.len(SeqNo::MAX, None)?, ITEM_COUNT); - assert_eq!( - src.iter(SeqNo::MAX, None).flat_map(|x| x.key()).count(), - ITEM_COUNT, - ); - assert_eq!( - src.iter(SeqNo::MAX, None) - .rev() - .flat_map(|x| x.key()) - .count(), - ITEM_COUNT, - ); - assert_eq!(1, src.blob_file_count()); - - let folder = get_tmp_folder(); - let dest = Config::new(&folder, seqno.clone(), visible_seqno.clone()) - .with_kv_separation(Some(KvSeparationOptions::default().separation_threshold(1))) - .open()?; - - let mut ingestion = dest.ingestion()?; - for item in src.iter(SeqNo::MAX, None) { - let (k, v) = item.into_inner().unwrap(); - ingestion.write(k, v)?; - } - ingestion.finish()?; - - assert_eq!(visible_seqno.get(), seqno.get()); - - assert_eq!(dest.len(SeqNo::MAX, None)?, ITEM_COUNT); - assert_eq!( - dest.iter(SeqNo::MAX, None).flat_map(|x| x.key()).count(), - ITEM_COUNT, - ); - assert_eq!( - dest.iter(SeqNo::MAX, None) - .rev() - .flat_map(|x| x.key()) - .count(), - ITEM_COUNT, - ); - assert_eq!(1, dest.blob_file_count()); - - Ok(()) -} diff --git a/crates/lsm-tree/tests/tree_clear.rs b/crates/lsm-tree/tests/tree_clear.rs index 022a8db48..32a427a86 100644 --- a/crates/lsm-tree/tests/tree_clear.rs +++ b/crates/lsm-tree/tests/tree_clear.rs @@ -1,4 +1,4 @@ -use lsm_tree::{get_tmp_folder, AbstractTree, Config, SeqNo, SequenceNumberCounter}; +use lsm_tree::{AbstractTree, Config, SeqNo, SequenceNumberCounter, get_tmp_folder}; use test_log::test; #[test] diff --git a/crates/lsm-tree/tests/tree_clear_snapshot.rs b/crates/lsm-tree/tests/tree_clear_snapshot.rs index 21342f5f2..e4717190c 100644 --- a/crates/lsm-tree/tests/tree_clear_snapshot.rs +++ b/crates/lsm-tree/tests/tree_clear_snapshot.rs @@ -1,4 +1,4 @@ -use lsm_tree::{get_tmp_folder, AbstractTree, Config, SequenceNumberCounter}; +use lsm_tree::{AbstractTree, Config, SequenceNumberCounter, get_tmp_folder}; use test_log::test; #[test] diff --git a/crates/lsm-tree/tests/tree_count.rs b/crates/lsm-tree/tests/tree_count.rs index 01a8c6de8..020343d25 100644 --- a/crates/lsm-tree/tests/tree_count.rs +++ b/crates/lsm-tree/tests/tree_count.rs @@ -1,4 +1,4 @@ -use lsm_tree::{get_tmp_folder, AbstractTree, Config, Guard, SeqNo, SequenceNumberCounter, Slice}; +use lsm_tree::{AbstractTree, Config, Guard, SeqNo, SequenceNumberCounter, Slice, get_tmp_folder}; use test_log::test; const ITEM_COUNT: usize = 1_000; @@ -70,43 +70,6 @@ fn tree_flushed_count() -> lsm_tree::Result<()> { Ok(()) } - -#[test] -fn tree_flushed_count_blob() -> lsm_tree::Result<()> { - let folder = get_tmp_folder(); - - let tree = Config::new( - &folder, - SequenceNumberCounter::default(), - SequenceNumberCounter::default(), - ) - .with_kv_separation(Some(Default::default())) - .open()?; - - for x in 0..ITEM_COUNT as u64 { - let key = x.to_be_bytes(); - let value = nanoid::nanoid!(); - tree.insert(key, value.as_bytes(), 0); - } - - tree.flush_active_memtable(0)?; - - assert_eq!(tree.len(SeqNo::MAX, None)?, ITEM_COUNT); - assert_eq!( - tree.iter(SeqNo::MAX, None).flat_map(|x| x.key()).count(), - ITEM_COUNT - ); - assert_eq!( - tree.iter(SeqNo::MAX, None) - .rev() - .flat_map(|x| x.key()) - .count(), - ITEM_COUNT - ); - - Ok(()) -} - #[test] fn tree_non_locking_count() -> lsm_tree::Result<()> { use std::ops::Bound::{self, Excluded, Unbounded}; diff --git a/crates/lsm-tree/tests/tree_delete_loop.rs b/crates/lsm-tree/tests/tree_delete_loop.rs index fe29a801e..4aaf2174f 100644 --- a/crates/lsm-tree/tests/tree_delete_loop.rs +++ b/crates/lsm-tree/tests/tree_delete_loop.rs @@ -1,4 +1,4 @@ -use lsm_tree::{get_tmp_folder, AbstractTree, Config, Guard, SeqNo, SequenceNumberCounter}; +use lsm_tree::{AbstractTree, Config, Guard, SeqNo, SequenceNumberCounter, get_tmp_folder}; use test_log::test; #[test] diff --git a/crates/lsm-tree/tests/tree_different_block_size.rs b/crates/lsm-tree/tests/tree_different_block_size.rs index 9cb2137f3..dfb6fbe11 100644 --- a/crates/lsm-tree/tests/tree_different_block_size.rs +++ b/crates/lsm-tree/tests/tree_different_block_size.rs @@ -1,5 +1,5 @@ use lsm_tree::{ - config::BlockSizePolicy, get_tmp_folder, AbstractTree, Config, SeqNo, SequenceNumberCounter, + AbstractTree, Config, SeqNo, SequenceNumberCounter, config::BlockSizePolicy, get_tmp_folder, }; use test_log::test; diff --git a/crates/lsm-tree/tests/tree_disjoint_iter.rs b/crates/lsm-tree/tests/tree_disjoint_iter.rs index a53a3afad..1e81422a4 100644 --- a/crates/lsm-tree/tests/tree_disjoint_iter.rs +++ b/crates/lsm-tree/tests/tree_disjoint_iter.rs @@ -1,4 +1,4 @@ -use lsm_tree::{get_tmp_folder, AbstractTree, Config, Guard, SeqNo, SequenceNumberCounter, Slice}; +use lsm_tree::{AbstractTree, Config, Guard, SeqNo, SequenceNumberCounter, Slice, get_tmp_folder}; use test_log::test; macro_rules! iter_closed { diff --git a/crates/lsm-tree/tests/tree_disjoint_point_read.rs b/crates/lsm-tree/tests/tree_disjoint_point_read.rs index 895fe6615..c7cb79312 100644 --- a/crates/lsm-tree/tests/tree_disjoint_point_read.rs +++ b/crates/lsm-tree/tests/tree_disjoint_point_read.rs @@ -1,5 +1,5 @@ use lsm_tree::{ - config::BlockSizePolicy, get_tmp_folder, AbstractTree, Config, SeqNo, SequenceNumberCounter, + AbstractTree, Config, SeqNo, SequenceNumberCounter, config::BlockSizePolicy, get_tmp_folder, }; use test_log::test; @@ -35,37 +35,3 @@ fn tree_disjoint_point_read() -> lsm_tree::Result<()> { Ok(()) } - -#[test] -fn tree_disjoint_point_read_blob() -> lsm_tree::Result<()> { - let folder = get_tmp_folder(); - - let tree = Config::new( - &folder, - SequenceNumberCounter::default(), - SequenceNumberCounter::default(), - ) - .data_block_size_policy(BlockSizePolicy::all(1_024)) - // .index_block_size_policy(BlockSizePolicy::all(1_024)) - .with_kv_separation(Some(Default::default())) - .open()?; - - tree.insert("a", "a", 0); - tree.insert("b", "b", 0); - tree.insert("c", "c", 0); - - tree.flush_active_memtable(0)?; - - tree.insert("d", "d", 0); - tree.insert("e", "e", 0); - tree.insert("f", "f", 0); - - tree.flush_active_memtable(0)?; - - for key in [b"a", b"b", b"c", b"d", b"e", b"f"] { - let value = tree.get(key, SeqNo::MAX).unwrap().unwrap(); - assert_eq!(&*value, key) - } - - Ok(()) -} diff --git a/crates/lsm-tree/tests/tree_disjoint_prefix.rs b/crates/lsm-tree/tests/tree_disjoint_prefix.rs index fb1907b49..b75aaa181 100644 --- a/crates/lsm-tree/tests/tree_disjoint_prefix.rs +++ b/crates/lsm-tree/tests/tree_disjoint_prefix.rs @@ -1,4 +1,4 @@ -use lsm_tree::{get_tmp_folder, AbstractTree, Config, Guard, SeqNo, SequenceNumberCounter, Slice}; +use lsm_tree::{AbstractTree, Config, Guard, SeqNo, SequenceNumberCounter, Slice, get_tmp_folder}; use test_log::test; macro_rules! iter_closed { diff --git a/crates/lsm-tree/tests/tree_disjoint_range.rs b/crates/lsm-tree/tests/tree_disjoint_range.rs index d2b81c086..ace48243d 100644 --- a/crates/lsm-tree/tests/tree_disjoint_range.rs +++ b/crates/lsm-tree/tests/tree_disjoint_range.rs @@ -1,4 +1,4 @@ -use lsm_tree::{get_tmp_folder, AbstractTree, Config, Guard, SeqNo, SequenceNumberCounter, Slice}; +use lsm_tree::{AbstractTree, Config, Guard, SeqNo, SequenceNumberCounter, Slice, get_tmp_folder}; use test_log::test; macro_rules! iter_closed { diff --git a/crates/lsm-tree/tests/tree_drop_range.rs b/crates/lsm-tree/tests/tree_drop_range.rs deleted file mode 100644 index 02cdd75de..000000000 --- a/crates/lsm-tree/tests/tree_drop_range.rs +++ /dev/null @@ -1,277 +0,0 @@ -use lsm_tree::{get_tmp_folder, AbstractTree, AnyTree, Config, SeqNo, SequenceNumberCounter}; -use std::ops::Bound::{Excluded, Included, Unbounded}; - -fn populate_tables(tree: &AnyTree) -> lsm_tree::Result<()> { - for key in 'a'..='e' { - tree.insert([key as u8], "", 0); - tree.flush_active_memtable(0)?; - } - Ok(()) -} - -#[test] -fn tree_drop_range_basic() -> lsm_tree::Result<()> { - let folder = get_tmp_folder(); - - let tree = Config::new( - &folder, - SequenceNumberCounter::default(), - SequenceNumberCounter::default(), - ) - .open()?; - - populate_tables(&tree)?; - - assert_eq!(1, tree.l0_run_count()); - assert_eq!(5, tree.table_count()); - - tree.drop_range("a"..="c")?; - - assert!(!tree.contains_key("a", SeqNo::MAX)?); - assert!(!tree.contains_key("b", SeqNo::MAX)?); - assert!(!tree.contains_key("c", SeqNo::MAX)?); - assert!(tree.contains_key("d", SeqNo::MAX)?); - assert!(tree.contains_key("e", SeqNo::MAX)?); - - assert_eq!(1, tree.l0_run_count()); - assert_eq!(2, tree.table_count()); - - Ok(()) -} - -#[test] -fn tree_drop_range_partial_table_overlap_kept() -> lsm_tree::Result<()> { - let folder = get_tmp_folder(); - - let tree = Config::new( - &folder, - SequenceNumberCounter::default(), - SequenceNumberCounter::default(), - ) - .open()?; - - for key in ['a', 'b', 'c', 'd', 'e'] { - tree.insert([key as u8], "", 0); - } - tree.flush_active_memtable(0)?; - - assert_eq!(1, tree.l0_run_count()); - assert_eq!(1, tree.table_count()); - - tree.drop_range("b".."d")?; - - for key in ['a', 'b', 'c', 'd', 'e'] { - assert!(tree.contains_key([key as u8], SeqNo::MAX)?); - } - - assert_eq!(1, tree.l0_run_count()); - assert_eq!(1, tree.table_count()); - - Ok(()) -} - -#[test] -fn tree_drop_range_upper_exclusive() -> lsm_tree::Result<()> { - let folder = get_tmp_folder(); - - let tree = Config::new( - &folder, - SequenceNumberCounter::default(), - SequenceNumberCounter::default(), - ) - .open()?; - - populate_tables(&tree)?; - - tree.drop_range("a".."d")?; - - assert!(!tree.contains_key("a", SeqNo::MAX)?); - assert!(!tree.contains_key("b", SeqNo::MAX)?); - assert!(!tree.contains_key("c", SeqNo::MAX)?); - assert!(tree.contains_key("d", SeqNo::MAX)?); - assert!(tree.contains_key("e", SeqNo::MAX)?); - - Ok(()) -} - -#[test] -fn tree_drop_range_lower_exclusive() -> lsm_tree::Result<()> { - let folder = get_tmp_folder(); - - let tree = Config::new( - &folder, - SequenceNumberCounter::default(), - SequenceNumberCounter::default(), - ) - .open()?; - - populate_tables(&tree)?; - - tree.drop_range::<&str, _>((Excluded("a"), Included("c")))?; - - assert!(tree.contains_key("a", SeqNo::MAX)?); - assert!(!tree.contains_key("b", SeqNo::MAX)?); - assert!(!tree.contains_key("c", SeqNo::MAX)?); - assert!(tree.contains_key("d", SeqNo::MAX)?); - - Ok(()) -} - -#[test] -fn tree_drop_range_unbounded_lower_inclusive_upper() -> lsm_tree::Result<()> { - let folder = get_tmp_folder(); - - let tree = Config::new( - &folder, - SequenceNumberCounter::default(), - SequenceNumberCounter::default(), - ) - .open()?; - - populate_tables(&tree)?; - - tree.drop_range::<&str, _>((Unbounded, Included("c")))?; - - assert!(!tree.contains_key("a", SeqNo::MAX)?); - assert!(!tree.contains_key("b", SeqNo::MAX)?); - assert!(!tree.contains_key("c", SeqNo::MAX)?); - assert!(tree.contains_key("d", SeqNo::MAX)?); - assert!(tree.contains_key("e", SeqNo::MAX)?); - - Ok(()) -} - -#[test] -fn tree_drop_range_unbounded_lower_exclusive_upper() -> lsm_tree::Result<()> { - let folder = get_tmp_folder(); - - let tree = Config::new( - &folder, - SequenceNumberCounter::default(), - SequenceNumberCounter::default(), - ) - .open()?; - - populate_tables(&tree)?; - - tree.drop_range::<&str, _>((Unbounded, Excluded("d")))?; - - assert!(!tree.contains_key("a", SeqNo::MAX)?); - assert!(!tree.contains_key("b", SeqNo::MAX)?); - assert!(!tree.contains_key("c", SeqNo::MAX)?); - assert!(tree.contains_key("d", SeqNo::MAX)?); - - Ok(()) -} - -#[test] -fn tree_drop_range_exclusive_empty_interval() -> lsm_tree::Result<()> { - let folder = get_tmp_folder(); - - let tree = Config::new( - &folder, - SequenceNumberCounter::default(), - SequenceNumberCounter::default(), - ) - .open()?; - - populate_tables(&tree)?; - - tree.drop_range::<&str, _>((Excluded("b"), Excluded("b")))?; - - assert!(tree.contains_key("a", SeqNo::MAX)?); - assert!(tree.contains_key("b", SeqNo::MAX)?); - assert!(tree.contains_key("c", SeqNo::MAX)?); - - Ok(()) -} - -#[test] -fn tree_drop_range_empty_tree() -> lsm_tree::Result<()> { - let folder = get_tmp_folder(); - - let tree = Config::new( - &folder, - SequenceNumberCounter::default(), - SequenceNumberCounter::default(), - ) - .open()?; - - tree.drop_range("a"..="c")?; - - assert_eq!(0, tree.l0_run_count()); - assert_eq!(0, tree.table_count()); - - Ok(()) -} - -#[test] -fn tree_drop_range_unbounded_upper() -> lsm_tree::Result<()> { - let folder = get_tmp_folder(); - - let tree = Config::new( - &folder, - SequenceNumberCounter::default(), - SequenceNumberCounter::default(), - ) - .open()?; - - populate_tables(&tree)?; - - tree.drop_range("c"..)?; - - assert!(tree.contains_key("a", SeqNo::MAX)?); - assert!(tree.contains_key("b", SeqNo::MAX)?); - assert!(!tree.contains_key("c", SeqNo::MAX)?); - assert!(!tree.contains_key("d", SeqNo::MAX)?); - assert!(!tree.contains_key("e", SeqNo::MAX)?); - - Ok(()) -} - -#[test] -fn tree_drop_range_clear_all() -> lsm_tree::Result<()> { - let folder = get_tmp_folder(); - - let tree = Config::new( - &folder, - SequenceNumberCounter::default(), - SequenceNumberCounter::default(), - ) - .open()?; - - populate_tables(&tree)?; - - tree.drop_range::<&str, _>(..)?; - - assert_eq!(0, tree.l0_run_count()); - assert_eq!(0, tree.table_count()); - assert!(!tree.contains_key("a", SeqNo::MAX)?); - assert!(!tree.contains_key("e", SeqNo::MAX)?); - - Ok(()) -} - -#[test] -fn tree_drop_range_inverted_bounds_is_noop() -> lsm_tree::Result<()> { - let folder = get_tmp_folder(); - - let tree = Config::new( - &folder, - SequenceNumberCounter::default(), - SequenceNumberCounter::default(), - ) - .open()?; - - populate_tables(&tree)?; - - tree.drop_range("c".."a")?; - tree.drop_range("c"..="a")?; - - // All keys remain because the range is treated as empty. - for key in 'a'..='e' { - assert!(tree.contains_key([key as u8], SeqNo::MAX)?); - } - - Ok(()) -} diff --git a/crates/lsm-tree/tests/tree_filter_hit_rate.rs b/crates/lsm-tree/tests/tree_filter_hit_rate.rs deleted file mode 100644 index 281eb2e1c..000000000 --- a/crates/lsm-tree/tests/tree_filter_hit_rate.rs +++ /dev/null @@ -1,86 +0,0 @@ -#[test_log::test] -#[cfg(feature = "metrics")] -fn tree_filter_hit_rate() -> lsm_tree::Result<()> { - use lsm_tree::{get_tmp_folder, AbstractTree, Config, SeqNo, SequenceNumberCounter}; - - let a = { - let folder = get_tmp_folder(); - let path = folder.path(); - - let seqno = SequenceNumberCounter::default(); - - let tree = Config::new(path, seqno.clone(), SequenceNumberCounter::default()).open()?; - - for k in 0u64..10_000 { - tree.insert(k.to_be_bytes(), "abc", seqno.next()); - } - tree.flush_active_memtable(0)?; - tree.major_compact(u64::MAX, 0)?; - - for k in 0u64..10_000 { - tree.get(k.to_be_bytes(), SeqNo::MAX).unwrap().unwrap(); - } - - tree.metrics().filter_efficiency() - }; - - { - let folder = get_tmp_folder(); - let path = folder.path(); - - let seqno = SequenceNumberCounter::default(); - - let tree = Config::new(path, seqno.clone(), SequenceNumberCounter::default()) - .with_kv_separation(Some(KvSeparationOptions::default().separation_threshold(1))) - .open()?; - - for k in 0u64..10_000 { - tree.insert(k.to_be_bytes(), "abc", seqno.next()); - } - tree.flush_active_memtable(0)?; - tree.major_compact(u64::MAX, 0)?; - - for k in 0u64..10_000 { - tree.get(k.to_be_bytes(), SeqNo::MAX).unwrap().unwrap(); - } - - assert_eq!(a, tree.metrics().filter_efficiency()); - } - - Ok(()) -} - -#[test_log::test] -#[cfg(feature = "metrics")] -fn tree_filter_hit_rate_hit() -> lsm_tree::Result<()> { - use lsm_tree::{ - get_tmp_folder, AbstractTree, Config, KvSeparationOptions, SeqNo, SequenceNumberCounter, - }; - - let folder = get_tmp_folder(); - let path = folder.path(); - - let seqno = SequenceNumberCounter::default(); - - let tree = Config::new(path, seqno.clone(), SequenceNumberCounter::default()).open()?; - - tree.insert("a", "a", 0); - tree.insert("c", "a", 0); - tree.flush_active_memtable(0)?; - - assert!(tree.get("b", SeqNo::MAX)?.is_none()); - assert_eq!(1, tree.metrics().filter_queries()); - assert_eq!(1, tree.metrics().io_skipped_by_filter()); - assert_eq!(1.0, tree.metrics().filter_efficiency()); - - assert!(tree.get("a", SeqNo::MAX)?.is_some()); - assert_eq!(1.0, tree.metrics().filter_efficiency()); - - assert!(tree.get("a", SeqNo::MAX)?.is_some()); - assert_eq!(1.0, tree.metrics().filter_efficiency()); - - assert!(tree.get("a", SeqNo::MAX)?.is_some()); - assert_eq!(1.0, tree.metrics().filter_efficiency()); - - Ok(()) -} diff --git a/crates/lsm-tree/tests/tree_flush_eviction.rs b/crates/lsm-tree/tests/tree_flush_eviction.rs index 714092db7..f3e43bc40 100644 --- a/crates/lsm-tree/tests/tree_flush_eviction.rs +++ b/crates/lsm-tree/tests/tree_flush_eviction.rs @@ -1,4 +1,4 @@ -use lsm_tree::{get_tmp_folder, AbstractTree, SeqNo, SequenceNumberCounter}; +use lsm_tree::{AbstractTree, SeqNo, SequenceNumberCounter, get_tmp_folder}; use std::sync::Arc; use test_log::test; diff --git a/crates/lsm-tree/tests/tree_guarded_range.rs b/crates/lsm-tree/tests/tree_guarded_range.rs index 40d0623f3..ddc9c4e3e 100644 --- a/crates/lsm-tree/tests/tree_guarded_range.rs +++ b/crates/lsm-tree/tests/tree_guarded_range.rs @@ -1,4 +1,4 @@ -use lsm_tree::{get_tmp_folder, AbstractTree, Config, Guard, SeqNo, SequenceNumberCounter}; +use lsm_tree::{AbstractTree, Config, Guard, SeqNo, SequenceNumberCounter, get_tmp_folder}; use test_log::test; #[test] @@ -35,39 +35,3 @@ fn tree_guarded_range() -> lsm_tree::Result<()> { Ok(()) } - -#[test] -fn blob_tree_guarded_range() -> lsm_tree::Result<()> { - let folder = get_tmp_folder(); - - let tree = Config::new( - &folder, - SequenceNumberCounter::default(), - SequenceNumberCounter::default(), - ) - .with_kv_separation(Some(Default::default())) - .open()?; - - tree.insert("a".as_bytes(), nanoid::nanoid!().as_bytes(), 0); - tree.insert("f".as_bytes(), nanoid::nanoid!().as_bytes(), 1); - tree.insert("g".as_bytes(), nanoid::nanoid!().as_bytes(), 2); - - tree.insert("a".as_bytes(), nanoid::nanoid!().as_bytes(), 3); - tree.insert("f".as_bytes(), nanoid::nanoid!().as_bytes(), 4); - tree.insert("g".as_bytes(), nanoid::nanoid!().as_bytes(), 5); - - assert_eq!( - 2, - tree.range("a"..="f", SeqNo::MAX, None) - .flat_map(Guard::key) - .count(), - ); - assert_eq!( - 2, - tree.range("f"..="g", SeqNo::MAX, None) - .flat_map(Guard::key) - .count(), - ); - - Ok(()) -} diff --git a/crates/lsm-tree/tests/tree_kv.rs b/crates/lsm-tree/tests/tree_kv.rs index af1d4d326..02835a66e 100644 --- a/crates/lsm-tree/tests/tree_kv.rs +++ b/crates/lsm-tree/tests/tree_kv.rs @@ -1,4 +1,4 @@ -use lsm_tree::{get_tmp_folder, AbstractTree, Config, Guard, SeqNo, SequenceNumberCounter}; +use lsm_tree::{AbstractTree, Config, Guard, SeqNo, SequenceNumberCounter, get_tmp_folder}; use test_log::test; #[test] diff --git a/crates/lsm-tree/tests/tree_l0_point_read.rs b/crates/lsm-tree/tests/tree_l0_point_read.rs deleted file mode 100644 index 230213a30..000000000 --- a/crates/lsm-tree/tests/tree_l0_point_read.rs +++ /dev/null @@ -1,48 +0,0 @@ -use lsm_tree::{get_tmp_folder, AbstractTree, SeqNo, SequenceNumberCounter}; -use test_log::test; - -#[test] -fn tree_l0_point_read() -> lsm_tree::Result<()> { - let folder = get_tmp_folder(); - - let tree = lsm_tree::Config::new( - &folder, - SequenceNumberCounter::default(), - SequenceNumberCounter::default(), - ) - .with_kv_separation(Some(Default::default())) - .open()?; - - tree.insert("a", "a", 0); - tree.insert("b", "b", 0); - tree.insert("c", "c", 0); - tree.flush_active_memtable(0)?; - - tree.insert("a", "a", 1); - tree.insert("b", "b", 1); - tree.insert("c", "c", 1); - tree.flush_active_memtable(0)?; - - tree.insert("a", "A", 2); - tree.insert("b", "B", 2); - tree.insert("c", "C", 2); - tree.flush_active_memtable(0)?; - - tree.insert("d", "d", 3); - tree.insert("e", "e", 3); - tree.insert("f", "f", 3); - tree.flush_active_memtable(0)?; - - tree.insert("g", "g", 3); - tree.flush_active_memtable(0)?; - - assert_eq!(b"A", &*tree.get("a", SeqNo::MAX)?.unwrap()); - assert_eq!(b"B", &*tree.get("b", SeqNo::MAX)?.unwrap()); - assert_eq!(b"C", &*tree.get("c", SeqNo::MAX)?.unwrap()); - assert_eq!(b"d", &*tree.get("d", SeqNo::MAX)?.unwrap()); - assert_eq!(b"e", &*tree.get("e", SeqNo::MAX)?.unwrap()); - assert_eq!(b"f", &*tree.get("f", SeqNo::MAX)?.unwrap()); - assert_eq!(b"g", &*tree.get("g", SeqNo::MAX)?.unwrap()); - - Ok(()) -} diff --git a/crates/lsm-tree/tests/tree_l0_range.rs b/crates/lsm-tree/tests/tree_l0_range.rs deleted file mode 100644 index a8fbc0f12..000000000 --- a/crates/lsm-tree/tests/tree_l0_range.rs +++ /dev/null @@ -1,51 +0,0 @@ -use lsm_tree::{get_tmp_folder, AbstractTree, Guard, SeqNo, SequenceNumberCounter}; -use test_log::test; - -#[test] -fn tree_l0_range_blob() -> lsm_tree::Result<()> { - let folder = get_tmp_folder(); - - let tree = lsm_tree::Config::new( - &folder, - SequenceNumberCounter::default(), - SequenceNumberCounter::default(), - ) - .with_kv_separation(Some(Default::default())) - .open()?; - - tree.insert("a", "a", 0); - tree.insert("b", "b", 0); - tree.insert("c", "c", 0); - tree.flush_active_memtable(0)?; - - tree.insert("a", "a", 1); - tree.insert("b", "b", 1); - tree.insert("c", "c", 1); - tree.flush_active_memtable(0)?; - - tree.insert("a", "A", 2); - tree.insert("b", "B", 2); - tree.insert("c", "C", 2); - tree.flush_active_memtable(0)?; - - tree.insert("d", "d", 3); - tree.insert("e", "e", 3); - tree.insert("f", "f", 3); - tree.flush_active_memtable(0)?; - - tree.insert("g", "g".repeat(10_000), 3); - tree.flush_active_memtable(0)?; - - let mut range = tree.range("c"..="e", SeqNo::MAX, None); - assert_eq!(b"C", &*range.next().unwrap().value()?); - assert_eq!(b"d", &*range.next().unwrap().value()?); - assert_eq!(b"e", &*range.next().unwrap().value()?); - assert!(range.next().is_none()); - - let mut range = tree.range("f"..="g", SeqNo::MAX, None).rev(); - assert_eq!(b"g".repeat(10_000), &*range.next().unwrap().value()?); - assert_eq!(b"f", &*range.next().unwrap().value()?); - assert!(range.next().is_none()); - - Ok(()) -} diff --git a/crates/lsm-tree/tests/tree_major_compaction.rs b/crates/lsm-tree/tests/tree_major_compaction.rs index 010efac19..6b49a7568 100644 --- a/crates/lsm-tree/tests/tree_major_compaction.rs +++ b/crates/lsm-tree/tests/tree_major_compaction.rs @@ -1,4 +1,4 @@ -use lsm_tree::{get_tmp_folder, AbstractTree, Config, SeqNo, SequenceNumberCounter}; +use lsm_tree::{AbstractTree, Config, SeqNo, SequenceNumberCounter, get_tmp_folder}; use test_log::test; #[test] diff --git a/crates/lsm-tree/tests/tree_mvcc_simple.rs b/crates/lsm-tree/tests/tree_mvcc_simple.rs index 4d4853929..820a8a446 100644 --- a/crates/lsm-tree/tests/tree_mvcc_simple.rs +++ b/crates/lsm-tree/tests/tree_mvcc_simple.rs @@ -1,4 +1,4 @@ -use lsm_tree::{get_tmp_folder, AbstractTree, Config, Guard, SeqNo, SequenceNumberCounter}; +use lsm_tree::{AbstractTree, Config, Guard, SeqNo, SequenceNumberCounter, get_tmp_folder}; use test_log::test; #[test] diff --git a/crates/lsm-tree/tests/tree_non_disjoint_point_read.rs b/crates/lsm-tree/tests/tree_non_disjoint_point_read.rs index 2cbbe8ccd..63063b243 100644 --- a/crates/lsm-tree/tests/tree_non_disjoint_point_read.rs +++ b/crates/lsm-tree/tests/tree_non_disjoint_point_read.rs @@ -1,5 +1,5 @@ use lsm_tree::{ - config::BlockSizePolicy, get_tmp_folder, AbstractTree, Config, SeqNo, SequenceNumberCounter, + AbstractTree, Config, SeqNo, SequenceNumberCounter, config::BlockSizePolicy, get_tmp_folder, }; use test_log::test; diff --git a/crates/lsm-tree/tests/tree_range.rs b/crates/lsm-tree/tests/tree_range.rs index 4d6ee2bbc..806b4d79e 100644 --- a/crates/lsm-tree/tests/tree_range.rs +++ b/crates/lsm-tree/tests/tree_range.rs @@ -1,4 +1,4 @@ -use lsm_tree::{get_tmp_folder, AbstractTree, Config, SeqNo, SequenceNumberCounter}; +use lsm_tree::{AbstractTree, Config, SeqNo, SequenceNumberCounter, get_tmp_folder}; use test_log::test; #[test] @@ -65,69 +65,3 @@ fn tree_range_count() -> lsm_tree::Result<()> { Ok(()) } - -#[test] -fn blob_tree_range_count() -> lsm_tree::Result<()> { - use std::ops::Bound::{self, Excluded, Unbounded}; - - let folder = get_tmp_folder(); - - let tree = Config::new( - &folder, - SequenceNumberCounter::default(), - SequenceNumberCounter::default(), - ) - .with_kv_separation(Some(Default::default())) - .open()?; - - tree.insert("a".as_bytes(), nanoid::nanoid!().as_bytes(), 0); - tree.insert("f".as_bytes(), nanoid::nanoid!().as_bytes(), 1); - tree.insert("g".as_bytes(), b"neptune!".repeat(128_000), 2); - - tree.insert("a".as_bytes(), nanoid::nanoid!().as_bytes(), 3); - tree.insert("f".as_bytes(), b"neptune!".repeat(128_000), 4); - tree.insert("g".as_bytes(), nanoid::nanoid!().as_bytes(), 5); - - assert_eq!(2, tree.range("a"..="f", SeqNo::MAX, None).count()); - assert_eq!(2, tree.range("f"..="g", SeqNo::MAX, None).count()); - - assert_eq!( - 1, - tree.range::, (Bound>, Bound>)>( - (Excluded("f".into()), Unbounded), - SeqNo::MAX, - None - ) - .count() - ); - - tree.flush_active_memtable(0)?; - - assert_eq!(2, tree.range("a"..="f", SeqNo::MAX, None).count()); - assert_eq!( - 1, - tree.range::, (Bound>, Bound>)>( - (Excluded("f".into()), Unbounded), - SeqNo::MAX, - None - ) - .count() - ); - - tree.insert("a".as_bytes(), nanoid::nanoid!().as_bytes(), 6); - tree.insert("f".as_bytes(), nanoid::nanoid!().as_bytes(), 7); - tree.insert("g".as_bytes(), nanoid::nanoid!().as_bytes(), 8); - - assert_eq!(2, tree.range("a"..="f", SeqNo::MAX, None).count()); - assert_eq!( - 1, - tree.range::, (Bound>, Bound>)>( - (Excluded("f".into()), Unbounded), - SeqNo::MAX, - None - ) - .count() - ); - - Ok(()) -} diff --git a/crates/lsm-tree/tests/tree_range_memtable_only.rs b/crates/lsm-tree/tests/tree_range_memtable_only.rs index 19038d7e9..5742cfc26 100644 --- a/crates/lsm-tree/tests/tree_range_memtable_only.rs +++ b/crates/lsm-tree/tests/tree_range_memtable_only.rs @@ -1,4 +1,4 @@ -use lsm_tree::{get_tmp_folder, AbstractTree, Config, Guard, SeqNo, SequenceNumberCounter}; +use lsm_tree::{AbstractTree, Config, Guard, SeqNo, SequenceNumberCounter, get_tmp_folder}; use test_log::test; #[test] diff --git a/crates/lsm-tree/tests/tree_recover_counter.rs b/crates/lsm-tree/tests/tree_recover_counter.rs index e8474c627..5f061bb46 100644 --- a/crates/lsm-tree/tests/tree_recover_counter.rs +++ b/crates/lsm-tree/tests/tree_recover_counter.rs @@ -1,4 +1,4 @@ -use lsm_tree::{get_tmp_folder, AbstractTree, Config, SequenceNumberCounter}; +use lsm_tree::{AbstractTree, Config, SequenceNumberCounter, get_tmp_folder}; use test_log::test; #[test] diff --git a/crates/lsm-tree/tests/tree_recover_large_value.rs b/crates/lsm-tree/tests/tree_recover_large_value.rs index 102d08dc5..4844c2b1a 100644 --- a/crates/lsm-tree/tests/tree_recover_large_value.rs +++ b/crates/lsm-tree/tests/tree_recover_large_value.rs @@ -1,4 +1,4 @@ -use lsm_tree::{get_tmp_folder, AbstractTree, Config, SeqNo, SequenceNumberCounter}; +use lsm_tree::{AbstractTree, Config, SeqNo, SequenceNumberCounter, get_tmp_folder}; use test_log::test; #[test] diff --git a/crates/lsm-tree/tests/tree_recovery_versions.rs b/crates/lsm-tree/tests/tree_recovery_versions.rs index b832f5554..888c11b95 100644 --- a/crates/lsm-tree/tests/tree_recovery_versions.rs +++ b/crates/lsm-tree/tests/tree_recovery_versions.rs @@ -1,4 +1,4 @@ -use lsm_tree::{get_tmp_folder, AbstractTree, Config, SequenceNumberCounter}; +use lsm_tree::{AbstractTree, Config, SequenceNumberCounter, get_tmp_folder}; use test_log::test; #[test] diff --git a/crates/lsm-tree/tests/tree_reload.rs b/crates/lsm-tree/tests/tree_reload.rs index 0d396388a..fff0ebbf7 100644 --- a/crates/lsm-tree/tests/tree_reload.rs +++ b/crates/lsm-tree/tests/tree_reload.rs @@ -1,6 +1,4 @@ -use lsm_tree::{ - get_tmp_folder, AbstractTree, Config, Guard, SeqNo, SequenceNumberCounter, TreeType, -}; +use lsm_tree::{AbstractTree, Config, Guard, SeqNo, SequenceNumberCounter, get_tmp_folder}; use test_log::test; const ITEM_COUNT: usize = 10_000; @@ -38,79 +36,6 @@ fn tree_reload_smoke_test() -> lsm_tree::Result<()> { Ok(()) } - -#[test] -fn tree_reload_smoke_test_blob() -> lsm_tree::Result<()> { - let folder = get_tmp_folder(); - - let large_value = "a".repeat(10_000); - - { - let tree = Config::new( - &folder, - SequenceNumberCounter::default(), - SequenceNumberCounter::default(), - ) - .with_kv_separation(Some(Default::default())) - .open()?; - - assert_eq!(0, tree.table_count()); - - tree.insert("a", &large_value, 0); - tree.flush_active_memtable(0)?; - - assert_eq!(1, tree.table_count()); - assert!(tree.contains_key("a", SeqNo::MAX)?); - } - - { - let tree = Config::new( - &folder, - SequenceNumberCounter::default(), - SequenceNumberCounter::default(), - ) - .with_kv_separation(Some(Default::default())) - .open()?; - - assert_eq!(1, tree.table_count()); - assert_eq!(large_value.as_bytes(), tree.get("a", SeqNo::MAX)?.unwrap()); - } - - Ok(()) -} - -#[test] -fn tree_reload_blob_again_without_opts() -> lsm_tree::Result<()> { - let folder = get_tmp_folder(); - - { - let tree = Config::new( - &folder, - SequenceNumberCounter::default(), - SequenceNumberCounter::default(), - ) - .with_kv_separation(Some(Default::default())) - .open()?; - - assert_eq!(0, tree.table_count()); - assert_eq!(0, tree.blob_file_count()); - } - - { - let tree = Config::new( - &folder, - SequenceNumberCounter::default(), - SequenceNumberCounter::default(), - ) - .with_kv_separation(None) - .open(); - - assert!(matches!(tree, Err(lsm_tree::Error::Unrecoverable))); - } - - Ok(()) -} - #[test] fn tree_reload_empty() -> lsm_tree::Result<()> { let folder = get_tmp_folder(); @@ -132,7 +57,6 @@ fn tree_reload_empty() -> lsm_tree::Result<()> { .count(), 0 ); - assert_eq!(tree.tree_type(), TreeType::Standard); } { @@ -152,7 +76,6 @@ fn tree_reload_empty() -> lsm_tree::Result<()> { .count(), 0 ); - assert_eq!(tree.tree_type(), TreeType::Standard); tree.flush_active_memtable(0)?; } @@ -174,7 +97,6 @@ fn tree_reload_empty() -> lsm_tree::Result<()> { .count(), 0 ); - assert_eq!(tree.tree_type(), TreeType::Standard); tree.flush_active_memtable(0)?; } @@ -196,7 +118,6 @@ fn tree_reload_empty() -> lsm_tree::Result<()> { .count(), 0 ); - assert_eq!(tree.tree_type(), TreeType::Standard); } Ok(()) diff --git a/crates/lsm-tree/tests/tree_reload_pwd.rs b/crates/lsm-tree/tests/tree_reload_pwd.rs index ca0e65805..a89cd5057 100644 --- a/crates/lsm-tree/tests/tree_reload_pwd.rs +++ b/crates/lsm-tree/tests/tree_reload_pwd.rs @@ -1,5 +1,5 @@ use fs_extra::dir::CopyOptions; -use lsm_tree::{get_tmp_folder, AbstractTree, Config, SeqNo, SequenceNumberCounter}; +use lsm_tree::{AbstractTree, Config, SeqNo, SequenceNumberCounter, get_tmp_folder}; use test_log::test; const ITEM_COUNT: usize = 10_000; diff --git a/crates/lsm-tree/tests/tree_sealed_shadowing.rs b/crates/lsm-tree/tests/tree_sealed_shadowing.rs index d61eed689..f3b70ee65 100644 --- a/crates/lsm-tree/tests/tree_sealed_shadowing.rs +++ b/crates/lsm-tree/tests/tree_sealed_shadowing.rs @@ -1,4 +1,4 @@ -use lsm_tree::{get_tmp_folder, AbstractTree, Config, SeqNo, SequenceNumberCounter}; +use lsm_tree::{AbstractTree, Config, SeqNo, SequenceNumberCounter, get_tmp_folder}; use test_log::test; #[test] diff --git a/crates/lsm-tree/tests/tree_seqno.rs b/crates/lsm-tree/tests/tree_seqno.rs index b8b0752af..249d41b5b 100644 --- a/crates/lsm-tree/tests/tree_seqno.rs +++ b/crates/lsm-tree/tests/tree_seqno.rs @@ -1,4 +1,4 @@ -use lsm_tree::{get_tmp_folder, AbstractTree, Config, SequenceNumberCounter}; +use lsm_tree::{AbstractTree, Config, SequenceNumberCounter, get_tmp_folder}; use test_log::test; #[test] diff --git a/crates/lsm-tree/tests/tree_shadowing.rs b/crates/lsm-tree/tests/tree_shadowing.rs index 710bb6a28..1ed15b872 100644 --- a/crates/lsm-tree/tests/tree_shadowing.rs +++ b/crates/lsm-tree/tests/tree_shadowing.rs @@ -1,4 +1,4 @@ -use lsm_tree::{get_tmp_folder, AbstractTree, Config, Guard, SeqNo, SequenceNumberCounter}; +use lsm_tree::{AbstractTree, Config, Guard, SeqNo, SequenceNumberCounter, get_tmp_folder}; use test_log::test; #[test] @@ -36,44 +36,6 @@ fn tree_shadowing_upsert() -> lsm_tree::Result<()> { Ok(()) } - -#[test] -fn tree_shadowing_upsert_blob() -> lsm_tree::Result<()> { - let folder = get_tmp_folder(); - - let tree = Config::new( - &folder, - SequenceNumberCounter::default(), - SequenceNumberCounter::default(), - ) - .with_kv_separation(Some(Default::default())) - .open()?; - - let key = "1".as_bytes(); - let value = "oldvalue".as_bytes(); - - assert_eq!(tree.len(SeqNo::MAX, None)?, 0); - tree.insert(key, value, 0); - assert_eq!(tree.len(SeqNo::MAX, None)?, 1); - assert_eq!(tree.get(key, SeqNo::MAX)?, Some(value.into())); - - tree.flush_active_memtable(0)?; - assert_eq!(tree.len(SeqNo::MAX, None)?, 1); - assert_eq!(tree.get(key, SeqNo::MAX)?, Some(value.into())); - - let value = "newvalue".as_bytes(); - - tree.insert(key, value, 1); - assert_eq!(tree.len(SeqNo::MAX, None)?, 1); - assert_eq!(tree.get(key, SeqNo::MAX)?, Some(value.into())); - - tree.flush_active_memtable(0)?; - assert_eq!(tree.len(SeqNo::MAX, None)?, 1); - assert_eq!(tree.get(key, SeqNo::MAX)?, Some(value.into())); - - Ok(()) -} - #[test] fn tree_shadowing_delete() -> lsm_tree::Result<()> { let folder = get_tmp_folder(); @@ -108,42 +70,6 @@ fn tree_shadowing_delete() -> lsm_tree::Result<()> { Ok(()) } - -#[test] -fn tree_shadowing_delete_blob() -> lsm_tree::Result<()> { - let folder = get_tmp_folder(); - - let tree = Config::new( - &folder, - SequenceNumberCounter::default(), - SequenceNumberCounter::default(), - ) - .with_kv_separation(Some(Default::default())) - .open()?; - - let key = "1".as_bytes(); - let value = "oldvalue".as_bytes(); - - assert_eq!(tree.len(SeqNo::MAX, None)?, 0); - tree.insert(key, value, 0); - assert_eq!(tree.len(SeqNo::MAX, None)?, 1); - assert_eq!(tree.get(key, SeqNo::MAX)?, Some(value.into())); - - tree.flush_active_memtable(0)?; - assert_eq!(tree.len(SeqNo::MAX, None)?, 1); - assert_eq!(tree.get(key, SeqNo::MAX)?, Some(value.into())); - - tree.remove(key, 1); - assert_eq!(tree.len(SeqNo::MAX, None)?, 0); - assert!(tree.get(key, SeqNo::MAX)?.is_none()); - - tree.flush_active_memtable(0)?; - assert_eq!(tree.len(SeqNo::MAX, None)?, 0); - assert!(tree.get(key, SeqNo::MAX)?.is_none()); - - Ok(()) -} - #[test] fn tree_shadowing_range() -> lsm_tree::Result<()> { const ITEM_COUNT: usize = 10_000; @@ -163,9 +89,10 @@ fn tree_shadowing_range() -> lsm_tree::Result<()> { tree.flush_active_memtable(0)?; assert_eq!(tree.len(SeqNo::MAX, None)?, ITEM_COUNT); - assert!(tree - .iter(SeqNo::MAX, None) - .all(|x| &*x.value().unwrap() == "old".as_bytes())); + assert!( + tree.iter(SeqNo::MAX, None) + .all(|x| &*x.value().unwrap() == "old".as_bytes()) + ); for x in 0..ITEM_COUNT as u64 { let key = x.to_be_bytes(); @@ -174,66 +101,21 @@ fn tree_shadowing_range() -> lsm_tree::Result<()> { } assert_eq!(tree.len(SeqNo::MAX, None)?, ITEM_COUNT); - assert!(tree - .iter(SeqNo::MAX, None) - .all(|x| &*x.value().unwrap() == "new".as_bytes())); + assert!( + tree.iter(SeqNo::MAX, None) + .all(|x| &*x.value().unwrap() == "new".as_bytes()) + ); tree.flush_active_memtable(0)?; assert_eq!(tree.len(SeqNo::MAX, None)?, ITEM_COUNT); - assert!(tree - .iter(SeqNo::MAX, None) - .all(|x| &*x.value().unwrap() == "new".as_bytes())); + assert!( + tree.iter(SeqNo::MAX, None) + .all(|x| &*x.value().unwrap() == "new".as_bytes()) + ); Ok(()) } - -#[test] -fn tree_shadowing_range_blob() -> lsm_tree::Result<()> { - const ITEM_COUNT: usize = 10_000; - - let folder = get_tmp_folder(); - - let seqno = SequenceNumberCounter::default(); - - let tree = Config::new(&folder, seqno.clone(), SequenceNumberCounter::default()) - .with_kv_separation(Some(Default::default())) - .open()?; - - for x in 0..ITEM_COUNT as u64 { - let key = x.to_be_bytes(); - let value = "old".as_bytes(); - tree.insert(key, value, seqno.next()); - } - - tree.flush_active_memtable(0)?; - - assert_eq!(tree.len(SeqNo::MAX, None)?, ITEM_COUNT); - assert!(tree - .iter(SeqNo::MAX, None) - .all(|x| &*x.value().unwrap() == "old".as_bytes())); - - for x in 0..ITEM_COUNT as u64 { - let key = x.to_be_bytes(); - let value = "new".as_bytes(); - tree.insert(key, value, seqno.next()); - } - - assert_eq!(tree.len(SeqNo::MAX, None)?, ITEM_COUNT); - assert!(tree - .iter(SeqNo::MAX, None) - .all(|x| &*x.value().unwrap() == "new".as_bytes())); - - tree.flush_active_memtable(0)?; - - assert_eq!(tree.len(SeqNo::MAX, None)?, ITEM_COUNT); - assert!(tree - .iter(SeqNo::MAX, None) - .all(|x| &*x.value().unwrap() == "new".as_bytes())); - - Ok(()) -} - #[test] fn tree_shadowing_prefix() -> lsm_tree::Result<()> { const ITEM_COUNT: usize = 10_000; @@ -263,9 +145,10 @@ fn tree_shadowing_prefix() -> lsm_tree::Result<()> { tree.prefix("prefix".as_bytes(), SeqNo::MAX, None).count(), ITEM_COUNT ); - assert!(tree - .iter(SeqNo::MAX, None) - .all(|x| &*x.value().unwrap() == "old".as_bytes())); + assert!( + tree.iter(SeqNo::MAX, None) + .all(|x| &*x.value().unwrap() == "old".as_bytes()) + ); for x in 0..ITEM_COUNT as u64 { let value = "new".as_bytes(); @@ -284,9 +167,10 @@ fn tree_shadowing_prefix() -> lsm_tree::Result<()> { tree.prefix("prefix".as_bytes(), SeqNo::MAX, None).count(), ITEM_COUNT ); - assert!(tree - .iter(SeqNo::MAX, None) - .all(|x| &*x.value().unwrap() == "new".as_bytes())); + assert!( + tree.iter(SeqNo::MAX, None) + .all(|x| &*x.value().unwrap() == "new".as_bytes()) + ); tree.flush_active_memtable(0)?; @@ -299,83 +183,10 @@ fn tree_shadowing_prefix() -> lsm_tree::Result<()> { tree.prefix("prefix".as_bytes(), SeqNo::MAX, None).count(), ITEM_COUNT ); - assert!(tree - .iter(SeqNo::MAX, None) - .all(|x| &*x.value().unwrap() == "new".as_bytes())); - - Ok(()) -} - -#[test] -fn tree_shadowing_prefix_blob() -> lsm_tree::Result<()> { - const ITEM_COUNT: usize = 10_000; - - let folder = get_tmp_folder(); - - let seqno = SequenceNumberCounter::default(); - - let tree = Config::new(&folder, seqno.clone(), SequenceNumberCounter::default()) - .with_kv_separation(Some(Default::default())) - .open()?; - - for x in 0..ITEM_COUNT as u64 { - let value = "old".as_bytes(); - let batch_seqno = seqno.next(); - - tree.insert(format!("pre:{x}").as_bytes(), value, batch_seqno); - tree.insert(format!("prefix:{x}").as_bytes(), value, batch_seqno); - } - - tree.flush_active_memtable(0)?; - - assert_eq!(tree.len(SeqNo::MAX, None)?, ITEM_COUNT * 2); - assert_eq!( - tree.prefix("pre".as_bytes(), SeqNo::MAX, None).count(), - ITEM_COUNT * 2 - ); - assert_eq!( - tree.prefix("prefix".as_bytes(), SeqNo::MAX, None).count(), - ITEM_COUNT - ); - assert!(tree - .iter(SeqNo::MAX, None) - .all(|x| &*x.value().unwrap() == "old".as_bytes())); - - for x in 0..ITEM_COUNT as u64 { - let value = "new".as_bytes(); - let batch_seqno = seqno.next(); - - tree.insert(format!("pre:{x}").as_bytes(), value, batch_seqno); - tree.insert(format!("prefix:{x}").as_bytes(), value, batch_seqno); - } - - assert_eq!(tree.len(SeqNo::MAX, None)?, ITEM_COUNT * 2); - assert_eq!( - tree.prefix("pre".as_bytes(), SeqNo::MAX, None).count(), - ITEM_COUNT * 2 - ); - assert_eq!( - tree.prefix("prefix".as_bytes(), SeqNo::MAX, None).count(), - ITEM_COUNT - ); - assert!(tree - .iter(SeqNo::MAX, None) - .all(|x| &*x.value().unwrap() == "new".as_bytes())); - - tree.flush_active_memtable(0)?; - - assert_eq!(tree.len(SeqNo::MAX, None)?, ITEM_COUNT * 2); - assert_eq!( - tree.prefix("pre".as_bytes(), SeqNo::MAX, None).count(), - ITEM_COUNT * 2 - ); - assert_eq!( - tree.prefix("prefix".as_bytes(), SeqNo::MAX, None).count(), - ITEM_COUNT - ); - assert!(tree - .iter(SeqNo::MAX, None) - .all(|x| &*x.value().unwrap() == "new".as_bytes())); + assert!( + tree.iter(SeqNo::MAX, None) + .all(|x| &*x.value().unwrap() == "new".as_bytes()) + ); Ok(()) } diff --git a/crates/lsm-tree/tests/tree_trace.rs b/crates/lsm-tree/tests/tree_trace.rs index 3bdac9dc0..0371dbbf8 100644 --- a/crates/lsm-tree/tests/tree_trace.rs +++ b/crates/lsm-tree/tests/tree_trace.rs @@ -1,4 +1,4 @@ -use lsm_tree::{get_tmp_folder, AbstractTree, Config, SequenceNumberCounter}; +use lsm_tree::{AbstractTree, Config, SequenceNumberCounter, get_tmp_folder}; use test_log::test; #[test] diff --git a/crates/lsm-tree/tests/tree_v1_load_fixture.rs b/crates/lsm-tree/tests/tree_v1_load_fixture.rs deleted file mode 100644 index ee968bc7c..000000000 --- a/crates/lsm-tree/tests/tree_v1_load_fixture.rs +++ /dev/null @@ -1,34 +0,0 @@ -use lsm_tree::{Config, SequenceNumberCounter}; -use test_log::test; - -#[test] -fn tree_load_v1() -> lsm_tree::Result<()> { - let folder = "test_fixture/v1_tree"; - - let result = Config::new( - folder, - SequenceNumberCounter::default(), - SequenceNumberCounter::default(), - ) - .open(); - - matches!(result, Err(lsm_tree::Error::InvalidVersion(1))); - - Ok(()) -} - -#[test] -fn tree_load_v1_corrupt() -> lsm_tree::Result<()> { - let folder = "test_fixture/v1_tree_corrupt"; - - let result = Config::new( - folder, - SequenceNumberCounter::default(), - SequenceNumberCounter::default(), - ) - .open(); - - matches!(result, Err(lsm_tree::Error::InvalidVersion(1))); - - Ok(()) -} diff --git a/crates/lsm-tree/tests/tree_weak_delete.rs b/crates/lsm-tree/tests/tree_weak_delete.rs index 68fc306a6..c8c573c76 100644 --- a/crates/lsm-tree/tests/tree_weak_delete.rs +++ b/crates/lsm-tree/tests/tree_weak_delete.rs @@ -1,4 +1,4 @@ -use lsm_tree::{get_tmp_folder, AbstractTree, SeqNo, SequenceNumberCounter}; +use lsm_tree::{AbstractTree, SeqNo, SequenceNumberCounter, get_tmp_folder}; use test_log::test; #[test] diff --git a/crates/lsm-tree/tests/tree_weak_delete_eviction.rs b/crates/lsm-tree/tests/tree_weak_delete_eviction.rs index d36dc09fc..d7ce02ed3 100644 --- a/crates/lsm-tree/tests/tree_weak_delete_eviction.rs +++ b/crates/lsm-tree/tests/tree_weak_delete_eviction.rs @@ -1,4 +1,4 @@ -use lsm_tree::{get_tmp_folder, AbstractTree, SeqNo, SequenceNumberCounter}; +use lsm_tree::{AbstractTree, SeqNo, SequenceNumberCounter, get_tmp_folder}; use test_log::test; #[test] diff --git a/crates/lsm-tree/tests/tree_weak_delete_queue.rs b/crates/lsm-tree/tests/tree_weak_delete_queue.rs index b03d9d5b4..3b7a1781d 100644 --- a/crates/lsm-tree/tests/tree_weak_delete_queue.rs +++ b/crates/lsm-tree/tests/tree_weak_delete_queue.rs @@ -1,4 +1,4 @@ -use lsm_tree::{get_tmp_folder, AbstractTree, Guard, SeqNo, SequenceNumberCounter}; +use lsm_tree::{AbstractTree, Guard, SeqNo, SequenceNumberCounter, get_tmp_folder}; use test_log::test; #[test] diff --git a/crates/lsm-tree/tests/tree_write_read.rs b/crates/lsm-tree/tests/tree_write_read.rs index af4e3508d..a3f60379a 100644 --- a/crates/lsm-tree/tests/tree_write_read.rs +++ b/crates/lsm-tree/tests/tree_write_read.rs @@ -1,4 +1,4 @@ -use lsm_tree::{get_tmp_folder, AbstractTree, Config, SeqNo, SequenceNumberCounter}; +use lsm_tree::{AbstractTree, Config, SeqNo, SequenceNumberCounter, get_tmp_folder}; use test_log::test; #[test] diff --git a/crates/rawdb/Cargo.toml b/crates/rawdb/Cargo.toml index 648f226d0..0fc531b3e 100644 --- a/crates/rawdb/Cargo.toml +++ b/crates/rawdb/Cargo.toml @@ -17,7 +17,7 @@ memmap2 = "0.9.11" parking_lot = { workspace = true } rayon = { workspace = true } smallvec = "1.15" -thiserror = "2.0.18" +thiserror = "2.0.19" [dev-dependencies] tempfile = { workspace = true } diff --git a/crates/vecdb/Cargo.toml b/crates/vecdb/Cargo.toml index 070be02d3..fa3fa508a 100644 --- a/crates/vecdb/Cargo.toml +++ b/crates/vecdb/Cargo.toml @@ -28,7 +28,7 @@ log = { workspace = true } parking_lot = { workspace = true } rawdb = { workspace = true } ryu = "1.0.23" -serde_json = { version = "1.0.150", features = ["float_roundtrip"], optional = true } +serde_json = { version = "1.0.151", features = ["float_roundtrip"], optional = true } thiserror = "2" # [optional-dependencies] @@ -38,7 +38,7 @@ schemars = { workspace = true, optional = true } serde = { workspace = true, features = ["derive"], optional = true } sonic-rs = { version = "0.5.8", optional = true } vecdb_derive = { workspace = true, optional = true } -zerocopy = { version = "0.8.54", features = ["zerocopy-derive"], optional = true } +zerocopy = { version = "0.8.55", features = ["zerocopy-derive"], optional = true } zstd = { version = "0.13.3", optional = true } [dev-dependencies] diff --git a/crates/vecdb_derive/Cargo.toml b/crates/vecdb_derive/Cargo.toml index b6eda1a85..6f6fa8f25 100644 --- a/crates/vecdb_derive/Cargo.toml +++ b/crates/vecdb_derive/Cargo.toml @@ -14,7 +14,7 @@ readme.workspace = true proc-macro = true [dependencies] -syn = "2.0" +syn = "3.0" quote = "1.0" [dev-dependencies]

- - CI - - - docs.rs - - - Crates.io - - MSRV - - dependency status - -