├── .github └── CODEOWNERS ├── .gitignore ├── CONTRIBUTING.md ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── docs └── The_Hitchhiker_s_Guide_to_Building_a_Distributed_Filesystem_in_Rust__The_beginning.pdf ├── file-sync ├── Cargo.toml └── src │ ├── lib.rs │ └── merkle.rs ├── rust-toolchain.toml └── shard-distribution ├── Cargo.toml ├── README.md ├── benches └── consistent_hashing.rs └── src ├── consistent_hashing.rs ├── hash.rs ├── lib.rs └── shard_distribution.rs /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # This is a comment. 2 | # Each line is a file pattern followed by one or more owners. 3 | 4 | # These owners will be the default owners for everything in 5 | # the repo. Unless a later match takes precedence, 6 | # @global-owner1 and @global-owner2 will be requested for 7 | # review when someone opens a pull request. 8 | * @radumarias 9 | 10 | # Order is important; the last matching pattern takes the most 11 | # precedence. When someone opens a pull request that only 12 | # modifies JS files, only @js-owner and not the global 13 | # owner(s) will be requested for a review. 14 | # *.js @js-owner #This is an inline comment. 15 | 16 | # You can also use email addresses if you prefer. They'll be 17 | # used to look up users just like we do for commit author 18 | # emails. 19 | # *.go docs@example.com 20 | 21 | # Teams can be specified as code owners as well. Teams should 22 | # be identified in the format @org/team-name. Teams must have 23 | # explicit write access to the repository. In this example, 24 | # the octocats team in the octo-org organization owns all .txt files. 25 | # *.txt @octo-org/octocats 26 | 27 | # In this example, @doctocat owns any files in the build/logs 28 | # directory at the root of the repository and any of its 29 | # subdirectories. 30 | # /build/logs/ @doctocat 31 | 32 | # The `docs/*` pattern will match files like 33 | # `docs/getting-started.md` but not further nested files like 34 | # `docs/build-app/troubleshooting.md`. 35 | # docs/* docs@example.com 36 | 37 | # In this example, @octocat owns any file in an apps directory 38 | # anywhere in your repository. 39 | # apps/ @octocat 40 | 41 | # In this example, @doctocat owns any file in the `/docs` 42 | # directory in the root of your repository and any of its 43 | # subdirectories. 44 | # /docs/ @doctocat 45 | 46 | # In this example, any change inside the `/scripts` directory 47 | # will require approval from @doctocat or @octocat. 48 | # /scripts/ @doctocat @octocat 49 | 50 | # In this example, @octocat owns any file in a `/logs` directory such as 51 | # `/build/logs`, `/scripts/logs`, and `/deeply/nested/logs`. Any changes 52 | # in a `/logs` directory will require approval from @octocat. 53 | # **/logs @octocat 54 | 55 | # In this example, @octocat owns any file in the `/apps` 56 | # directory in the root of your repository except for the `/apps/github` 57 | # subdirectory, as its owners are left empty. 58 | # /apps/ @octocat 59 | # /apps/github 60 | 61 | # In this example, @octocat owns any file in the `/apps` 62 | # directory in the root of your repository except for the `/apps/github` 63 | # subdirectory, as this subdirectory has its own owner @doctocat 64 | # /apps/ @octocat 65 | # /apps/github @doctocat 66 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | **/target 2 | **/.idea 3 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## How to contribute 2 | 3 | You can use this [CONTRIBUTING](https://github.com/radumarias/rencfs/blob/main/CONTRIBUTING.md) guide as a reference, skipping the steps that don't apply. 4 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "aho-corasick" 7 | version = "1.1.3" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" 10 | dependencies = [ 11 | "memchr", 12 | ] 13 | 14 | [[package]] 15 | name = "anes" 16 | version = "0.1.6" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" 19 | 20 | [[package]] 21 | name = "anstyle" 22 | version = "1.0.8" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" 25 | 26 | [[package]] 27 | name = "arrayref" 28 | version = "0.3.8" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "9d151e35f61089500b617991b791fc8bfd237ae50cd5950803758a179b41e67a" 31 | 32 | [[package]] 33 | name = "arrayvec" 34 | version = "0.7.4" 35 | source = "registry+https://github.com/rust-lang/crates.io-index" 36 | checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" 37 | 38 | [[package]] 39 | name = "autocfg" 40 | version = "1.3.0" 41 | source = "registry+https://github.com/rust-lang/crates.io-index" 42 | checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" 43 | 44 | [[package]] 45 | name = "blake3" 46 | version = "1.5.3" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "e9ec96fe9a81b5e365f9db71fe00edc4fe4ca2cc7dcb7861f0603012a7caa210" 49 | dependencies = [ 50 | "arrayref", 51 | "arrayvec", 52 | "cc", 53 | "cfg-if", 54 | "constant_time_eq", 55 | ] 56 | 57 | [[package]] 58 | name = "block-buffer" 59 | version = "0.10.4" 60 | source = "registry+https://github.com/rust-lang/crates.io-index" 61 | checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" 62 | dependencies = [ 63 | "generic-array", 64 | ] 65 | 66 | [[package]] 67 | name = "bumpalo" 68 | version = "3.16.0" 69 | source = "registry+https://github.com/rust-lang/crates.io-index" 70 | checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" 71 | 72 | [[package]] 73 | name = "cast" 74 | version = "0.3.0" 75 | source = "registry+https://github.com/rust-lang/crates.io-index" 76 | checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" 77 | 78 | [[package]] 79 | name = "cc" 80 | version = "1.1.7" 81 | source = "registry+https://github.com/rust-lang/crates.io-index" 82 | checksum = "26a5c3fd7bfa1ce3897a3a3501d362b2d87b7f2583ebcb4a949ec25911025cbc" 83 | 84 | [[package]] 85 | name = "cfg-if" 86 | version = "1.0.0" 87 | source = "registry+https://github.com/rust-lang/crates.io-index" 88 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 89 | 90 | [[package]] 91 | name = "ciborium" 92 | version = "0.2.2" 93 | source = "registry+https://github.com/rust-lang/crates.io-index" 94 | checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" 95 | dependencies = [ 96 | "ciborium-io", 97 | "ciborium-ll", 98 | "serde", 99 | ] 100 | 101 | [[package]] 102 | name = "ciborium-io" 103 | version = "0.2.2" 104 | source = "registry+https://github.com/rust-lang/crates.io-index" 105 | checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" 106 | 107 | [[package]] 108 | name = "ciborium-ll" 109 | version = "0.2.2" 110 | source = "registry+https://github.com/rust-lang/crates.io-index" 111 | checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" 112 | dependencies = [ 113 | "ciborium-io", 114 | "half", 115 | ] 116 | 117 | [[package]] 118 | name = "clap" 119 | version = "4.5.11" 120 | source = "registry+https://github.com/rust-lang/crates.io-index" 121 | checksum = "35723e6a11662c2afb578bcf0b88bf6ea8e21282a953428f240574fcc3a2b5b3" 122 | dependencies = [ 123 | "clap_builder", 124 | ] 125 | 126 | [[package]] 127 | name = "clap_builder" 128 | version = "4.5.11" 129 | source = "registry+https://github.com/rust-lang/crates.io-index" 130 | checksum = "49eb96cbfa7cfa35017b7cd548c75b14c3118c98b423041d70562665e07fb0fa" 131 | dependencies = [ 132 | "anstyle", 133 | "clap_lex", 134 | ] 135 | 136 | [[package]] 137 | name = "clap_lex" 138 | version = "0.7.2" 139 | source = "registry+https://github.com/rust-lang/crates.io-index" 140 | checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" 141 | 142 | [[package]] 143 | name = "constant_time_eq" 144 | version = "0.3.0" 145 | source = "registry+https://github.com/rust-lang/crates.io-index" 146 | checksum = "f7144d30dcf0fafbce74250a3963025d8d52177934239851c917d29f1df280c2" 147 | 148 | [[package]] 149 | name = "cpufeatures" 150 | version = "0.2.12" 151 | source = "registry+https://github.com/rust-lang/crates.io-index" 152 | checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" 153 | dependencies = [ 154 | "libc", 155 | ] 156 | 157 | [[package]] 158 | name = "criterion" 159 | version = "0.5.1" 160 | source = "registry+https://github.com/rust-lang/crates.io-index" 161 | checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f" 162 | dependencies = [ 163 | "anes", 164 | "cast", 165 | "ciborium", 166 | "clap", 167 | "criterion-plot", 168 | "is-terminal", 169 | "itertools", 170 | "num-traits", 171 | "once_cell", 172 | "oorandom", 173 | "plotters", 174 | "rayon", 175 | "regex", 176 | "serde", 177 | "serde_derive", 178 | "serde_json", 179 | "tinytemplate", 180 | "walkdir", 181 | ] 182 | 183 | [[package]] 184 | name = "criterion-plot" 185 | version = "0.5.0" 186 | source = "registry+https://github.com/rust-lang/crates.io-index" 187 | checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" 188 | dependencies = [ 189 | "cast", 190 | "itertools", 191 | ] 192 | 193 | [[package]] 194 | name = "crossbeam-deque" 195 | version = "0.8.5" 196 | source = "registry+https://github.com/rust-lang/crates.io-index" 197 | checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" 198 | dependencies = [ 199 | "crossbeam-epoch", 200 | "crossbeam-utils", 201 | ] 202 | 203 | [[package]] 204 | name = "crossbeam-epoch" 205 | version = "0.9.18" 206 | source = "registry+https://github.com/rust-lang/crates.io-index" 207 | checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" 208 | dependencies = [ 209 | "crossbeam-utils", 210 | ] 211 | 212 | [[package]] 213 | name = "crossbeam-utils" 214 | version = "0.8.20" 215 | source = "registry+https://github.com/rust-lang/crates.io-index" 216 | checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" 217 | 218 | [[package]] 219 | name = "crunchy" 220 | version = "0.2.2" 221 | source = "registry+https://github.com/rust-lang/crates.io-index" 222 | checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" 223 | 224 | [[package]] 225 | name = "crypto-common" 226 | version = "0.1.6" 227 | source = "registry+https://github.com/rust-lang/crates.io-index" 228 | checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" 229 | dependencies = [ 230 | "generic-array", 231 | "typenum", 232 | ] 233 | 234 | [[package]] 235 | name = "digest" 236 | version = "0.10.7" 237 | source = "registry+https://github.com/rust-lang/crates.io-index" 238 | checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" 239 | dependencies = [ 240 | "block-buffer", 241 | "crypto-common", 242 | ] 243 | 244 | [[package]] 245 | name = "either" 246 | version = "1.13.0" 247 | source = "registry+https://github.com/rust-lang/crates.io-index" 248 | checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" 249 | 250 | [[package]] 251 | name = "file-sync" 252 | version = "0.1.0" 253 | dependencies = [ 254 | "blake3", 255 | ] 256 | 257 | [[package]] 258 | name = "generic-array" 259 | version = "0.14.7" 260 | source = "registry+https://github.com/rust-lang/crates.io-index" 261 | checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" 262 | dependencies = [ 263 | "typenum", 264 | "version_check", 265 | ] 266 | 267 | [[package]] 268 | name = "getrandom" 269 | version = "0.2.15" 270 | source = "registry+https://github.com/rust-lang/crates.io-index" 271 | checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" 272 | dependencies = [ 273 | "cfg-if", 274 | "libc", 275 | "wasi", 276 | ] 277 | 278 | [[package]] 279 | name = "half" 280 | version = "2.4.1" 281 | source = "registry+https://github.com/rust-lang/crates.io-index" 282 | checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" 283 | dependencies = [ 284 | "cfg-if", 285 | "crunchy", 286 | ] 287 | 288 | [[package]] 289 | name = "hermit-abi" 290 | version = "0.3.9" 291 | source = "registry+https://github.com/rust-lang/crates.io-index" 292 | checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" 293 | 294 | [[package]] 295 | name = "hex" 296 | version = "0.4.3" 297 | source = "registry+https://github.com/rust-lang/crates.io-index" 298 | checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" 299 | 300 | [[package]] 301 | name = "is-terminal" 302 | version = "0.4.12" 303 | source = "registry+https://github.com/rust-lang/crates.io-index" 304 | checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b" 305 | dependencies = [ 306 | "hermit-abi", 307 | "libc", 308 | "windows-sys", 309 | ] 310 | 311 | [[package]] 312 | name = "itertools" 313 | version = "0.10.5" 314 | source = "registry+https://github.com/rust-lang/crates.io-index" 315 | checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" 316 | dependencies = [ 317 | "either", 318 | ] 319 | 320 | [[package]] 321 | name = "itoa" 322 | version = "1.0.11" 323 | source = "registry+https://github.com/rust-lang/crates.io-index" 324 | checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" 325 | 326 | [[package]] 327 | name = "js-sys" 328 | version = "0.3.69" 329 | source = "registry+https://github.com/rust-lang/crates.io-index" 330 | checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" 331 | dependencies = [ 332 | "wasm-bindgen", 333 | ] 334 | 335 | [[package]] 336 | name = "libc" 337 | version = "0.2.155" 338 | source = "registry+https://github.com/rust-lang/crates.io-index" 339 | checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" 340 | 341 | [[package]] 342 | name = "log" 343 | version = "0.4.22" 344 | source = "registry+https://github.com/rust-lang/crates.io-index" 345 | checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" 346 | 347 | [[package]] 348 | name = "memchr" 349 | version = "2.7.4" 350 | source = "registry+https://github.com/rust-lang/crates.io-index" 351 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 352 | 353 | [[package]] 354 | name = "num-bigint" 355 | version = "0.4.6" 356 | source = "registry+https://github.com/rust-lang/crates.io-index" 357 | checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" 358 | dependencies = [ 359 | "num-integer", 360 | "num-traits", 361 | ] 362 | 363 | [[package]] 364 | name = "num-integer" 365 | version = "0.1.46" 366 | source = "registry+https://github.com/rust-lang/crates.io-index" 367 | checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" 368 | dependencies = [ 369 | "num-traits", 370 | ] 371 | 372 | [[package]] 373 | name = "num-traits" 374 | version = "0.2.19" 375 | source = "registry+https://github.com/rust-lang/crates.io-index" 376 | checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" 377 | dependencies = [ 378 | "autocfg", 379 | ] 380 | 381 | [[package]] 382 | name = "once_cell" 383 | version = "1.19.0" 384 | source = "registry+https://github.com/rust-lang/crates.io-index" 385 | checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" 386 | 387 | [[package]] 388 | name = "oorandom" 389 | version = "11.1.4" 390 | source = "registry+https://github.com/rust-lang/crates.io-index" 391 | checksum = "b410bbe7e14ab526a0e86877eb47c6996a2bd7746f027ba551028c925390e4e9" 392 | 393 | [[package]] 394 | name = "plotters" 395 | version = "0.3.6" 396 | source = "registry+https://github.com/rust-lang/crates.io-index" 397 | checksum = "a15b6eccb8484002195a3e44fe65a4ce8e93a625797a063735536fd59cb01cf3" 398 | dependencies = [ 399 | "num-traits", 400 | "plotters-backend", 401 | "plotters-svg", 402 | "wasm-bindgen", 403 | "web-sys", 404 | ] 405 | 406 | [[package]] 407 | name = "plotters-backend" 408 | version = "0.3.6" 409 | source = "registry+https://github.com/rust-lang/crates.io-index" 410 | checksum = "414cec62c6634ae900ea1c56128dfe87cf63e7caece0852ec76aba307cebadb7" 411 | 412 | [[package]] 413 | name = "plotters-svg" 414 | version = "0.3.6" 415 | source = "registry+https://github.com/rust-lang/crates.io-index" 416 | checksum = "81b30686a7d9c3e010b84284bdd26a29f2138574f52f5eb6f794fc0ad924e705" 417 | dependencies = [ 418 | "plotters-backend", 419 | ] 420 | 421 | [[package]] 422 | name = "ppv-lite86" 423 | version = "0.2.17" 424 | source = "registry+https://github.com/rust-lang/crates.io-index" 425 | checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" 426 | 427 | [[package]] 428 | name = "proc-macro2" 429 | version = "1.0.86" 430 | source = "registry+https://github.com/rust-lang/crates.io-index" 431 | checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" 432 | dependencies = [ 433 | "unicode-ident", 434 | ] 435 | 436 | [[package]] 437 | name = "quote" 438 | version = "1.0.36" 439 | source = "registry+https://github.com/rust-lang/crates.io-index" 440 | checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" 441 | dependencies = [ 442 | "proc-macro2", 443 | ] 444 | 445 | [[package]] 446 | name = "rand" 447 | version = "0.8.5" 448 | source = "registry+https://github.com/rust-lang/crates.io-index" 449 | checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 450 | dependencies = [ 451 | "libc", 452 | "rand_chacha", 453 | "rand_core", 454 | ] 455 | 456 | [[package]] 457 | name = "rand_chacha" 458 | version = "0.3.1" 459 | source = "registry+https://github.com/rust-lang/crates.io-index" 460 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 461 | dependencies = [ 462 | "ppv-lite86", 463 | "rand_core", 464 | ] 465 | 466 | [[package]] 467 | name = "rand_core" 468 | version = "0.6.4" 469 | source = "registry+https://github.com/rust-lang/crates.io-index" 470 | checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 471 | dependencies = [ 472 | "getrandom", 473 | ] 474 | 475 | [[package]] 476 | name = "rayon" 477 | version = "1.10.0" 478 | source = "registry+https://github.com/rust-lang/crates.io-index" 479 | checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" 480 | dependencies = [ 481 | "either", 482 | "rayon-core", 483 | ] 484 | 485 | [[package]] 486 | name = "rayon-core" 487 | version = "1.12.1" 488 | source = "registry+https://github.com/rust-lang/crates.io-index" 489 | checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" 490 | dependencies = [ 491 | "crossbeam-deque", 492 | "crossbeam-utils", 493 | ] 494 | 495 | [[package]] 496 | name = "regex" 497 | version = "1.10.5" 498 | source = "registry+https://github.com/rust-lang/crates.io-index" 499 | checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f" 500 | dependencies = [ 501 | "aho-corasick", 502 | "memchr", 503 | "regex-automata", 504 | "regex-syntax", 505 | ] 506 | 507 | [[package]] 508 | name = "regex-automata" 509 | version = "0.4.7" 510 | source = "registry+https://github.com/rust-lang/crates.io-index" 511 | checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" 512 | dependencies = [ 513 | "aho-corasick", 514 | "memchr", 515 | "regex-syntax", 516 | ] 517 | 518 | [[package]] 519 | name = "regex-syntax" 520 | version = "0.8.4" 521 | source = "registry+https://github.com/rust-lang/crates.io-index" 522 | checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" 523 | 524 | [[package]] 525 | name = "ryu" 526 | version = "1.0.18" 527 | source = "registry+https://github.com/rust-lang/crates.io-index" 528 | checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" 529 | 530 | [[package]] 531 | name = "same-file" 532 | version = "1.0.6" 533 | source = "registry+https://github.com/rust-lang/crates.io-index" 534 | checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" 535 | dependencies = [ 536 | "winapi-util", 537 | ] 538 | 539 | [[package]] 540 | name = "serde" 541 | version = "1.0.204" 542 | source = "registry+https://github.com/rust-lang/crates.io-index" 543 | checksum = "bc76f558e0cbb2a839d37354c575f1dc3fdc6546b5be373ba43d95f231bf7c12" 544 | dependencies = [ 545 | "serde_derive", 546 | ] 547 | 548 | [[package]] 549 | name = "serde_derive" 550 | version = "1.0.204" 551 | source = "registry+https://github.com/rust-lang/crates.io-index" 552 | checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222" 553 | dependencies = [ 554 | "proc-macro2", 555 | "quote", 556 | "syn", 557 | ] 558 | 559 | [[package]] 560 | name = "serde_json" 561 | version = "1.0.121" 562 | source = "registry+https://github.com/rust-lang/crates.io-index" 563 | checksum = "4ab380d7d9f22ef3f21ad3e6c1ebe8e4fc7a2000ccba2e4d71fc96f15b2cb609" 564 | dependencies = [ 565 | "itoa", 566 | "memchr", 567 | "ryu", 568 | "serde", 569 | ] 570 | 571 | [[package]] 572 | name = "sha2" 573 | version = "0.10.8" 574 | source = "registry+https://github.com/rust-lang/crates.io-index" 575 | checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" 576 | dependencies = [ 577 | "cfg-if", 578 | "cpufeatures", 579 | "digest", 580 | ] 581 | 582 | [[package]] 583 | name = "shard-distribution" 584 | version = "0.1.0" 585 | dependencies = [ 586 | "blake3", 587 | "criterion", 588 | "hex", 589 | "num-bigint", 590 | "num-traits", 591 | "rand", 592 | "sha2", 593 | ] 594 | 595 | [[package]] 596 | name = "syn" 597 | version = "2.0.72" 598 | source = "registry+https://github.com/rust-lang/crates.io-index" 599 | checksum = "dc4b9b9bf2add8093d3f2c0204471e951b2285580335de42f9d2534f3ae7a8af" 600 | dependencies = [ 601 | "proc-macro2", 602 | "quote", 603 | "unicode-ident", 604 | ] 605 | 606 | [[package]] 607 | name = "tinytemplate" 608 | version = "1.2.1" 609 | source = "registry+https://github.com/rust-lang/crates.io-index" 610 | checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" 611 | dependencies = [ 612 | "serde", 613 | "serde_json", 614 | ] 615 | 616 | [[package]] 617 | name = "typenum" 618 | version = "1.17.0" 619 | source = "registry+https://github.com/rust-lang/crates.io-index" 620 | checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" 621 | 622 | [[package]] 623 | name = "unicode-ident" 624 | version = "1.0.12" 625 | source = "registry+https://github.com/rust-lang/crates.io-index" 626 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 627 | 628 | [[package]] 629 | name = "version_check" 630 | version = "0.9.5" 631 | source = "registry+https://github.com/rust-lang/crates.io-index" 632 | checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" 633 | 634 | [[package]] 635 | name = "walkdir" 636 | version = "2.5.0" 637 | source = "registry+https://github.com/rust-lang/crates.io-index" 638 | checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" 639 | dependencies = [ 640 | "same-file", 641 | "winapi-util", 642 | ] 643 | 644 | [[package]] 645 | name = "wasi" 646 | version = "0.11.0+wasi-snapshot-preview1" 647 | source = "registry+https://github.com/rust-lang/crates.io-index" 648 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 649 | 650 | [[package]] 651 | name = "wasm-bindgen" 652 | version = "0.2.92" 653 | source = "registry+https://github.com/rust-lang/crates.io-index" 654 | checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" 655 | dependencies = [ 656 | "cfg-if", 657 | "wasm-bindgen-macro", 658 | ] 659 | 660 | [[package]] 661 | name = "wasm-bindgen-backend" 662 | version = "0.2.92" 663 | source = "registry+https://github.com/rust-lang/crates.io-index" 664 | checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" 665 | dependencies = [ 666 | "bumpalo", 667 | "log", 668 | "once_cell", 669 | "proc-macro2", 670 | "quote", 671 | "syn", 672 | "wasm-bindgen-shared", 673 | ] 674 | 675 | [[package]] 676 | name = "wasm-bindgen-macro" 677 | version = "0.2.92" 678 | source = "registry+https://github.com/rust-lang/crates.io-index" 679 | checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" 680 | dependencies = [ 681 | "quote", 682 | "wasm-bindgen-macro-support", 683 | ] 684 | 685 | [[package]] 686 | name = "wasm-bindgen-macro-support" 687 | version = "0.2.92" 688 | source = "registry+https://github.com/rust-lang/crates.io-index" 689 | checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" 690 | dependencies = [ 691 | "proc-macro2", 692 | "quote", 693 | "syn", 694 | "wasm-bindgen-backend", 695 | "wasm-bindgen-shared", 696 | ] 697 | 698 | [[package]] 699 | name = "wasm-bindgen-shared" 700 | version = "0.2.92" 701 | source = "registry+https://github.com/rust-lang/crates.io-index" 702 | checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" 703 | 704 | [[package]] 705 | name = "web-sys" 706 | version = "0.3.69" 707 | source = "registry+https://github.com/rust-lang/crates.io-index" 708 | checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" 709 | dependencies = [ 710 | "js-sys", 711 | "wasm-bindgen", 712 | ] 713 | 714 | [[package]] 715 | name = "winapi-util" 716 | version = "0.1.8" 717 | source = "registry+https://github.com/rust-lang/crates.io-index" 718 | checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b" 719 | dependencies = [ 720 | "windows-sys", 721 | ] 722 | 723 | [[package]] 724 | name = "windows-sys" 725 | version = "0.52.0" 726 | source = "registry+https://github.com/rust-lang/crates.io-index" 727 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 728 | dependencies = [ 729 | "windows-targets", 730 | ] 731 | 732 | [[package]] 733 | name = "windows-targets" 734 | version = "0.52.6" 735 | source = "registry+https://github.com/rust-lang/crates.io-index" 736 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 737 | dependencies = [ 738 | "windows_aarch64_gnullvm", 739 | "windows_aarch64_msvc", 740 | "windows_i686_gnu", 741 | "windows_i686_gnullvm", 742 | "windows_i686_msvc", 743 | "windows_x86_64_gnu", 744 | "windows_x86_64_gnullvm", 745 | "windows_x86_64_msvc", 746 | ] 747 | 748 | [[package]] 749 | name = "windows_aarch64_gnullvm" 750 | version = "0.52.6" 751 | source = "registry+https://github.com/rust-lang/crates.io-index" 752 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 753 | 754 | [[package]] 755 | name = "windows_aarch64_msvc" 756 | version = "0.52.6" 757 | source = "registry+https://github.com/rust-lang/crates.io-index" 758 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 759 | 760 | [[package]] 761 | name = "windows_i686_gnu" 762 | version = "0.52.6" 763 | source = "registry+https://github.com/rust-lang/crates.io-index" 764 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 765 | 766 | [[package]] 767 | name = "windows_i686_gnullvm" 768 | version = "0.52.6" 769 | source = "registry+https://github.com/rust-lang/crates.io-index" 770 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 771 | 772 | [[package]] 773 | name = "windows_i686_msvc" 774 | version = "0.52.6" 775 | source = "registry+https://github.com/rust-lang/crates.io-index" 776 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 777 | 778 | [[package]] 779 | name = "windows_x86_64_gnu" 780 | version = "0.52.6" 781 | source = "registry+https://github.com/rust-lang/crates.io-index" 782 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 783 | 784 | [[package]] 785 | name = "windows_x86_64_gnullvm" 786 | version = "0.52.6" 787 | source = "registry+https://github.com/rust-lang/crates.io-index" 788 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 789 | 790 | [[package]] 791 | name = "windows_x86_64_msvc" 792 | version = "0.52.6" 793 | source = "registry+https://github.com/rust-lang/crates.io-index" 794 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 795 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ "file-sync", 3 | "shard-distribution", 4 | ] 5 | 6 | resolver = "2" 7 | 8 | [workspace.package] 9 | edition = "2021" 10 | description = "A library for sharding distribution of file chunks" 11 | license = "MIT OR Apache-2.0" 12 | repository = "https://github.com/radumarias/rfs" 13 | readme = "README.md" 14 | authors = ["Radu Marias , Bnchi Fahmi "] 15 | keywords = ["sharding", "files", "distribution", "consistent-hashing", "parition-by-range"] 16 | categories = ["cryptography", "filesystem"] 17 | exclude = [".github/"] 18 | 19 | [workspace.dependencies] 20 | sha2 = "0.10" 21 | hex = "0.4.3" 22 | criterion = "0.5.1" 23 | rand = "0.8.5" 24 | num-bigint = "0.4" 25 | num-traits = "0.2" 26 | blake3 = "1.5.3" 27 | 28 | [profile.release] 29 | panic = "abort" 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rfs 2 | 3 | Distributed filesystem written in Rust. The intention is to be a learning and experimental project for the concepts involved in building such a system. 4 | 5 | > [!WARNING] 6 | > **This is still under development. Please do not use it with sensitive data for now, please wait for a 7 | stable release. 8 | > It's mostly ideal for experimental and learning projects.** 9 | 10 | # A short story 11 | 12 | [The Hitchhiker's Guide to Building a Distributed Filesystem in Rust The_beginning](docs/The_Hitchhiker_s_Guide_to_Building_a_Distributed_Filesystem_in_Rust__The_beginning.pdf) 13 | 14 | # Blog and tutorial 15 | 16 | There will be a [series](https://medium.com/@xorio42/list/317d40f38304) of articles about the evolution of this project trying to keep it like a tutorial. This is the [first one](https://systemweakness.com/hitchhikers-guide-to-building-a-distributed-filesystem-in-rust-the-very-beginning-2c02eb7313e7). 17 | 18 | # [Motivation](https://github.com/radumarias/rfs/wiki/Motivation) and [the big picture](https://github.com/radumarias/rfs/wiki/The-big-picture) 19 | 20 | # Wiki 21 | 22 | There is also a [wiki](https://github.com/radumarias/rfs/wiki) for docs and ideas. 23 | 24 | # Key features 25 | 26 | - Masterless 27 | - BitTorrent with QUIC as transport and zero-copy for synching files 28 | - Sync only changes by diff with Merkle tree 29 | - Client Libs for various languages 30 | - REST API and gRPC access for clients 31 | - Exposed wuth FUSE and NFS 32 | - ACLs and authentication 33 | - Data corruption protection with error correcting codes 34 | - Search and analytics 35 | - Snapshots and versioning 36 | - Protection agains Byzantine failures and other failures 37 | - Xan ve deployed on cluster on On-Prem 38 | 39 | # Contribute 40 | 41 | Feel free to fork it, change and use it in any way that you want. 42 | If you build something interesting and feel like sharing pull requests are always appreciated. 43 | 44 | ## How to contribute 45 | 46 | Please see [CONTRIBUTING.md](CONTRIBUTING.md). 47 | -------------------------------------------------------------------------------- /docs/The_Hitchhiker_s_Guide_to_Building_a_Distributed_Filesystem_in_Rust__The_beginning.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/radumarias/rfs/a5f140b66928285968d0bcd91e5d1e4dad0ab78d/docs/The_Hitchhiker_s_Guide_to_Building_a_Distributed_Filesystem_in_Rust__The_beginning.pdf -------------------------------------------------------------------------------- /file-sync/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "file-sync" 3 | version = "0.1.0" 4 | edition.workspace = true 5 | description.workspace = true 6 | license.workspace = true 7 | repository.workspace = true 8 | readme.workspace = true 9 | authors.workspace = true 10 | keywords.workspace = true 11 | categories.workspace = true 12 | exclude.workspace = true 13 | 14 | [dependencies] 15 | blake3.workspace = true 16 | -------------------------------------------------------------------------------- /file-sync/src/lib.rs: -------------------------------------------------------------------------------- 1 | 2 | #![cfg_attr(not(debug_assertions), deny(warnings))] 3 | #![doc(html_playground_url = "https://play.rust-lang.org")] 4 | #![deny(clippy::all)] 5 | #![deny(clippy::correctness)] 6 | #![deny(clippy::suspicious)] 7 | #![deny(clippy::complexity)] 8 | #![deny(clippy::perf)] 9 | #![deny(clippy::style)] 10 | #![deny(clippy::pedantic)] 11 | #![deny(clippy::nursery)] 12 | #![deny(clippy::cargo)] 13 | // #![deny(missing_docs)] 14 | #![allow(clippy::similar_names)] 15 | #![allow(clippy::too_many_arguments)] 16 | #![allow(clippy::significant_drop_tightening)] 17 | #![allow(clippy::redundant_closure)] 18 | #![allow(clippy::missing_errors_doc)] 19 | pub mod merkle; 20 | -------------------------------------------------------------------------------- /file-sync/src/merkle.rs: -------------------------------------------------------------------------------- 1 | use std::{error::Error, fmt::Display}; 2 | 3 | #[derive(Debug, Clone)] 4 | pub struct Tree { 5 | #[allow(dead_code)] 6 | root: Node, 7 | } 8 | 9 | impl Tree { 10 | /// # Panics 11 | /// 12 | pub fn new(chunks: Vec<&[u8]>) -> Result> { 13 | if chunks.is_empty() { 14 | return Err("There are 0 chunks provided".into()); 15 | } 16 | 17 | let leaves = chunks 18 | .into_iter() 19 | .enumerate() 20 | .map(|(i, d)| Node { 21 | hash: blake3::hash(d).as_bytes().to_vec(), 22 | right: None, 23 | left: None, 24 | chunk_id: Some(i), 25 | }) 26 | .collect::>(); 27 | 28 | let root = Self::build_tree(leaves); 29 | 30 | Ok(Self { root }) 31 | } 32 | 33 | fn build_tree(mut leaves: Vec) -> Node { 34 | if leaves.len() == 1 { 35 | return leaves.remove(0); 36 | } 37 | 38 | let mid = (leaves.len() + 1) / 2; 39 | 40 | let (left_leaves, right_leaves) = leaves.split_at(mid); 41 | 42 | let left = Self::build_tree(left_leaves.to_vec()); 43 | let right = Self::build_tree(right_leaves.to_vec()); 44 | 45 | Node::create_parent(left, right) 46 | } 47 | } 48 | 49 | #[derive(Debug, Clone, Eq)] 50 | pub struct Node { 51 | #[allow(dead_code)] 52 | right: Option>, 53 | 54 | #[allow(dead_code)] 55 | left: Option>, 56 | 57 | #[allow(dead_code)] 58 | hash: Vec, 59 | 60 | #[allow(dead_code)] 61 | chunk_id: Option, 62 | } 63 | 64 | impl PartialEq for Node { 65 | fn eq(&self, other: &Self) -> bool { 66 | self.hash.eq(&other.hash) 67 | } 68 | } 69 | 70 | impl Node { 71 | #[must_use] 72 | pub fn create_parent(left: Self, right: Self) -> Self { 73 | let mut hasher = blake3::Hasher::new(); 74 | let combined_hash = hasher.update(&left.hash).update(&right.hash).finalize(); 75 | 76 | Self { 77 | left: Some(Box::new(left)), 78 | right: Some(Box::new(right)), 79 | hash: combined_hash.as_bytes().to_vec(), 80 | chunk_id: None, 81 | } 82 | } 83 | 84 | fn fmt_with_indent(&self, f: &mut std::fmt::Formatter<'_>, indent: usize) -> std::fmt::Result { 85 | for _ in 0..indent { 86 | write!(f, " ")?; 87 | } 88 | 89 | // Display the first hash byte of the current node 90 | writeln!(f, "Node(hash: [{:?}..])", self.hash[0])?; 91 | 92 | if let Some(left) = &self.left { 93 | for _ in 0..indent { 94 | write!(f, " ")?; 95 | } 96 | write!(f, "L: ")?; 97 | left.fmt_with_indent(f, indent + 1)?; 98 | } 99 | 100 | if let Some(right) = &self.right { 101 | for _ in 0..indent { 102 | write!(f, " ")?; 103 | } 104 | write!(f, "R: ")?; 105 | right.fmt_with_indent(f, indent + 1)?; 106 | } 107 | 108 | Ok(()) 109 | } 110 | } 111 | 112 | impl Display for Node { 113 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 114 | self.fmt_with_indent(f, 0) 115 | } 116 | } 117 | 118 | #[cfg(test)] 119 | mod tests { 120 | use crate::merkle::Tree; 121 | 122 | #[test] 123 | fn create_a_merkle_tree() { 124 | let data: Vec<&[u8]> = vec![b"This", b"creates", b"a", b"balanced", b"merkle", b"tree"]; 125 | 126 | let m_tree_root = Tree::new(data); 127 | 128 | assert!(m_tree_root.is_ok()); 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "nightly" 3 | components = ["rustfmt", "rustc-dev", "clippy"] 4 | -------------------------------------------------------------------------------- /shard-distribution/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "shard-distribution" 3 | version = "0.1.0" 4 | edition = "2021" 5 | description = "A library for sharding distribution of file chunks" 6 | license = "MIT OR Apache-2.0" 7 | repository = "https://github.com/radumarias/rfs" 8 | readme = "README.md" 9 | authors = ["Radu Marias , Bnchi Fahmi "] 10 | keywords = ["sharding", "files", "distribution", "consistent-hashing", "parition-by-range"] 11 | categories = ["cryptography", "filesystem"] 12 | exclude = [".github/"] 13 | 14 | [dependencies] 15 | sha2 = { workspace = true } 16 | hex = { workspace = true } 17 | criterion = { workspace = true } 18 | rand = { workspace = true } 19 | num-bigint = { workspace = true } 20 | num-traits = { workspace = true } 21 | blake3 = { workspace = true } 22 | 23 | [dev-dependencies] 24 | criterion = { version = "^0.5.1", features = ["html_reports"] } 25 | 26 | [[bench]] 27 | name = "consistent_hashing" 28 | harness = false 29 | -------------------------------------------------------------------------------- /shard-distribution/README.md: -------------------------------------------------------------------------------- 1 | # shard-distribution 2 | 3 | ## File sharding 4 | 5 | Collection of several partitioning and sharding distribution algorithms used to determine the nodes on which a chunk 6 | (shard) of a file and its replicas should be placed. 7 | **It doesn't handle the actual data transfer, but only the logical distribution of the shards.** 8 | 9 | You can see more details [File Sharding](https://github.com/radumarias/rfs/wiki/File-sharding). 10 | -------------------------------------------------------------------------------- /shard-distribution/benches/consistent_hashing.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use criterion::{black_box, criterion_group, criterion_main, Criterion}; 4 | use rand::{thread_rng, Rng}; 5 | 6 | use shard_distribution::consistent_hashing::ConsistentHashing; 7 | use shard_distribution::shard_distribution::ShardDistribution; 8 | 9 | fn consistent_hashing(c: &mut Criterion) { 10 | let mut nodes = vec![]; 11 | for i in 0..1_000 { 12 | nodes.push((format!("node{}", i), i as f64)); 13 | } 14 | let nodes: HashMap = nodes.into_iter().collect(); 15 | c.bench_function("create", |b| { 16 | b.iter(|| black_box(ConsistentHashing::new(nodes.clone(), 50))) 17 | }); 18 | 19 | let mut hasher = ConsistentHashing::new(nodes, 50); 20 | c.bench_function("distribute", |b| { 21 | b.iter(|| { 22 | let nodes = hasher.distribute( 23 | format!("key{}", thread_rng().gen_range(0..1_000_000)), 24 | Some(10), 25 | Some(10_f64), 26 | ); 27 | black_box(nodes) 28 | }) 29 | }); 30 | 31 | c.bench_function("add", |b| { 32 | b.iter(|| { 33 | let node = format!("node{}", thread_rng().gen_range(1_000..2_000)); 34 | hasher.add(node.clone(), 10_f64); 35 | black_box(node) 36 | }) 37 | }); 38 | 39 | c.bench_function("update", |b| { 40 | b.iter(|| { 41 | let node = format!("node{}", thread_rng().gen_range(0..1_000)); 42 | hasher.update(node.clone(), 10_f64); 43 | black_box(node) 44 | }) 45 | }); 46 | 47 | c.bench_function("remove", |b| { 48 | b.iter(|| { 49 | let node = format!("node{}", thread_rng().gen_range(0..2_000)); 50 | hasher.remove(node.clone()); 51 | black_box(node) 52 | }) 53 | }); 54 | } 55 | 56 | criterion_group!(benches, consistent_hashing); 57 | criterion_main!(benches); 58 | -------------------------------------------------------------------------------- /shard-distribution/src/consistent_hashing.rs: -------------------------------------------------------------------------------- 1 | use crate::hash::hash; 2 | use crate::shard_distribution::ShardDistribution; 3 | use std::collections::{BTreeMap, HashMap, HashSet}; 4 | 5 | pub struct ConsistentHashing { 6 | nodes: HashMap, 7 | ring: Vec, 8 | node_replicas: u16, 9 | hash_to_node: BTreeMap, 10 | node_to_hashes: BTreeMap>, 11 | } 12 | 13 | impl ConsistentHashing { 14 | pub fn new(nodes: HashMap, replica: u16) -> Self { 15 | let (ring, node_hash_to_id, node_id_to_hashes) = 16 | build_ring(nodes.keys().map(String::clone).collect(), replica); 17 | Self { 18 | nodes, 19 | ring, 20 | node_replicas: replica, 21 | hash_to_node: node_hash_to_id, 22 | node_to_hashes: node_id_to_hashes, 23 | } 24 | } 25 | 26 | #[must_use] 27 | pub const fn nodes(&self) -> &HashMap { 28 | &self.nodes 29 | } 30 | } 31 | 32 | impl ShardDistribution for ConsistentHashing { 33 | fn add(&mut self, node: String, available_resources: f64) { 34 | let hashes = add_node(&node, self.node_replicas); 35 | for hash in hashes { 36 | self.ring.push(hash); 37 | self.hash_to_node.insert(hash, node.clone()); 38 | self.node_to_hashes 39 | .entry(node.clone()) 40 | .or_default() 41 | .insert(hash); 42 | } 43 | self.nodes.insert(node, available_resources); 44 | self.ring.sort_unstable(); 45 | } 46 | 47 | fn update(&mut self, node: String, available_resources: f64) { 48 | if let Some(avail) = self.nodes.get_mut(&node) { 49 | *avail = available_resources; 50 | } 51 | } 52 | 53 | fn remove(&mut self, node: String) { 54 | if self.nodes.contains_key(&node) { 55 | for hash in self.node_to_hashes.get(&node).unwrap() { 56 | let ring_hash_to_idx: BTreeMap = 57 | self.ring.iter().enumerate().map(|(i, v)| (*v, i)).collect(); 58 | self.ring.remove(*ring_hash_to_idx.get(hash).unwrap()); 59 | self.ring.retain(|e| *e != *hash); 60 | self.hash_to_node.remove(hash); 61 | } 62 | self.node_to_hashes.remove(&node); 63 | self.nodes.remove(&node); 64 | } 65 | } 66 | 67 | fn distribute( 68 | &mut self, 69 | entity: String, 70 | entity_replica: Option, 71 | consumed_resources: Option, 72 | ) -> HashSet { 73 | if self.ring.is_empty() || entity_replica.is_none() { 74 | return HashSet::new(); 75 | } 76 | let mut nodes = HashSet::new(); 77 | let mut ring = self.ring.clone(); 78 | if let Some(consumed_resources) = consumed_resources { 79 | // remove servers with not enough resources 80 | self.nodes 81 | .iter() 82 | .filter(|(_, avail)| **avail < consumed_resources) 83 | .for_each(|(node, _)| { 84 | for hash in self.node_to_hashes.get(node).unwrap() { 85 | let ring_hash_to_idx: BTreeMap = 86 | ring.iter().enumerate().map(|(i, v)| (*v, i)).collect(); 87 | ring.remove(*ring_hash_to_idx.get(hash).unwrap()); 88 | } 89 | }); 90 | } 91 | for r in 0..entity_replica.unwrap() { 92 | if ring.is_empty() { 93 | break; 94 | } 95 | let entity_hash = hash(&format!("{entity}-{r}")); 96 | let node_hash = search_node(&ring, entity_hash); 97 | let node = self.hash_to_node.get(&node_hash).unwrap().clone(); 98 | nodes.insert(node.clone()); 99 | // remove nodes with vnodes from the ring 100 | for hash in self.node_to_hashes.get(&node).unwrap() { 101 | let ring_hash_to_idx: BTreeMap = 102 | ring.iter().enumerate().map(|(i, v)| (*v, i)).collect(); 103 | ring.remove(*ring_hash_to_idx.get(hash).unwrap()); 104 | } 105 | if let Some(consumed) = consumed_resources { 106 | if let Some(avail) = self.nodes.get_mut(&node) { 107 | *avail -= consumed; 108 | } 109 | } 110 | } 111 | nodes 112 | } 113 | } 114 | 115 | fn search_node(nodes: &[u64], target: u64) -> u64 { 116 | assert!(!nodes.is_empty(), "No nodes found"); 117 | let mid_idx = nodes.len() / 2; 118 | let mid = nodes[mid_idx]; 119 | if target == mid { 120 | return mid; 121 | } 122 | let left = &nodes[..mid_idx]; 123 | let right = &nodes[mid_idx..]; 124 | if left.is_empty() { 125 | return right[0]; 126 | } else if right.is_empty() { 127 | return left[0]; 128 | } 129 | if target < mid { 130 | search_node(left, target) 131 | } else { 132 | search_node(right, target) 133 | } 134 | } 135 | 136 | type Ring = ( 137 | Vec, 138 | BTreeMap, 139 | BTreeMap>, 140 | ); 141 | 142 | /// Build the ring by adding several virtual nodes to get a more uniform distribution. 143 | #[must_use] 144 | pub fn build_ring(nodes: Vec, replicas: u16) -> Ring { 145 | let mut ring = vec![]; 146 | let mut node_hash_to_id = BTreeMap::new(); 147 | let mut node_id_to_hashes: BTreeMap> = BTreeMap::new(); 148 | 149 | for node in nodes { 150 | let hashes = add_node(&node, replicas); 151 | for hash in hashes { 152 | ring.push(hash); 153 | node_hash_to_id.insert(hash, node.clone()); 154 | node_id_to_hashes 155 | .entry(node.clone()) 156 | .or_default() 157 | .insert(hash); 158 | } 159 | } 160 | ring.sort_unstable(); 161 | (ring, node_hash_to_id, node_id_to_hashes) 162 | } 163 | 164 | /// Add a node with virtual nodes. 165 | fn add_node(node: &str, replicas: u16) -> Vec { 166 | let mut hashes = vec![]; 167 | for r in 0..replicas { 168 | let hash = hash(&format!("{node}-{r}")); 169 | hashes.push(hash); 170 | } 171 | hashes 172 | } 173 | 174 | // add test module 175 | #[cfg(test)] 176 | mod tests { 177 | use crate::consistent_hashing::ConsistentHashing; 178 | use crate::shard_distribution::ShardDistribution; 179 | use std::collections::HashMap; 180 | 181 | #[test] 182 | fn basic() { 183 | let nodes: HashMap = vec![ 184 | ("node1".to_string(), 15_f64), 185 | ("node2".to_string(), 25_f64), 186 | ("node3".to_string(), 30_f64), 187 | ("node4".to_string(), 35_f64), 188 | ("node5".to_string(), 42_f64), 189 | ] 190 | .into_iter() 191 | .collect(); 192 | let mut hasher = ConsistentHashing::new(nodes, 5); 193 | let nodes = hasher.distribute("key1".to_string(), Some(3), Some(10_f64)); 194 | let mut nodes = nodes.iter().cloned().collect::>(); 195 | nodes.sort_unstable(); 196 | println!("key1 {nodes:?}"); 197 | println!("nodes {:?}", hasher.nodes()); 198 | println!(); 199 | assert_eq!(nodes.len(), 3); 200 | 201 | let node = nodes.last().unwrap().clone(); 202 | hasher.remove(node.clone()); 203 | let nodes = hasher.distribute("key1".to_string(), Some(3), None); 204 | println!("{node} removed key1 {nodes:?}"); 205 | println!("nodes {:?}", hasher.nodes()); 206 | println!(); 207 | assert!(!nodes.contains(&node)); 208 | 209 | let nodes = hasher.distribute("key2".to_string(), None, None); 210 | println!("key2 no replicas {nodes:?}"); 211 | println!("nodes {:?}", hasher.nodes()); 212 | println!(); 213 | assert!(nodes.is_empty()); 214 | 215 | hasher.add("node6".to_string(), 10_f64); 216 | let nodes = hasher.distribute("key2".to_string(), Some(3), Some(10_f64)); 217 | println!("node6 added key2 {nodes:?}"); 218 | println!(); 219 | println!("nodes {:?}", hasher.nodes()); 220 | assert_eq!(nodes.len(), 3); 221 | 222 | hasher.remove("node6".to_string()); 223 | hasher.remove("node5".to_string()); 224 | hasher.remove("node4".to_string()); 225 | hasher.remove("node3".to_string()); 226 | let nodes = hasher.distribute("key3".to_string(), Some(3), Some(1_f64)); 227 | println!("node[3-6] removed key3 {nodes:?}"); 228 | println!("nodes {:?}", hasher.nodes()); 229 | println!(); 230 | assert_eq!(nodes.len(), 2); 231 | 232 | let nodes = hasher.distribute("key4".to_string(), Some(3), Some(50_f64)); 233 | println!("use too many resources key4 {nodes:?}"); 234 | println!("nodes {:?}", hasher.nodes()); 235 | println!(); 236 | assert_eq!(nodes.len(), 0); 237 | 238 | hasher.remove("node2".to_string()); 239 | let nodes = hasher.distribute("key3".to_string(), Some(3), None); 240 | println!("node2 removed, lookup key3 {nodes:?}"); 241 | println!("nodes {:?}", hasher.nodes()); 242 | println!(); 243 | assert_eq!(nodes.len(), 1); 244 | 245 | hasher.remove("node1".to_string()); 246 | let nodes = hasher.distribute("key3".to_string(), Some(3), Some(5_f64)); 247 | println!("node1 removed key3 {nodes:?}"); 248 | println!("nodes {:?}", hasher.nodes()); 249 | assert_eq!(nodes.len(), 0); 250 | } 251 | } 252 | -------------------------------------------------------------------------------- /shard-distribution/src/hash.rs: -------------------------------------------------------------------------------- 1 | #[must_use] 2 | #[allow(clippy::missing_panics_doc)] 3 | pub fn hash(entity: &str) -> u64 { 4 | let mut hasher = blake3::Hasher::new(); 5 | hasher.update(entity.as_bytes()); 6 | let result: [u8; 32] = hasher.finalize().into(); 7 | u64::from_be_bytes(result[..8].try_into().unwrap()) 8 | } 9 | -------------------------------------------------------------------------------- /shard-distribution/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(not(debug_assertions), deny(warnings))] 2 | #![doc(html_playground_url = "https://play.rust-lang.org")] 3 | #![deny(clippy::all)] 4 | #![deny(clippy::correctness)] 5 | #![deny(clippy::suspicious)] 6 | #![deny(clippy::complexity)] 7 | #![deny(clippy::perf)] 8 | #![deny(clippy::style)] 9 | #![deny(clippy::pedantic)] 10 | #![deny(clippy::nursery)] 11 | #![deny(clippy::cargo)] 12 | // #![deny(missing_docs)] 13 | #![allow(clippy::similar_names)] 14 | #![allow(clippy::too_many_arguments)] 15 | #![allow(clippy::significant_drop_tightening)] 16 | #![allow(clippy::redundant_closure)] 17 | #![allow(clippy::missing_errors_doc)] 18 | pub mod consistent_hashing; 19 | pub mod hash; 20 | pub mod shard_distribution; 21 | -------------------------------------------------------------------------------- /shard-distribution/src/shard_distribution.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashSet; 2 | 3 | /// Shard distribution in a cluster of nodes based on resources. 4 | pub trait ShardDistribution { 5 | /// Add a new node. 6 | /// 7 | /// `available_resources` available resources for the node used when distributing entities. 8 | fn add(&mut self, node: String, available_resources: f64); 9 | 10 | /// Update the metric of a node. 11 | /// 12 | /// `available_resources` available resources for the node used when distributing entities. 13 | fn update(&mut self, node: String, available_resources: f64); 14 | 15 | /// Remove a node. 16 | fn remove(&mut self, node: String); 17 | 18 | /// Get the nodes on which the entity resides. 19 | /// 20 | /// `consumed_resources` consumed resources for the entity. 21 | /// Nodes with the lower available resources are **NOT** selected. 22 | /// If is `None` then the entity is not distributed, this is useful to do lookup only. 23 | /// 24 | /// Returns a list of nodes based on entity replica count. 25 | /// If there are not enough nodes, it returns the max-available nodes. 26 | fn distribute( 27 | &mut self, 28 | entity: String, 29 | entity_replicas: Option, 30 | consumed_resources: Option, 31 | ) -> HashSet; 32 | } 33 | 34 | #[cfg(test)] 35 | mod tests { 36 | use std::collections::HashMap; 37 | 38 | use crate::consistent_hashing::ConsistentHashing; 39 | use crate::shard_distribution::ShardDistribution; 40 | 41 | #[test] 42 | fn basic() { 43 | let nodes: HashMap = vec![ 44 | ("node1".to_string(), 15_f64), 45 | ("node2".to_string(), 25_f64), 46 | ("node3".to_string(), 30_f64), 47 | ("node4".to_string(), 35_f64), 48 | ("node5".to_string(), 42_f64), 49 | ] 50 | .into_iter() 51 | .collect(); 52 | let mut hasher = ConsistentHashing::new(nodes, 5); 53 | let nodes = hasher.distribute("key1".to_string(), Some(3), Some(10_f64)); 54 | let mut nodes = nodes.iter().cloned().collect::>(); 55 | nodes.sort_unstable(); 56 | println!("key1 {nodes:?}"); 57 | println!("nodes {:?}", hasher.nodes()); 58 | println!(); 59 | assert_eq!(nodes.len(), 3); 60 | 61 | let node = nodes.last().unwrap().clone(); 62 | hasher.remove(node.clone()); 63 | let nodes = hasher.distribute("key1".to_string(), Some(3), None); 64 | println!("{node} removed key1 {nodes:?}"); 65 | println!("nodes {:?}", hasher.nodes()); 66 | println!(); 67 | assert!(!nodes.contains(&node)); 68 | 69 | let nodes = hasher.distribute("key2".to_string(), None, None); 70 | println!("key2 no replicas {nodes:?}"); 71 | println!("nodes {:?}", hasher.nodes()); 72 | println!(); 73 | assert!(nodes.is_empty()); 74 | 75 | hasher.add("node6".to_string(), 10_f64); 76 | let nodes = hasher.distribute("key2".to_string(), Some(3), Some(10_f64)); 77 | println!("node6 added key2 {nodes:?}"); 78 | println!(); 79 | println!("nodes {:?}", hasher.nodes()); 80 | assert_eq!(nodes.len(), 3); 81 | 82 | hasher.remove("node6".to_string()); 83 | hasher.remove("node5".to_string()); 84 | hasher.remove("node4".to_string()); 85 | hasher.remove("node3".to_string()); 86 | let nodes = hasher.distribute("key3".to_string(), Some(3), Some(1_f64)); 87 | println!("node[3-6] removed key3 {nodes:?}"); 88 | println!("nodes {:?}", hasher.nodes()); 89 | println!(); 90 | assert_eq!(nodes.len(), 2); 91 | 92 | let nodes = hasher.distribute("key4".to_string(), Some(3), Some(50_f64)); 93 | println!("use too many resources key4 {nodes:?}"); 94 | println!("nodes {:?}", hasher.nodes()); 95 | println!(); 96 | assert_eq!(nodes.len(), 0); 97 | 98 | hasher.remove("node2".to_string()); 99 | let nodes = hasher.distribute("key3".to_string(), Some(3), None); 100 | println!("node2 removed, lookup key3 {nodes:?}"); 101 | println!("nodes {:?}", hasher.nodes()); 102 | println!(); 103 | assert_eq!(nodes.len(), 1); 104 | 105 | hasher.remove("node1".to_string()); 106 | let nodes = hasher.distribute("key3".to_string(), Some(3), Some(5_f64)); 107 | println!("node1 removed key3 {nodes:?}"); 108 | println!("nodes {:?}", hasher.nodes()); 109 | assert_eq!(nodes.len(), 0); 110 | } 111 | } 112 | --------------------------------------------------------------------------------