hf-xet 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- hf_xet-0.1.0/Cargo.toml +31 -0
- hf_xet-0.1.0/PKG-INFO +17 -0
- hf_xet-0.1.0/cas_client/Cargo.toml +41 -0
- hf_xet-0.1.0/cas_client/Dockerfile +37 -0
- hf_xet-0.1.0/cas_client/README.md +35 -0
- hf_xet-0.1.0/cas_client/src/error.rs +75 -0
- hf_xet-0.1.0/cas_client/src/global_dedup_table.rs +230 -0
- hf_xet-0.1.0/cas_client/src/http_client.rs +269 -0
- hf_xet-0.1.0/cas_client/src/http_shard_client.rs +233 -0
- hf_xet-0.1.0/cas_client/src/interface.rs +108 -0
- hf_xet-0.1.0/cas_client/src/lib.rs +24 -0
- hf_xet-0.1.0/cas_client/src/local_client.rs +473 -0
- hf_xet-0.1.0/cas_client/src/local_shard_client.rs +100 -0
- hf_xet-0.1.0/cas_client/src/remote_client.rs +655 -0
- hf_xet-0.1.0/cas_object/Cargo.toml +20 -0
- hf_xet-0.1.0/cas_object/src/cas_chunk_format/deserialize_async.rs +175 -0
- hf_xet-0.1.0/cas_object/src/cas_chunk_format.rs +319 -0
- hf_xet-0.1.0/cas_object/src/cas_object_format.rs +1171 -0
- hf_xet-0.1.0/cas_object/src/chunk_verification.rs +16 -0
- hf_xet-0.1.0/cas_object/src/compression_scheme.rs +86 -0
- hf_xet-0.1.0/cas_object/src/error.rs +57 -0
- hf_xet-0.1.0/cas_object/src/lib.rs +12 -0
- hf_xet-0.1.0/cas_object/src/validate_xorb_stream.rs +196 -0
- hf_xet-0.1.0/cas_types/Cargo.toml +10 -0
- hf_xet-0.1.0/cas_types/src/error.rs +8 -0
- hf_xet-0.1.0/cas_types/src/key.rs +133 -0
- hf_xet-0.1.0/cas_types/src/lib.rs +139 -0
- hf_xet-0.1.0/chunk_cache/.gitignore +1 -0
- hf_xet-0.1.0/chunk_cache/Cargo.toml +32 -0
- hf_xet-0.1.0/chunk_cache/src/bin/analysis.rs +27 -0
- hf_xet-0.1.0/chunk_cache/src/cache_manager.rs +55 -0
- hf_xet-0.1.0/chunk_cache/src/disk/cache_file_header.rs +56 -0
- hf_xet-0.1.0/chunk_cache/src/disk/cache_item.rs +123 -0
- hf_xet-0.1.0/chunk_cache/src/disk/test_utils.rs +182 -0
- hf_xet-0.1.0/chunk_cache/src/disk.rs +1313 -0
- hf_xet-0.1.0/chunk_cache/src/error.rs +57 -0
- hf_xet-0.1.0/chunk_cache/src/lib.rs +73 -0
- hf_xet-0.1.0/data/Cargo.toml +76 -0
- hf_xet-0.1.0/data/src/bin/example.rs +222 -0
- hf_xet-0.1.0/data/src/cas_interface.rs +56 -0
- hf_xet-0.1.0/data/src/chunking.rs +192 -0
- hf_xet-0.1.0/data/src/clean.rs +716 -0
- hf_xet-0.1.0/data/src/configurations.rs +124 -0
- hf_xet-0.1.0/data/src/constants.rs +44 -0
- hf_xet-0.1.0/data/src/data_client.rs +192 -0
- hf_xet-0.1.0/data/src/data_processing.rs +234 -0
- hf_xet-0.1.0/data/src/errors.rs +89 -0
- hf_xet-0.1.0/data/src/lib.rs +23 -0
- hf_xet-0.1.0/data/src/metrics.rs +13 -0
- hf_xet-0.1.0/data/src/parallel_xorb_uploader.rs +192 -0
- hf_xet-0.1.0/data/src/pointer_file.rs +316 -0
- hf_xet-0.1.0/data/src/remote_shard_interface.rs +336 -0
- hf_xet-0.1.0/data/src/repo_salt.rs +14 -0
- hf_xet-0.1.0/data/src/shard_interface.rs +54 -0
- hf_xet-0.1.0/data/src/small_file_determination.rs +19 -0
- hf_xet-0.1.0/data/src/test_utils/local_test_client.rs +103 -0
- hf_xet-0.1.0/data/src/test_utils/mod.rs +3 -0
- hf_xet-0.1.0/data/tests/integration_tests/initialize.sh +168 -0
- hf_xet-0.1.0/data/tests/integration_tests/test_basic_clean_smudge.sh +32 -0
- hf_xet-0.1.0/data/tests/integration_tests.rs +112 -0
- hf_xet-0.1.0/error_printer/Cargo.toml +12 -0
- hf_xet-0.1.0/error_printer/src/lib.rs +151 -0
- hf_xet-0.1.0/error_printer/tests/test_error.rs +68 -0
- hf_xet-0.1.0/error_printer/tests/test_option.rs +68 -0
- hf_xet-0.1.0/file_utils/Cargo.toml +27 -0
- hf_xet-0.1.0/file_utils/src/file_metadata.rs +165 -0
- hf_xet-0.1.0/file_utils/src/lib.rs +6 -0
- hf_xet-0.1.0/file_utils/src/privilege_context.rs +371 -0
- hf_xet-0.1.0/file_utils/src/safe_file_creator.rs +329 -0
- hf_xet-0.1.0/hf_xet/Cargo.lock +3506 -0
- hf_xet-0.1.0/hf_xet/Cargo.toml +45 -0
- hf_xet-0.1.0/hf_xet/README.md +4 -0
- hf_xet-0.1.0/hf_xet/python/.gitkeep +0 -0
- hf_xet-0.1.0/hf_xet/src/lib.rs +153 -0
- hf_xet-0.1.0/hf_xet/src/log.rs +49 -0
- hf_xet-0.1.0/hf_xet/src/log_buffer.rs +303 -0
- hf_xet-0.1.0/hf_xet/src/progress_update.rs +66 -0
- hf_xet-0.1.0/hf_xet/src/runtime.rs +173 -0
- hf_xet-0.1.0/hf_xet/src/token_refresh.rs +67 -0
- hf_xet-0.1.0/mdb_shard/Cargo.toml +29 -0
- hf_xet-0.1.0/mdb_shard/src/cas_structs.rs +248 -0
- hf_xet-0.1.0/mdb_shard/src/constants.rs +13 -0
- hf_xet-0.1.0/mdb_shard/src/error.rs +51 -0
- hf_xet-0.1.0/mdb_shard/src/file_structs.rs +606 -0
- hf_xet-0.1.0/mdb_shard/src/interpolation_search.rs +293 -0
- hf_xet-0.1.0/mdb_shard/src/lib.rs +24 -0
- hf_xet-0.1.0/mdb_shard/src/session_directory.rs +135 -0
- hf_xet-0.1.0/mdb_shard/src/set_operations.rs +650 -0
- hf_xet-0.1.0/mdb_shard/src/shard_benchmark.rs +234 -0
- hf_xet-0.1.0/mdb_shard/src/shard_dedup_probe.rs +15 -0
- hf_xet-0.1.0/mdb_shard/src/shard_file.rs +2 -0
- hf_xet-0.1.0/mdb_shard/src/shard_file_handle.rs +288 -0
- hf_xet-0.1.0/mdb_shard/src/shard_file_manager.rs +1092 -0
- hf_xet-0.1.0/mdb_shard/src/shard_file_reconstructor.rs +15 -0
- hf_xet-0.1.0/mdb_shard/src/shard_format.rs +1702 -0
- hf_xet-0.1.0/mdb_shard/src/shard_in_memory.rs +268 -0
- hf_xet-0.1.0/mdb_shard/src/streaming_shard.rs +510 -0
- hf_xet-0.1.0/mdb_shard/src/utils.rs +64 -0
- hf_xet-0.1.0/merkledb/Cargo.toml +47 -0
- hf_xet-0.1.0/merkledb/benches/rolling_hash_benchmark.rs +272 -0
- hf_xet-0.1.0/merkledb/src/aggregate_hashes.rs +54 -0
- hf_xet-0.1.0/merkledb/src/async_chunk_iterator.rs +499 -0
- hf_xet-0.1.0/merkledb/src/bin/testdedupe.rs +78 -0
- hf_xet-0.1.0/merkledb/src/chunk_iterator.rs +280 -0
- hf_xet-0.1.0/merkledb/src/constants.rs +15 -0
- hf_xet-0.1.0/merkledb/src/error.rs +42 -0
- hf_xet-0.1.0/merkledb/src/internal_methods.rs +498 -0
- hf_xet-0.1.0/merkledb/src/lib.rs +46 -0
- hf_xet-0.1.0/merkledb/src/merkledb_debug.rs +301 -0
- hf_xet-0.1.0/merkledb/src/merkledb_highlevel_v1.rs +134 -0
- hf_xet-0.1.0/merkledb/src/merkledb_highlevel_v2.rs +28 -0
- hf_xet-0.1.0/merkledb/src/merkledb_ingestion_v1.rs +105 -0
- hf_xet-0.1.0/merkledb/src/merkledb_reconstruction.rs +158 -0
- hf_xet-0.1.0/merkledb/src/merkledbbase.rs +150 -0
- hf_xet-0.1.0/merkledb/src/merkledbv1.rs +14 -0
- hf_xet-0.1.0/merkledb/src/merkledbv2.rs +8 -0
- hf_xet-0.1.0/merkledb/src/merklememdb.rs +425 -0
- hf_xet-0.1.0/merkledb/src/merklenode.rs +364 -0
- hf_xet-0.1.0/merkledb/src/tests.rs +481 -0
- hf_xet-0.1.0/merklehash/Cargo.toml +16 -0
- hf_xet-0.1.0/merklehash/src/data_hash.rs +448 -0
- hf_xet-0.1.0/merklehash/src/lib.rs +49 -0
- hf_xet-0.1.0/parutils/Cargo.toml +20 -0
- hf_xet-0.1.0/parutils/src/async_iterator.rs +38 -0
- hf_xet-0.1.0/parutils/src/lib.rs +7 -0
- hf_xet-0.1.0/parutils/src/parallel_utils.rs +302 -0
- hf_xet-0.1.0/pyproject.toml +21 -0
- hf_xet-0.1.0/utils/Cargo.toml +31 -0
- hf_xet-0.1.0/utils/README.md +18 -0
- hf_xet-0.1.0/utils/src/async_read.rs +126 -0
- hf_xet-0.1.0/utils/src/auth.rs +103 -0
- hf_xet-0.1.0/utils/src/errors.rs +60 -0
- hf_xet-0.1.0/utils/src/lib.rs +15 -0
- hf_xet-0.1.0/utils/src/output_bytes.rs +52 -0
- hf_xet-0.1.0/utils/src/progress.rs +16 -0
- hf_xet-0.1.0/utils/src/serialization_utils.rs +59 -0
- hf_xet-0.1.0/utils/src/singleflight.rs +716 -0
- hf_xet-0.1.0/utils/src/threadpool.rs +232 -0
hf_xet-0.1.0/Cargo.toml
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
[workspace]
|
|
2
|
+
resolver = "2"
|
|
3
|
+
|
|
4
|
+
members = [
|
|
5
|
+
"cas_client",
|
|
6
|
+
"cas_object",
|
|
7
|
+
"cas_types",
|
|
8
|
+
"data",
|
|
9
|
+
"error_printer",
|
|
10
|
+
"file_utils",
|
|
11
|
+
"merklehash",
|
|
12
|
+
"mdb_shard",
|
|
13
|
+
"parutils",
|
|
14
|
+
"progress_reporting",
|
|
15
|
+
"utils",
|
|
16
|
+
"cas_object",
|
|
17
|
+
"cas_types",
|
|
18
|
+
"chunk_cache",
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
exclude = ["hf_xet", "chunk_cache_bench"]
|
|
22
|
+
|
|
23
|
+
[profile.release]
|
|
24
|
+
opt-level = 3
|
|
25
|
+
lto = true
|
|
26
|
+
debug = 1
|
|
27
|
+
|
|
28
|
+
[profile.opt-test]
|
|
29
|
+
inherits = "dev"
|
|
30
|
+
opt-level = 1
|
|
31
|
+
debug = 1
|
hf_xet-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: hf-xet
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Classifier: Programming Language :: Rust
|
|
5
|
+
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
6
|
+
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
|
7
|
+
Requires-Dist: pytest ; extra == 'tests'
|
|
8
|
+
Provides-Extra: tests
|
|
9
|
+
License: Apache-2.0
|
|
10
|
+
Requires-Python: >=3.8
|
|
11
|
+
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
|
|
12
|
+
|
|
13
|
+
# Development Notes
|
|
14
|
+
|
|
15
|
+
* `pip install maturin`
|
|
16
|
+
* from this directory: `maturin develop`
|
|
17
|
+
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
[package]
|
|
2
|
+
name = "cas_client"
|
|
3
|
+
version = "0.14.5"
|
|
4
|
+
edition = "2021"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
|
|
7
|
+
[features]
|
|
8
|
+
strict = []
|
|
9
|
+
|
|
10
|
+
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
|
11
|
+
|
|
12
|
+
[dependencies]
|
|
13
|
+
cas_types = { version = "0.1.0", path = "../cas_types" }
|
|
14
|
+
cas_object = { path = "../cas_object" }
|
|
15
|
+
chunk_cache = { path = "../chunk_cache" }
|
|
16
|
+
error_printer = { path = "../error_printer" }
|
|
17
|
+
file_utils = { path = "../file_utils" }
|
|
18
|
+
utils = { path = "../utils" }
|
|
19
|
+
merkledb = { path = "../merkledb" }
|
|
20
|
+
mdb_shard = { path = "../mdb_shard" }
|
|
21
|
+
merklehash = { path = "../merklehash" }
|
|
22
|
+
thiserror = "2.0"
|
|
23
|
+
tokio = { version = "1.41", features = ["full"] }
|
|
24
|
+
async-trait = "0.1.9"
|
|
25
|
+
anyhow = "1"
|
|
26
|
+
http = "1.1.0"
|
|
27
|
+
tempfile = "3.13.0"
|
|
28
|
+
tracing = "0.1.31"
|
|
29
|
+
bytes = "1"
|
|
30
|
+
itertools = "0.10"
|
|
31
|
+
reqwest = { version = "0.12.7", features = ["json", "stream"] }
|
|
32
|
+
reqwest-middleware = "0.3.3"
|
|
33
|
+
url = "2.5.2"
|
|
34
|
+
reqwest-retry = "0.6.1"
|
|
35
|
+
heed = "0.11"
|
|
36
|
+
futures = "0.3.31"
|
|
37
|
+
|
|
38
|
+
[dev-dependencies]
|
|
39
|
+
rand = "0.8.5"
|
|
40
|
+
httpmock = "0.7.0"
|
|
41
|
+
tracing-test = { version = "0.2.5", features = ["no-env-filter"] }
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
FROM rust:1.58 as builder
|
|
2
|
+
|
|
3
|
+
RUN USER=root rustup component add rustfmt
|
|
4
|
+
RUN USER=root cargo new --bin cas_client
|
|
5
|
+
|
|
6
|
+
WORKDIR ./cas_client
|
|
7
|
+
ADD ./utils ../utils
|
|
8
|
+
COPY ./cas_client/Cargo.toml ./Cargo.toml
|
|
9
|
+
RUN cargo build --release
|
|
10
|
+
RUN rm src/*.rs
|
|
11
|
+
|
|
12
|
+
COPY ./cas_client .
|
|
13
|
+
RUN rm ./target/release/deps/cas_client*
|
|
14
|
+
|
|
15
|
+
RUN cargo build --release
|
|
16
|
+
|
|
17
|
+
FROM debian:buster-slim
|
|
18
|
+
ARG APP=/usr/src/app
|
|
19
|
+
|
|
20
|
+
RUN apt-get update \
|
|
21
|
+
&& apt-get install -y ca-certificates tzdata \
|
|
22
|
+
&& rm -rf /var/lib/apt/lists/*
|
|
23
|
+
|
|
24
|
+
ENV TZ=Etc/UTC \
|
|
25
|
+
APP_USER=appuser
|
|
26
|
+
|
|
27
|
+
RUN groupadd $APP_USER \
|
|
28
|
+
&& useradd -g $APP_USER $APP_USER \
|
|
29
|
+
&& mkdir -p ${APP}
|
|
30
|
+
|
|
31
|
+
COPY --from=builder /cas_client/target/release/cas_client ${APP}/cas_client
|
|
32
|
+
RUN mkdir ${APP}/config
|
|
33
|
+
|
|
34
|
+
RUN chown -R $APP_USER:$APP_USER ${APP}
|
|
35
|
+
|
|
36
|
+
USER $APP_USER
|
|
37
|
+
WORKDIR ${APP}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# CAS client
|
|
2
|
+
|
|
3
|
+
This package is responsible for handling all communication with the CAS services.
|
|
4
|
+
|
|
5
|
+
## Layout
|
|
6
|
+
|
|
7
|
+
Check out the traits published by this crate to understand how it is intended to be used. These are stored in [src/interface.rs].
|
|
8
|
+
|
|
9
|
+
### Main impl of Client trait
|
|
10
|
+
|
|
11
|
+
- [src/remote_client.rs]: This is the main impl of Client - and is responsible for communicating with a remote CAS.
|
|
12
|
+
- [src/local_client.rs]: This is an impl of Client for local filesystem usage. It is currently only used for testing.
|
|
13
|
+
|
|
14
|
+
### Caching
|
|
15
|
+
|
|
16
|
+
Caching happens locally using the [chunk_cache](../chunk_cache) crate, specifically using the ChunkCache. When RemoteClient is provided a ChunkCache then it will use this on download calls (the ReconstructionClient trait `get_file` and `get_file_byte_range`).
|
|
17
|
+
|
|
18
|
+
## Overall CAS Communication Design
|
|
19
|
+
|
|
20
|
+
### Authentication
|
|
21
|
+
|
|
22
|
+
Authentication is done using AuthMiddleware, which sets an Authorization Header and refreshes it periodically with CAS. See [src/http_client.rs].
|
|
23
|
+
|
|
24
|
+
### Retry
|
|
25
|
+
|
|
26
|
+
HTTP operations are retried using a RetryPolicy defined in [src/http_client.rs]. This is implemented as Middleware for the reqwest HTTP clients.
|
|
27
|
+
|
|
28
|
+
### Operations
|
|
29
|
+
|
|
30
|
+
CAS offers a set of services used by the client to upload and download user files. These files are stored using two different
|
|
31
|
+
types of storage objects, Xorbs and Shards. Xorbs contain chunks and Shards contain mappings of files to Xorb chunks.
|
|
32
|
+
|
|
33
|
+
### Logging / Tracing
|
|
34
|
+
|
|
35
|
+
Logging & Tracing is done through the tracing crate, with info!, warn!, and debug! macros widely used in the code.
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
use merklehash::MerkleHash;
|
|
2
|
+
use thiserror::Error;
|
|
3
|
+
|
|
4
|
+
#[non_exhaustive]
|
|
5
|
+
#[derive(Error, Debug)]
|
|
6
|
+
pub enum CasClientError {
|
|
7
|
+
#[error("ChunkCache Error: {0}")]
|
|
8
|
+
ChunkCache(#[from] chunk_cache::error::ChunkCacheError),
|
|
9
|
+
|
|
10
|
+
#[error("Cas Object Error: {0}")]
|
|
11
|
+
CasObjectError(#[from] cas_object::error::CasObjectError),
|
|
12
|
+
|
|
13
|
+
#[error("Configuration Error: {0} ")]
|
|
14
|
+
ConfigurationError(String),
|
|
15
|
+
|
|
16
|
+
#[error("Invalid Range")]
|
|
17
|
+
InvalidRange,
|
|
18
|
+
|
|
19
|
+
#[error("Invalid Arguments")]
|
|
20
|
+
InvalidArguments,
|
|
21
|
+
|
|
22
|
+
#[error("File not found for hash: {0}")]
|
|
23
|
+
FileNotFound(MerkleHash),
|
|
24
|
+
|
|
25
|
+
#[error("IO Error: {0}")]
|
|
26
|
+
IOError(#[from] std::io::Error),
|
|
27
|
+
|
|
28
|
+
#[error("Invalid Shard Key: {0}")]
|
|
29
|
+
InvalidShardKey(String),
|
|
30
|
+
|
|
31
|
+
#[error("Other Internal Error: {0}")]
|
|
32
|
+
InternalError(#[from] anyhow::Error),
|
|
33
|
+
|
|
34
|
+
#[error("MerkleDB Shard Error : {0}")]
|
|
35
|
+
MDBShardError(#[from] mdb_shard::error::MDBShardError),
|
|
36
|
+
|
|
37
|
+
#[error("Error : {0}")]
|
|
38
|
+
Other(String),
|
|
39
|
+
|
|
40
|
+
#[error("Parse Error: {0}")]
|
|
41
|
+
ParseError(#[from] url::ParseError),
|
|
42
|
+
|
|
43
|
+
#[error("ReqwestMiddleware Error: {0}")]
|
|
44
|
+
ReqwestMiddlewareError(#[from] reqwest_middleware::Error),
|
|
45
|
+
|
|
46
|
+
#[error("Reqwest Error: {0}")]
|
|
47
|
+
ReqwestError(#[from] reqwest::Error),
|
|
48
|
+
|
|
49
|
+
#[error("LMDB Error: {0}")]
|
|
50
|
+
ShardDedupDBError(String),
|
|
51
|
+
|
|
52
|
+
#[error("CAS object not found for hash: {0}")]
|
|
53
|
+
XORBNotFound(MerkleHash),
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Define our own result type here (this seems to be the standard).
|
|
57
|
+
pub type Result<T> = std::result::Result<T, CasClientError>;
|
|
58
|
+
|
|
59
|
+
impl PartialEq for CasClientError {
|
|
60
|
+
fn eq(&self, other: &CasClientError) -> bool {
|
|
61
|
+
match (self, other) {
|
|
62
|
+
(CasClientError::XORBNotFound(a), CasClientError::XORBNotFound(b)) => a == b,
|
|
63
|
+
(e1, e2) => std::mem::discriminant(e1) == std::mem::discriminant(e2),
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
impl From<utils::errors::SingleflightError<CasClientError>> for CasClientError {
|
|
69
|
+
fn from(value: utils::singleflight::SingleflightError<CasClientError>) -> Self {
|
|
70
|
+
match value {
|
|
71
|
+
utils::singleflight::SingleflightError::InternalError(e) => e,
|
|
72
|
+
e => CasClientError::Other(format!("single flight error: {e}")),
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
use std::collections::HashMap;
|
|
2
|
+
use std::path::Path;
|
|
3
|
+
use std::sync::Arc;
|
|
4
|
+
|
|
5
|
+
use heed::types::*;
|
|
6
|
+
use heed::EnvOpenOptions;
|
|
7
|
+
use itertools::Itertools;
|
|
8
|
+
use merkledb::aggregate_hashes::with_salt;
|
|
9
|
+
use merklehash::MerkleHash;
|
|
10
|
+
use tokio::sync::RwLock;
|
|
11
|
+
use tracing::{info, warn};
|
|
12
|
+
|
|
13
|
+
use crate::error::{CasClientError, Result};
|
|
14
|
+
|
|
15
|
+
type DB = heed::Database<OwnedType<MerkleHash>, OwnedType<MerkleHash>>;
|
|
16
|
+
|
|
17
|
+
pub struct DiskBasedGlobalDedupTable {
|
|
18
|
+
env: heed::Env,
|
|
19
|
+
table: RwLock<HashMap<String, Arc<DB>>>, // map of chunk_hash -> shard_hash
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Annoyingly, heed::Error is not Send/Sync, so convert to string.
|
|
23
|
+
fn map_db_error(e: heed::Error) -> CasClientError {
|
|
24
|
+
let msg = format!("Global shard dedup database error: {e:?}");
|
|
25
|
+
warn!("{msg}");
|
|
26
|
+
CasClientError::ShardDedupDBError(msg)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
impl DiskBasedGlobalDedupTable {
|
|
30
|
+
pub fn open_or_create(path: impl AsRef<Path>) -> Result<Self> {
|
|
31
|
+
let db_path = path.as_ref().join("global_shard_dedup.db");
|
|
32
|
+
info!("Using {db_path:?} as path to global shard dedup database.");
|
|
33
|
+
|
|
34
|
+
std::fs::create_dir_all(&db_path)?;
|
|
35
|
+
let env = EnvOpenOptions::new()
|
|
36
|
+
.max_dbs(32)
|
|
37
|
+
.max_readers(32)
|
|
38
|
+
.open(&db_path)
|
|
39
|
+
.map_err(map_db_error)?;
|
|
40
|
+
|
|
41
|
+
Ok(Self {
|
|
42
|
+
env,
|
|
43
|
+
table: RwLock::new(HashMap::new()),
|
|
44
|
+
})
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async fn get_db(&self, prefix: &str) -> Result<Arc<DB>> {
|
|
48
|
+
if let Some(db) = self.table.read().await.get(prefix).cloned() {
|
|
49
|
+
return Ok(db);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
let mut write_lock = self.table.write().await;
|
|
53
|
+
|
|
54
|
+
match write_lock.entry(prefix.to_owned()) {
|
|
55
|
+
std::collections::hash_map::Entry::Occupied(db) => Ok(db.get().clone()),
|
|
56
|
+
std::collections::hash_map::Entry::Vacant(entry_ref) => {
|
|
57
|
+
let db = Arc::new(self.env.create_database(Some(prefix)).map_err(map_db_error)?);
|
|
58
|
+
entry_ref.insert(db.clone());
|
|
59
|
+
Ok(db)
|
|
60
|
+
},
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
pub async fn batch_add(
|
|
65
|
+
&self,
|
|
66
|
+
chunk_hashes: &[MerkleHash],
|
|
67
|
+
shard_hash: &MerkleHash,
|
|
68
|
+
prefix: &str,
|
|
69
|
+
salt: &[u8; 32],
|
|
70
|
+
) -> Result<()> {
|
|
71
|
+
let db = self.get_db(prefix).await?;
|
|
72
|
+
|
|
73
|
+
let mut write_txn = self.env.write_txn().map_err(map_db_error)?;
|
|
74
|
+
|
|
75
|
+
chunk_hashes.iter().for_each(|chunk| {
|
|
76
|
+
let maybe_salted_chunk_hash = with_salt(chunk, salt).ok();
|
|
77
|
+
if let Some(salted_chunk_hash) = maybe_salted_chunk_hash {
|
|
78
|
+
let _ = db.put(&mut write_txn, &salted_chunk_hash, shard_hash).map_err(map_db_error);
|
|
79
|
+
// Prints warning for error, otherwise ignores.
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
write_txn.commit().map_err(map_db_error)?;
|
|
83
|
+
|
|
84
|
+
Ok(())
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
pub async fn query(&self, salted_chunk_hash: &[MerkleHash], prefix: &str) -> Vec<MerkleHash> {
|
|
88
|
+
let Ok(db) = self.get_db(prefix).await else {
|
|
89
|
+
return vec![];
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
let Ok(read_txn) = self.env.read_txn().map_err(|e| {
|
|
93
|
+
warn!("Error starting read transaction for prefix {prefix}: {e:?}");
|
|
94
|
+
e
|
|
95
|
+
}) else {
|
|
96
|
+
return vec![];
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
salted_chunk_hash
|
|
100
|
+
.iter()
|
|
101
|
+
.filter_map(|chunk| db.get(&read_txn, chunk).unwrap_or(None))
|
|
102
|
+
.collect_vec()
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
#[cfg(test)]
|
|
107
|
+
mod tests {
|
|
108
|
+
use std::sync::Arc;
|
|
109
|
+
|
|
110
|
+
use itertools::Itertools;
|
|
111
|
+
use mdb_shard::shard_format::test_routines::rng_hash;
|
|
112
|
+
use merkledb::aggregate_hashes::with_salt;
|
|
113
|
+
use rand::{thread_rng, Rng};
|
|
114
|
+
use tempfile::TempDir;
|
|
115
|
+
|
|
116
|
+
use super::DiskBasedGlobalDedupTable;
|
|
117
|
+
|
|
118
|
+
#[tokio::test]
|
|
119
|
+
async fn test_basic_insert_retrieval() -> anyhow::Result<()> {
|
|
120
|
+
let tempdir = TempDir::new()?;
|
|
121
|
+
|
|
122
|
+
let db_file = tempdir.path().join("db");
|
|
123
|
+
|
|
124
|
+
let db = DiskBasedGlobalDedupTable::open_or_create(&db_file)?;
|
|
125
|
+
|
|
126
|
+
let mut rng = thread_rng();
|
|
127
|
+
|
|
128
|
+
let prefix = "default";
|
|
129
|
+
let chunk_hash = rng_hash(rng.gen());
|
|
130
|
+
let shard_hash = rng_hash(rng.gen());
|
|
131
|
+
let salt: [u8; 32] = rng.gen();
|
|
132
|
+
|
|
133
|
+
db.batch_add(&[chunk_hash], &shard_hash, "default", &salt).await?;
|
|
134
|
+
|
|
135
|
+
let query_shard = db.query(&[with_salt(&chunk_hash, &salt)?], prefix).await;
|
|
136
|
+
|
|
137
|
+
assert_eq!(query_shard.len(), 1);
|
|
138
|
+
assert_eq!(query_shard.first(), Some(&shard_hash));
|
|
139
|
+
|
|
140
|
+
Ok(())
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
#[tokio::test]
|
|
144
|
+
async fn test_multithread_insert_retrieval() -> anyhow::Result<()> {
|
|
145
|
+
let tempdir = TempDir::new()?;
|
|
146
|
+
|
|
147
|
+
let db_file = tempdir.path().join("db");
|
|
148
|
+
|
|
149
|
+
let db = Arc::new(DiskBasedGlobalDedupTable::open_or_create(&db_file)?);
|
|
150
|
+
|
|
151
|
+
let mut rng = thread_rng();
|
|
152
|
+
let prefix = "default";
|
|
153
|
+
let chunk_hashes = (0..10).map(|_| rng_hash(rng.gen())).collect_vec();
|
|
154
|
+
let shard_hashes = (0..10).map(|_| rng_hash(rng.gen())).collect_vec();
|
|
155
|
+
let salt: [u8; 32] = rng.gen();
|
|
156
|
+
|
|
157
|
+
// insert to the db concurrently
|
|
158
|
+
let handles = (0..10)
|
|
159
|
+
.map(|i| {
|
|
160
|
+
let chunk_hash = chunk_hashes[i];
|
|
161
|
+
let shard_hash = shard_hashes[i];
|
|
162
|
+
let db = db.clone();
|
|
163
|
+
|
|
164
|
+
tokio::spawn(async move { db.batch_add(&[chunk_hash], &shard_hash, prefix, &salt).await })
|
|
165
|
+
})
|
|
166
|
+
.collect_vec();
|
|
167
|
+
|
|
168
|
+
for h in handles {
|
|
169
|
+
let _ = h.await?;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// now examine that inserts succeeded
|
|
173
|
+
for i in 0..10 {
|
|
174
|
+
let chunk_hash = chunk_hashes[i];
|
|
175
|
+
let shard_hash = shard_hashes[i];
|
|
176
|
+
let query_shard = db.query(&[with_salt(&chunk_hash, &salt)?], prefix).await;
|
|
177
|
+
|
|
178
|
+
assert_eq!(query_shard.len(), 1);
|
|
179
|
+
assert_eq!(query_shard.first(), Some(&shard_hash));
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
Ok(())
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
#[tokio::test]
|
|
186
|
+
async fn test_multi_db_instance_insert_retrieval() -> anyhow::Result<()> {
|
|
187
|
+
let tempdir = TempDir::new()?;
|
|
188
|
+
|
|
189
|
+
let db_file = tempdir.path().join("db");
|
|
190
|
+
|
|
191
|
+
let mut rng = thread_rng();
|
|
192
|
+
let prefix = "default";
|
|
193
|
+
let chunk_hashes = (0..1000).map(|_| rng_hash(rng.gen())).collect_vec();
|
|
194
|
+
let shard_hashes = (0..10).map(|_| rng_hash(rng.gen())).collect_vec();
|
|
195
|
+
let salt: [u8; 32] = rng.gen();
|
|
196
|
+
|
|
197
|
+
// insert to the db concurrently
|
|
198
|
+
let handles = (0..10)
|
|
199
|
+
.map(|i| {
|
|
200
|
+
let chunk_hashes = chunk_hashes[i * 100..(i + 1) * 100].to_vec();
|
|
201
|
+
let shard_hash = shard_hashes[i];
|
|
202
|
+
let db_file = db_file.clone();
|
|
203
|
+
|
|
204
|
+
tokio::spawn(async move {
|
|
205
|
+
let db = DiskBasedGlobalDedupTable::open_or_create(&db_file).unwrap();
|
|
206
|
+
db.batch_add(&chunk_hashes, &shard_hash, prefix, &salt).await
|
|
207
|
+
})
|
|
208
|
+
})
|
|
209
|
+
.collect_vec();
|
|
210
|
+
|
|
211
|
+
for h in handles {
|
|
212
|
+
let _ = h.await?;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// now examine that inserts succeeded
|
|
216
|
+
let db = DiskBasedGlobalDedupTable::open_or_create(&db_file)?;
|
|
217
|
+
for i in 0..10 {
|
|
218
|
+
let shard_hash = shard_hashes[i];
|
|
219
|
+
|
|
220
|
+
for chunk_hash in &chunk_hashes[i * 100..(i + 1) * 100] {
|
|
221
|
+
let query_shard = db.query(&[with_salt(chunk_hash, &salt)?], prefix).await;
|
|
222
|
+
|
|
223
|
+
assert_eq!(query_shard.len(), 1);
|
|
224
|
+
assert_eq!(query_shard.first(), Some(&shard_hash));
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
Ok(())
|
|
229
|
+
}
|
|
230
|
+
}
|