From 3616631300ab6d2b2a2cefb002ff567448710e06 Mon Sep 17 00:00:00 2001 From: Andrey Vasnetsov Date: Sun, 30 May 2021 17:14:42 +0200 Subject: [PATCH] Filtrable hnsw (#26) * raw points scorer * raw point scorer for memmap storage * search interface prepare * graph binary saving + store PointOffsetId as u32 * WIP: entry points * connect new link method * update libs + search layer method + visited list + search context + update rust * implement Euclid metric + always use MinHeap for priority queue * small refactor * search for 0 level entry * update visited pool to be lock free and thread safe * use ef_construct from graph layer struct + limit visited links to M * add metric pre-processing before on vector upsert * old hnsw heuristic * save hnsw graph for export * search method + tests * small fixes * add benchmark and profiler * build time optimizations * use SeaHash * remove unsed benchmark * merge hnsw graph function * WIP:HNSW index build function * HNSW build_index with additional indexing * refactor fixtures * graph save and load test * test and fixes for filterable HNSW * enable hnsw index for query planning * fix cardinality estimation tests + remove query planner as class * small refactor * store full copy of collection settings with collection + allow partial override on creation #16 * API for updating collection parameters #16 * refactor: move collection error -> types * report collection status in info API #17 * update OpenAPI Schema --- Cargo.lock | 924 +++++++++++------- Cargo.toml | 2 +- Dockerfile | 4 +- QUICK_START.md | 28 +- config/config.yaml | 10 + docs/redoc/openapi.json | 544 +++++++---- lib/collection/Cargo.toml | 3 +- lib/collection/src/collection.rs | 120 ++- .../collection_builder/collection_builder.rs | 102 +- .../collection_builder/collection_loader.rs | 59 +- .../collection_builder/optimizers_builder.rs | 24 +- lib/collection/src/config.rs | 80 ++ lib/collection/src/lib.rs | 1 + lib/collection/src/operations/config_diff.rs | 118 +++ lib/collection/src/operations/mod.rs | 1 + lib/collection/src/operations/types.rs | 86 +- .../segment_manager/holders/proxy_segment.rs | 2 +- .../optimizers/indexing_optimizer.rs | 29 +- .../optimizers/merge_optimizer.rs | 33 +- .../optimizers/segment_optimizer.rs | 53 +- .../optimizers/vacuum_optimizer.rs | 33 +- .../src/segment_manager/segment_managers.rs | 9 +- .../simple_segment_searcher.rs | 5 +- .../segment_manager/simple_segment_updater.rs | 33 +- .../src/update_handler/update_handler.rs | 50 +- lib/collection/tests/collection_test.rs | 19 +- lib/collection/tests/common/mod.rs | 30 +- lib/segment/Cargo.toml | 23 +- lib/segment/benches/hnsw_build_graph.rs | 53 + lib/segment/benches/prof.rs | 79 ++ lib/segment/benches/vector_search.rs | 23 +- lib/segment/src/common/file_operations.rs | 24 +- lib/segment/src/common/mod.rs | 3 +- lib/segment/src/common/utils.rs | 6 + lib/segment/src/fixtures/index_fixtures.rs | 56 ++ lib/segment/src/fixtures/mod.rs | 2 + lib/segment/src/fixtures/payload_fixtures.rs | 114 +++ .../src/index/field_index/field_index.rs | 13 +- .../src/index/field_index/map_index.rs | 52 +- lib/segment/src/index/field_index/mod.rs | 6 + .../src/index/field_index/numeric_index.rs | 40 +- .../src/index/hnsw_index/build_cache.rs | 105 ++ .../hnsw_index/build_condition_checker.rs | 22 + lib/segment/src/index/hnsw_index/config.rs | 45 + .../src/index/hnsw_index/entry_points.rs | 135 +++ .../src/index/hnsw_index/graph_layers.rs | 665 +++++++++++++ lib/segment/src/index/hnsw_index/hnsw.rs | 244 +++++ lib/segment/src/index/hnsw_index/mod.rs | 11 + .../src/index/hnsw_index/point_scorer.rs | 48 + .../src/index/hnsw_index/search_context.rs | 35 + lib/segment/src/index/hnsw_index/tests/mod.rs | 0 lib/segment/src/index/index.rs | 10 +- lib/segment/src/index/mod.rs | 7 +- lib/segment/src/index/plain_payload_index.rs | 46 +- lib/segment/src/index/query_estimator.rs | 4 +- lib/segment/src/index/sample_estimation.rs | 102 ++ lib/segment/src/index/struct_payload_index.rs | 54 +- lib/segment/src/index/visited_pool.rs | 86 ++ lib/segment/src/lib.rs | 6 +- lib/segment/src/payload_storage/mod.rs | 2 +- .../src/payload_storage/payload_storage.rs | 2 +- lib/segment/src/query_planner/mod.rs | 2 - .../src/query_planner/query_planner.rs | 18 - .../src/query_planner/simple_query_planner.rs | 35 - lib/segment/src/segment.rs | 21 +- .../segment_constructor/segment_builder.rs | 2 +- .../segment_constructor.rs | 58 +- .../simple_segment_constructor.rs | 2 +- lib/segment/src/spaces/simple.rs | 28 +- lib/segment/src/spaces/tools.rs | 88 +- lib/segment/src/types.rs | 54 +- .../vector_storage/memmap_vector_storage.rs | 356 +++---- .../src/vector_storage/mmap_vectors.rs | 131 +++ lib/segment/src/vector_storage/mod.rs | 1 + .../vector_storage/simple_vector_storage.rs | 222 +++-- .../src/vector_storage/vector_storage.rs | 55 +- lib/segment/tests/filtrable_hnsw_test.rs | 130 +++ lib/segment/tests/payload_index_test.rs | 170 ++-- lib/storage/src/content_manager/errors.rs | 2 +- .../src/content_manager/storage_ops.rs | 18 +- lib/storage/src/content_manager/toc.rs | 65 +- lib/storage/src/types.rs | 10 +- openapi/models.json | 562 +++++++---- openapi/openapi-merged.yaml | 486 ++++++--- openapi/openapi.yaml | 28 +- 85 files changed, 5247 insertions(+), 1822 deletions(-) create mode 100644 lib/collection/src/config.rs create mode 100644 lib/collection/src/operations/config_diff.rs create mode 100644 lib/segment/benches/hnsw_build_graph.rs create mode 100644 lib/segment/benches/prof.rs create mode 100644 lib/segment/src/common/utils.rs create mode 100644 lib/segment/src/fixtures/index_fixtures.rs create mode 100644 lib/segment/src/fixtures/mod.rs create mode 100644 lib/segment/src/fixtures/payload_fixtures.rs create mode 100644 lib/segment/src/index/hnsw_index/build_cache.rs create mode 100644 lib/segment/src/index/hnsw_index/build_condition_checker.rs create mode 100644 lib/segment/src/index/hnsw_index/config.rs create mode 100644 lib/segment/src/index/hnsw_index/entry_points.rs create mode 100644 lib/segment/src/index/hnsw_index/graph_layers.rs create mode 100644 lib/segment/src/index/hnsw_index/hnsw.rs create mode 100644 lib/segment/src/index/hnsw_index/mod.rs create mode 100644 lib/segment/src/index/hnsw_index/point_scorer.rs create mode 100644 lib/segment/src/index/hnsw_index/search_context.rs create mode 100644 lib/segment/src/index/hnsw_index/tests/mod.rs create mode 100644 lib/segment/src/index/sample_estimation.rs create mode 100644 lib/segment/src/index/visited_pool.rs delete mode 100644 lib/segment/src/query_planner/mod.rs delete mode 100644 lib/segment/src/query_planner/query_planner.rs delete mode 100644 lib/segment/src/query_planner/simple_query_planner.rs create mode 100644 lib/segment/src/vector_storage/mmap_vectors.rs create mode 100644 lib/segment/tests/filtrable_hnsw_test.rs diff --git a/Cargo.lock b/Cargo.lock index 63f306c119..1dd2864eab 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,7 +11,7 @@ dependencies = [ "futures-core", "futures-sink", "log 0.4.14", - "pin-project 0.4.27", + "pin-project 0.4.28", "tokio 0.2.25", "tokio-util", ] @@ -71,15 +71,15 @@ dependencies = [ "log 0.4.14", "mime", "percent-encoding", - "pin-project 1.0.5", + "pin-project 1.0.7", "rand 0.7.3", - "regex 1.4.3", - "serde 1.0.123", + "regex 1.4.6", + "serde 1.0.125", "serde_json", "serde_urlencoded", "sha-1", "slab", - "time 0.2.25", + "time 0.2.26", ] [[package]] @@ -101,8 +101,8 @@ dependencies = [ "bytestring", "http", "log 0.4.14", - "regex 1.4.3", - "serde 1.0.123", + "regex 1.4.6", + "serde 1.0.125", ] [[package]] @@ -147,7 +147,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0052435d581b5be835d11f4eb3bce417c8af18d87ddf8ace99f8e67e595882bb" dependencies = [ "futures-util", - "pin-project 0.4.27", + "pin-project 0.4.28", ] [[package]] @@ -207,7 +207,7 @@ dependencies = [ "futures-sink", "futures-util", "log 0.4.14", - "pin-project 0.4.27", + "pin-project 0.4.28", "slab", ] @@ -239,13 +239,13 @@ dependencies = [ "fxhash", "log 0.4.14", "mime", - "pin-project 1.0.5", - "regex 1.4.3", - "serde 1.0.123", + "pin-project 1.0.7", + "regex 1.4.6", + "serde 1.0.125", "serde_json", "serde_urlencoded", "socket2", - "time 0.2.25", + "time 0.2.26", "tinyvec", "url", ] @@ -272,9 +272,26 @@ dependencies = [ [[package]] name = "adler" -version = "0.2.3" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee2a4ec343196209d6594e19543ae87a39f96d5534d7174822a3ad825dd6ed7e" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "ahash" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "739f4a8db6605981345c5654f3a85b056ce52f37a39d34da03f25bf2151ea16e" + +[[package]] +name = "ahash" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "796540673305a66d127804eef19ad696f1f204b8c1025aaca4958c17eab32877" +dependencies = [ + "getrandom 0.2.2", + "once_cell", + "version_check", +] [[package]] name = "aho-corasick" @@ -294,15 +311,6 @@ dependencies = [ "memchr 2.3.4", ] -[[package]] -name = "ansi_term" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" -dependencies = [ - "winapi 0.3.9", -] - [[package]] name = "approx" version = "0.4.0" @@ -312,6 +320,15 @@ dependencies = [ "num-traits 0.2.14", ] +[[package]] +name = "arrayvec" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd9fd44efafa8690358b7408d253adf110036b88f55672a933f01d616ad9b1b9" +dependencies = [ + "nodrop", +] + [[package]] name = "arrayvec" version = "0.5.2" @@ -320,21 +337,21 @@ checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" [[package]] name = "as-slice" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb4d1c23475b74e3672afa8c2be22040b8b7783ad9b461021144ed10a46bb0e6" +checksum = "45403b49e3954a4b8428a0ac21a4b7afadccf92bfd96273f1a58cd4812496ae0" dependencies = [ - "generic-array 0.12.3", - "generic-array 0.13.2", + "generic-array 0.12.4", + "generic-array 0.13.3", "generic-array 0.14.4", "stable_deref_trait", ] [[package]] name = "async-trait" -version = "0.1.42" +version = "0.1.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d3a45e77e34375a7923b1e8febb049bb011f064714a8e17a1a616fef01da13d" +checksum = "0b98e84bbb4cbcdd97da190ba0c58a1bb0de2c1fdf67d159e192ed766aeca722" dependencies = [ "proc-macro2", "quote", @@ -343,9 +360,9 @@ dependencies = [ [[package]] name = "atomic_refcell" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bc31dce067eab974c815a9deb95f6217806de7b53685d7fc31f8ccf3fb2539f" +checksum = "681b971236e0f76b20fcafca0236b8718c9186ee778d67cd78bd5f28fd85427f" [[package]] name = "atomicwrites" @@ -353,7 +370,7 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a2baf2feb820299c53c7ad1cc4f5914a220a1cb76d7ce321d2522a94b54651f" dependencies = [ - "nix", + "nix 0.14.1", "tempdir", "winapi 0.3.9", ] @@ -394,18 +411,19 @@ dependencies = [ "mime", "percent-encoding", "rand 0.7.3", - "serde 1.0.123", + "serde 1.0.125", "serde_json", "serde_urlencoded", ] [[package]] name = "backtrace" -version = "0.3.56" +version = "0.3.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d117600f438b1707d4e4ae15d3595657288f8235a0eb593e80ecc98ab34e1bc" +checksum = "88fb5a785d6b44fd9d6700935608639af1b8356de1e55d5f7c2740f4faa15d82" dependencies = [ "addr2line", + "cc", "cfg-if 1.0.0", "libc", "miniz_oxide", @@ -427,38 +445,38 @@ checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" [[package]] name = "bincode" -version = "1.3.1" +version = "1.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f30d3a39baa26f9651f17b375061f3233dde33424a8b72b0dbe93a68a0bc896d" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" dependencies = [ - "byteorder 1.4.2", - "serde 1.0.123", + "serde 1.0.125", ] [[package]] name = "bindgen" -version = "0.54.0" +version = "0.57.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66c0bb6167449588ff70803f4127f0684f9063097eca5016f37eb52b92c2cf36" +checksum = "fd4865004a46a0aafb2a0a5eb19d3c9fc46ee5f063a6cfc605c69ac9ecf5263d" dependencies = [ "bitflags", "cexpr", - "cfg-if 0.1.10", "clang-sys", - "clap", - "env_logger 0.7.1", "lazy_static 1.4.0", "lazycell", - "log 0.4.14", "peeking_take_while", "proc-macro2", "quote", - "regex 1.4.3", + "regex 1.4.6", "rustc-hash", "shlex", - "which", ] +[[package]] +name = "bit-vec" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" + [[package]] name = "bitflags" version = "1.2.1" @@ -467,9 +485,9 @@ checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" [[package]] name = "blas-src" -version = "0.6.1" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a25f136d1bcc6fc28330077511a0646aaf75bc894e5532b1a33a93d814272328" +checksum = "bb48fbaa7a0cb9d6d96c46bac6cedb16f13a10aebcef1c4e73515aaae8c9909d" dependencies = [ "openblas-src", ] @@ -512,14 +530,14 @@ dependencies = [ "lazy_static 1.4.0", "memchr 2.3.4", "regex-automata", - "serde 1.0.123", + "serde 1.0.125", ] [[package]] name = "build_const" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39092a32794787acd8525ee150305ff051b0aa6cc2abaf193924f5ab05425f39" +checksum = "b4ae4235e6dac0694637c763029ecea1a2ec9e4e06ec2729bd21ba4d9c863eb7" [[package]] name = "bumpalo" @@ -527,6 +545,12 @@ version = "3.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "63396b8a4b9de3f4fdfb320ab6080762242f66a8ef174c49d8e19b674db4cdbe" +[[package]] +name = "bytemuck" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bed57e2090563b83ba8f83366628ce535a7584c9afa4c9fc0612a03925c6df58" + [[package]] name = "byteorder" version = "0.4.2" @@ -535,9 +559,9 @@ checksum = "96c8b41881888cc08af32d47ac4edd52bc7fa27fef774be47a92443756451304" [[package]] name = "byteorder" -version = "1.4.2" +version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae44d1a3d5a19df61dd0c8beb138458ac2a53a7ac09eba97d55592540004306b" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "bytes" @@ -568,9 +592,9 @@ dependencies = [ [[package]] name = "cast" -version = "0.2.3" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b9434b9a5aa1450faa3f9cb14ea0e8c53bb5d2b3c1bfd1ab4fc03e9f33fbfb0" +checksum = "cc38c385bfd7e444464011bb24820f40dd1c76bcdfa1b78611cb7c2e5cafab75" dependencies = [ "rustc_version", ] @@ -616,9 +640,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clang-sys" -version = "0.29.3" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe6837df1d5cba2397b835c8530f51723267e16abbf83892e9e5af4f0e5dd10a" +checksum = "853eda514c284c2287f4bf20ae614f8781f40a81d32ecda6e91449304dfe077c" dependencies = [ "glob", "libc", @@ -631,18 +655,14 @@ version = "2.33.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002" dependencies = [ - "ansi_term", - "atty", "bitflags", - "strsim 0.8.0", "textwrap", "unicode-width", - "vec_map", ] [[package]] name = "collection" -version = "0.1.0" +version = "0.3.0" dependencies = [ "atomicwrites", "criterion", @@ -652,13 +672,14 @@ dependencies = [ "indicatif", "itertools 0.9.0", "log 0.4.14", + "merge", "ordered-float", "parking_lot", "rand 0.7.3", "rmp-serde", "schemars", "segment", - "serde 1.0.123", + "serde 1.0.125", "serde_cbor", "serde_json", "tempdir", @@ -676,7 +697,7 @@ dependencies = [ "lazy_static 1.4.0", "nom", "rust-ini", - "serde 1.0.123", + "serde 1.0.125", "serde-hjson", "serde_json", "toml", @@ -685,14 +706,14 @@ dependencies = [ [[package]] name = "console" -version = "0.14.0" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cc80946b3480f421c2f17ed1cb841753a371c7c5104f51d507e13f532c856aa" +checksum = "3993e6445baa160675931ec041a5e03ca84b9c6e32a056150d3aa2bdda0a1f45" dependencies = [ "encode_unicode", "lazy_static 1.4.0", "libc", - "regex 1.4.3", + "regex 1.4.6", "terminal_size", "unicode-width", "winapi 0.3.9", @@ -700,18 +721,24 @@ dependencies = [ [[package]] name = "const_fn" -version = "0.4.5" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28b9d6de7f49e22cf97ad17fc4036ece69300032f45f78f30b4a4482cdc3f4a6" +checksum = "402da840495de3f976eaefc3485b7f5eb5b0bf9761f9a47be27fe975b3b8c2ec" + +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" [[package]] name = "cookie" -version = "0.14.3" +version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "784ad0fbab4f3e9cef09f20e0aea6000ae08d2cb98ac4c0abc53df18803d702f" +checksum = "03a5d7b21829bc7b4bf4754a978a241ae54ea55a40f92bb20216e54096f4b951" dependencies = [ "percent-encoding", - "time 0.2.25", + "time 0.2.26", "version_check", ] @@ -721,6 +748,16 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2df960f5d869b2dd8532793fde43eb5427cceb126c929747a26823ab0eeb536" +[[package]] +name = "cpp_demangle" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44919ecaf6f99e8e737bc239408931c9a01e9a6c74814fee8242dd2506b65390" +dependencies = [ + "cfg-if 1.0.0", + "glob", +] + [[package]] name = "cpuid-bool" version = "0.1.2" @@ -762,8 +799,8 @@ dependencies = [ "oorandom", "plotters", "rayon", - "regex 1.4.3", - "serde 1.0.123", + "regex 1.4.6", + "serde 1.0.125", "serde_cbor", "serde_derive", "serde_json", @@ -793,12 +830,12 @@ dependencies = [ [[package]] name = "crossbeam-channel" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dca26ee1f8d361640700bde38b2c37d8c22b3ce2d360e1fc1c74ea4b0aa7d775" +checksum = "06ed27e177f16d65f0f0c22a213e17c696ace5dd64b14258b52f9417ccb52db4" dependencies = [ "cfg-if 1.0.0", - "crossbeam-utils 0.8.2", + "crossbeam-utils 0.8.3", ] [[package]] @@ -809,19 +846,18 @@ checksum = "94af6efb46fef72616855b036a624cf27ba656ffc9be1b9a3c931cfc7749a9a9" dependencies = [ "cfg-if 1.0.0", "crossbeam-epoch", - "crossbeam-utils 0.8.2", + "crossbeam-utils 0.8.3", ] [[package]] name = "crossbeam-epoch" -version = "0.9.2" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d60ab4a8dba064f2fbb5aa270c28da5cf4bbd0e72dae1140a6b0353a779dbe00" +checksum = "2584f639eb95fea8c798496315b297cf81b9b58b6d30ab066a75455333cf4b12" dependencies = [ "cfg-if 1.0.0", - "crossbeam-utils 0.8.2", + "crossbeam-utils 0.8.3", "lazy_static 1.4.0", - "loom", "memoffset", "scopeguard", ] @@ -839,27 +875,26 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bae8f328835f8f5a6ceb6a7842a7f2d0c03692adb5c889347235d59194731fe3" +checksum = "e7e9d99fa91428effe99c5c6d4634cdeba32b8cf784fc428a2a687f61a952c49" dependencies = [ "autocfg", "cfg-if 1.0.0", "lazy_static 1.4.0", - "loom", ] [[package]] name = "csv" -version = "1.1.5" +version = "1.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9d58633299b24b515ac72a3f869f8b91306a3cec616a602843a383acd6f9e97" +checksum = "22813a6dc45b335f9bade10bf7271dc477e81113e89eb251a0bc2a8a81c536e1" dependencies = [ "bstr", "csv-core", "itoa", "ryu", - "serde 1.0.123", + "serde 1.0.125", ] [[package]] @@ -872,11 +907,21 @@ dependencies = [ ] [[package]] -name = "derive_more" -version = "0.99.11" +name = "debugid" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41cb0e6161ad61ed084a36ba71fbba9e3ac5aee3606fb607fe08da6acbcf3d8c" +checksum = "f91cf5a8c2f2097e2a32627123508635d47ce10563d999ec1a95addf08b502ba" dependencies = [ + "uuid", +] + +[[package]] +name = "derive_more" +version = "0.99.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f82b1b72f1263f214c0f823371768776c4f5841b942c9883aa8e5ec584fd0ba6" +dependencies = [ + "convert_case", "proc-macro2", "quote", "syn", @@ -891,6 +936,26 @@ dependencies = [ "generic-array 0.14.4", ] +[[package]] +name = "dirs" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30baa043103c9d0c2a57cf537cc2f35623889dc0d405e6c3cccfadbc81c71309" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03d86534ed367a67548dc68113a0f5db55432fdfbb6e6f9d77704397d95d5780" +dependencies = [ + "libc", + "redox_users", + "winapi 0.3.9", +] + [[package]] name = "discard" version = "1.0.4" @@ -906,14 +971,14 @@ dependencies = [ "lazy_static 0.2.11", "regex 0.1.80", "rustc-serialize", - "strsim 0.5.2", + "strsim", ] [[package]] name = "dtoa" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88d7ed2934d741c6b37e33e3832298e8850b53fd2d2bea03873375596c7cea4e" +checksum = "56899898ce76aaf4a0f24d914c97ea6ed976d42fec6ad33fcbb0a1103e07b2b0" [[package]] name = "dyn-clone" @@ -973,7 +1038,7 @@ dependencies = [ "atty", "humantime", "log 0.4.14", - "regex 1.4.3", + "regex 1.4.6", "termcolor", ] @@ -988,6 +1053,28 @@ dependencies = [ "time 0.1.43", ] +[[package]] +name = "fasthash" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "032213946b4eaae09117ec63f020322b78ca7a31d8aa2cf64df3032e1579690f" +dependencies = [ + "cfg-if 0.1.10", + "fasthash-sys", + "num-traits 0.2.14", + "seahash", + "xoroshiro128", +] + +[[package]] +name = "fasthash-sys" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6de941abfe2e715cdd34009d90546f850597eb69ca628ddfbf616e53dda28f8" +dependencies = [ + "gcc", +] + [[package]] name = "flate2" version = "1.0.20" @@ -1061,9 +1148,9 @@ checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" [[package]] name = "futures" -version = "0.3.12" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da9052a1a50244d8d5aa9bf55cbc2fb6f357c86cc52e46c62ed390a7180cf150" +checksum = "a9d5813545e459ad3ca1bff9915e9ad7f1a47dc6a91b627ce321d5863b7dd253" dependencies = [ "futures-channel", "futures-core", @@ -1076,9 +1163,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.12" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2d31b7ec7efab6eefc7c57233bb10b847986139d88cc2f5a02a1ae6871a1846" +checksum = "ce79c6a52a299137a6013061e0cf0e688fce5d7f1bc60125f520912fdb29ec25" dependencies = [ "futures-core", "futures-sink", @@ -1086,15 +1173,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.12" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79e5145dde8da7d1b3892dad07a9c98fc04bc39892b1ecc9692cf53e2b780a65" +checksum = "098cd1c6dda6ca01650f1a37a794245eb73181d0d4d4e955e2f3c37db7af1815" [[package]] name = "futures-executor" -version = "0.3.12" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9e59fdc009a4b3096bf94f740a0f2424c082521f20a9b08c5c07c48d90fd9b9" +checksum = "10f6cb7042eda00f0049b1d2080aa4b93442997ee507eb3828e8bd7577f94c9d" dependencies = [ "futures-core", "futures-task", @@ -1103,15 +1190,15 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.12" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28be053525281ad8259d47e4de5de657b25e7bac113458555bb4b70bc6870500" +checksum = "365a1a1fb30ea1c03a830fdb2158f5236833ac81fa0ad12fe35b29cddc35cb04" [[package]] name = "futures-macro" -version = "0.3.12" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c287d25add322d9f9abdcdc5927ca398917996600182178774032e9f8258fedd" +checksum = "668c6733a182cd7deb4f1de7ba3bf2120823835b3bcfbeacf7d2c4a773c1bb8b" dependencies = [ "proc-macro-hack", "proc-macro2", @@ -1121,24 +1208,21 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.12" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "caf5c69029bda2e743fddd0582d1083951d65cc9539aebf8812f36c3491342d6" +checksum = "5c5629433c555de3d82861a7a4e3794a4c40040390907cfbfd7143a92a426c23" [[package]] name = "futures-task" -version = "0.3.12" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13de07eb8ea81ae445aca7b69f5f7bf15d7bf4912d8ca37d6645c77ae8a58d86" -dependencies = [ - "once_cell", -] +checksum = "ba7aa51095076f3ba6d9a1f702f74bd05ec65f555d70d2033d55ba8d69f581bc" [[package]] name = "futures-util" -version = "0.3.12" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "632a8cd0f2a4b3fdea1657f08bde063848c3bd00f9bbf6e256b8be78802e624b" +checksum = "3c144ad54d60f23927f0a6b6d816e4271278b64f005ad65e4e35291d2de9c025" dependencies = [ "futures-channel", "futures-core", @@ -1147,7 +1231,7 @@ dependencies = [ "futures-sink", "futures-task", "memchr 2.3.4", - "pin-project-lite 0.2.4", + "pin-project-lite 0.2.6", "pin-utils", "proc-macro-hack", "proc-macro-nested", @@ -1160,36 +1244,29 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" dependencies = [ - "byteorder 1.4.2", + "byteorder 1.4.3", ] [[package]] -name = "generator" -version = "0.6.24" +name = "gcc" +version = "0.3.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9fed24fd1e18827652b4d55652899a1e9da8e54d91624dc3437a5bc3a9f9a9c" -dependencies = [ - "cc", - "libc", - "log 0.4.14", - "rustversion", - "winapi 0.3.9", -] +checksum = "8f5f3913fa0bfe7ee1fd8248b6b9f42a5af4b9d65ec2dd2c3c26132b950ecfc2" [[package]] name = "generic-array" -version = "0.12.3" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c68f0274ae0e023facc3c97b2e00f076be70e254bc851d972503b328db79b2ec" +checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd" dependencies = [ "typenum", ] [[package]] name = "generic-array" -version = "0.13.2" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ed1e761351b56f54eb9dcd0cfaca9fd0daecf93918e1cfc01c8a3d26ee7adcd" +checksum = "f797e67af32588215eaaab8327027ee8e71b9dd0b2b26996aedf20c030fce309" dependencies = [ "typenum", ] @@ -1206,9 +1283,9 @@ dependencies = [ [[package]] name = "geo" -version = "0.17.0" +version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479219490dcbbf2a374908457df0b4d5b1d8eb62eb48b0ee897f9af9e7c44210" +checksum = "bb5120badf60b67440af049f8c1a265aa44bdccc34027a5bb39eb3b081c56526" dependencies = [ "geo-types", "geographiclib-rs", @@ -1219,9 +1296,9 @@ dependencies = [ [[package]] name = "geo-types" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f53acb0efebf761179d5346ab1dc2b3657535d461bde5eaf4465ff7fee7a9a15" +checksum = "d8bd2e95dd9f5c8ff74159ed9205ad7fd239a9569173a550863976421b45d2bb" dependencies = [ "approx", "num-traits 0.2.14", @@ -1303,7 +1380,7 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4041af86e63ac4298ce40e5cca669066e75b6f1aa3390fe2561ffa5e1d9f4cc" dependencies = [ - "byteorder 1.4.2", + "byteorder 1.4.3", ] [[package]] @@ -1311,6 +1388,9 @@ name = "hashbrown" version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04" +dependencies = [ + "ahash 0.4.7", +] [[package]] name = "heapless" @@ -1319,7 +1399,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74911a68a1658cfcfb61bc0ccfbd536e3b6e906f8c2f7883ee50157e3e2184f1" dependencies = [ "as-slice", - "generic-array 0.13.2", + "generic-array 0.13.3", "hash32", "stable_deref_trait", ] @@ -1355,9 +1435,9 @@ dependencies = [ [[package]] name = "http" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7245cd7449cc792608c3c8a9eaf69bd4eabbabf802713748fd739c98b82f0747" +checksum = "527e8c9ac747e28542699a951517aa9a6945af506cd1f2e1b53a576c17b6cc11" dependencies = [ "bytes 1.0.1", "fnv", @@ -1366,9 +1446,9 @@ dependencies = [ [[package]] name = "httparse" -version = "1.3.5" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "615caabe2c3160b313d52ccc905335f4ed5f10881dd63dc5699d47e90be85691" +checksum = "4a1ce40d6fc9764887c2fdc7305c3dcc429ba11ff981c1509416afd5697e4437" [[package]] name = "humantime" @@ -1381,9 +1461,9 @@ dependencies = [ [[package]] name = "idna" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89829a5d69c23d348314a7ac337fe39173b61149a9864deabd260983aed48c21" +checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" dependencies = [ "matches", "unicode-bidi", @@ -1392,9 +1472,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "1.6.1" +version = "1.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fb1fa934250de4de8aef298d81c729a7d33d8c239daa3a7575e6b92bfc7313b" +checksum = "824845a0bf897a9042383849b02c1bc219c2383772efcd5c6f9766fa4b81aef3" dependencies = [ "autocfg", "hashbrown", @@ -1409,7 +1489,25 @@ dependencies = [ "console", "lazy_static 1.4.0", "number_prefix", - "regex 1.4.3", + "regex 1.4.6", +] + +[[package]] +name = "inferno" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67bcf81cd7094c6827cec657c53f0d22dec1afed5eeccb16118f5763a7bbd869" +dependencies = [ + "ahash 0.6.3", + "atty", + "indexmap", + "itoa", + "lazy_static 1.4.0", + "log 0.4.14", + "num-format", + "quick-xml", + "rgb", + "str_stack", ] [[package]] @@ -1468,18 +1566,18 @@ checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736" [[package]] name = "jobserver" -version = "0.1.21" +version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c71313ebb9439f74b00d9d2dcec36440beaf57a6aa0623068441dd7cd81a7f2" +checksum = "972f5ae5d1cb9c6ae417789196c803205313edde988685da5e3aae0827b9e7fd" dependencies = [ "libc", ] [[package]] name = "js-sys" -version = "0.3.47" +version = "0.3.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cfb73131c35423a367daf8cbd24100af0d077668c8c2943f0e7dd775fef0f65" +checksum = "2d99f9e3e84b8f67f846ef5b4cbbc3b1c29f6c759fcbce6f01aa0e73d932a24c" dependencies = [ "wasm-bindgen", ] @@ -1520,11 +1618,11 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "lexical-core" -version = "0.7.5" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21f866863575d0e1d654fbeeabdc927292fdf862873dc3c96c6f753357e13374" +checksum = "6607c62aa161d23d17a9072cc5da0be67cdfc89d3afb1e8d9c842bebc2525ffe" dependencies = [ - "arrayvec", + "arrayvec 0.5.2", "bitflags", "cfg-if 1.0.0", "ryu", @@ -1533,25 +1631,25 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.86" +version = "0.2.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7282d924be3275cec7f6756ff4121987bc6481325397dde6ba3e7802b1a8b1c" +checksum = "18794a8ad5b29321f790b55d93dfba91e125cb1a9edbd4f8e3150acc771c1a5e" [[package]] name = "libloading" -version = "0.5.2" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2b111a074963af1d37a139918ac6d49ad1d0d5e47f72fd55388619691a7d753" +checksum = "6f84d96438c15fcd6c3f244c8fce01d1e2b9c6b5623e9c711dc9286d8fc92d6a" dependencies = [ - "cc", + "cfg-if 1.0.0", "winapi 0.3.9", ] [[package]] name = "librocksdb-sys" -version = "6.11.4" +version = "6.17.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb5b56f651c204634b936be2f92dbb42c36867e00ff7fe2405591f3b9fa66f09" +checksum = "5da125e1c0f22c7cae785982115523a0738728498547f415c9054cb17c7e89f9" dependencies = [ "bindgen", "cc", @@ -1577,9 +1675,9 @@ checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3" [[package]] name = "lock_api" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd96ffd135b2fd7b973ac026d28085defbe8983df057ced3eb4f2130b0831312" +checksum = "5a3c91c24eae6777794bb1997ad98bbb87daf92890acab859f7eaa4320333176" dependencies = [ "scopeguard", ] @@ -1603,14 +1701,12 @@ dependencies = [ ] [[package]] -name = "loom" -version = "0.4.0" +name = "lru" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d44c73b4636e497b4917eb21c33539efa3816741a2d3ff26c6316f1b529481a4" +checksum = "1f374d42cdfc1d7dbf3d3dec28afab2eb97ffbf43a3234d795b5986dbf4b90ba" dependencies = [ - "cfg-if 1.0.0", - "generator", - "scoped-tls", + "hashbrown", ] [[package]] @@ -1636,9 +1732,9 @@ checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" [[package]] name = "matrixmultiply" -version = "0.2.4" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "916806ba0031cd542105d916a97c8572e1fa6dd79c9c51e7eb43a09ec2dd84c1" +checksum = "5a8a15b776d9dfaecd44b03c5828c2199cddff5247215858aac14624f8d6b741" dependencies = [ "rawpointer", ] @@ -1688,13 +1784,35 @@ dependencies = [ [[package]] name = "memoffset" -version = "0.6.1" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "157b4208e3059a8f9e78d559edc658e13df41410cb3ae03979c83130067fdd87" +checksum = "f83fb6581e8ed1f85fd45c116db8405483899489e38406156c25eb743554361d" dependencies = [ "autocfg", ] +[[package]] +name = "merge" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10bbef93abb1da61525bbc45eeaff6473a41907d19f8f9aa5168d214e10693e9" +dependencies = [ + "merge_derive", + "num-traits 0.2.14", +] + +[[package]] +name = "merge_derive" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "209d075476da2e63b4b29e72a2ef627b840589588e71400a25e3565c4f849d07" +dependencies = [ + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "mime" version = "0.3.16" @@ -1703,9 +1821,9 @@ checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" [[package]] name = "miniz_oxide" -version = "0.4.3" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f2d26ec3309788e423cfbf68ad1800f061638098d76a83681af979dc4eda19d" +checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" dependencies = [ "adler", "autocfg", @@ -1732,13 +1850,13 @@ dependencies = [ [[package]] name = "mio" -version = "0.7.8" +version = "0.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc250d6848c90d719ea2ce34546fb5df7af1d3fd189d10bf7bad80bfcebecd95" +checksum = "cf80d3e903b34e0bd7282b218398aec54e082c840d9baf8339e0080a0c542956" dependencies = [ "libc", "log 0.4.14", - "miow 0.3.6", + "miow 0.3.7", "ntapi", "winapi 0.3.9", ] @@ -1768,22 +1886,21 @@ dependencies = [ [[package]] name = "miow" -version = "0.3.6" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a33c1b55807fbed163481b5ba66db4b2fa6cde694a5027be10fb724206c5897" +checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21" dependencies = [ - "socket2", "winapi 0.3.9", ] [[package]] name = "ndarray" -version = "0.14.0" +version = "0.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c0d5c9540a691d153064dc47a4db2504587a75eae07bf1d73f7a596ebc73c04" +checksum = "cc1372704f14bb132a49a6701c2238970a359ee0829fed481b522a63bf25456a" dependencies = [ - "blas-src", "cblas-sys", + "libc", "matrixmultiply", "num-complex", "num-integer", @@ -1815,6 +1932,24 @@ dependencies = [ "void", ] +[[package]] +name = "nix" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa9b4819da1bc61c0ea48b63b7bc8604064dd43013e7cc325df098d49cd7c18a" +dependencies = [ + "bitflags", + "cc", + "cfg-if 1.0.0", + "libc", +] + +[[package]] +name = "nodrop" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" + [[package]] name = "nom" version = "5.1.2" @@ -1837,13 +1972,23 @@ dependencies = [ [[package]] name = "num-complex" -version = "0.3.1" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "747d632c0c558b87dbabbe6a82f3b4ae03720d0646ac5b7b4dae89394be5f2c5" +checksum = "26873667bbbb7c5182d4a37c1add32cdf09f841af72da53318fdb81543c15085" dependencies = [ "num-traits 0.2.14", ] +[[package]] +name = "num-format" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bafe4179722c2894288ee77a9f044f02811c86af699344c498b0840c698a2465" +dependencies = [ + "arrayvec 0.4.12", + "itoa", +] + [[package]] name = "num-integer" version = "0.1.44" @@ -1896,9 +2041,9 @@ checksum = "a9a7ab5d64814df0fe4a4b5ead45ed6c5f181ee3ff04ba344313a6c80446c5d4" [[package]] name = "once_cell" -version = "1.6.0" +version = "1.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ad167a2f54e832b82dbe003a046280dceffe5227b5f79e08e363a29638cfddd" +checksum = "af8b08b04175473088b46763e51ee54da5f9a164bc162f615b91bc179dbf15a3" [[package]] name = "oorandom" @@ -1913,10 +2058,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[package]] -name = "openblas-src" -version = "0.9.0" +name = "openblas-build" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84629d1d4f9f40d3f9e4756dae33c6af50d3c2fcc186558dac65bf55141ade6b" +checksum = "59ecb601fc56534b17316cec4a4d8c848840f2c6744538a4e0fd3833d24f2e8f" +dependencies = [ + "thiserror", + "walkdir", +] + +[[package]] +name = "openblas-src" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35a0967b308cab5942eb908b4737385719bef9e1b4953614a0e9d26ad5d41c69" +dependencies = [ + "dirs", + "openblas-build", + "vcpkg", +] [[package]] name = "ordered-float" @@ -1972,27 +2132,27 @@ checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" [[package]] name = "pin-project" -version = "0.4.27" +version = "0.4.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ffbc8e94b38ea3d2d8ba92aea2983b503cd75d0888d75b86bb37970b5698e15" +checksum = "918192b5c59119d51e0cd221f4d49dde9112824ba717369e903c97d076083d0f" dependencies = [ - "pin-project-internal 0.4.27", + "pin-project-internal 0.4.28", ] [[package]] name = "pin-project" -version = "1.0.5" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96fa8ebb90271c4477f144354485b8068bd8f6b78b428b01ba892ca26caf0b63" +checksum = "c7509cc106041c40a4518d2af7a61530e1eed0e6285296a3d8c5472806ccc4a4" dependencies = [ - "pin-project-internal 1.0.5", + "pin-project-internal 1.0.7", ] [[package]] name = "pin-project-internal" -version = "0.4.27" +version = "0.4.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65ad2ae56b6abe3a1ee25f15ee605bacadb9a764edaba9c2bf4103800d4a1895" +checksum = "3be26700300be6d9d23264c73211d8190e755b6b5ca7a1b28230025511b52a5e" dependencies = [ "proc-macro2", "quote", @@ -2001,9 +2161,9 @@ dependencies = [ [[package]] name = "pin-project-internal" -version = "1.0.5" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "758669ae3558c6f74bd2a18b41f7ac0b5a195aea6639d6a9b5e5d1ad5ba24c0b" +checksum = "48c950132583b500556b1efd71d45b319029f2b71518d979fcc208e16b42426f" dependencies = [ "proc-macro2", "quote", @@ -2012,15 +2172,15 @@ dependencies = [ [[package]] name = "pin-project-lite" -version = "0.1.11" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c917123afa01924fc84bb20c4c03f004d9c38e5127e3c039bbf7f4b9c76a2f6b" +checksum = "257b64915a082f7811703966789728173279bdebb956b143dbcd23f6f970a777" [[package]] name = "pin-project-lite" -version = "0.2.4" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439697af366c49a6d0a010c56a0d97685bc140ce0d377b13a2ea2aa42d64a827" +checksum = "dc0e1f259c92177c30a4c9d177246edd0a3568b25756a977d0632cf8fa37e905" [[package]] name = "pin-utils" @@ -2056,12 +2216,54 @@ dependencies = [ "plotters-backend", ] +[[package]] +name = "pprof" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "234cb1ca0d59aa771d9bc7e268739d7aef6ca7e9e8d3b78d92f266cd663fd0c1" +dependencies = [ + "backtrace", + "inferno", + "lazy_static 1.4.0", + "libc", + "log 0.4.14", + "nix 0.20.0", + "parking_lot", + "symbolic-demangle", + "tempfile", + "thiserror", +] + [[package]] name = "ppv-lite86" version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + [[package]] name = "proc-macro-hack" version = "0.5.19" @@ -2076,16 +2278,16 @@ checksum = "bc881b2c22681370c6a780e47af9840ef841837bc98118431d4e1868bd0c1086" [[package]] name = "proc-macro2" -version = "1.0.24" +version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71" +checksum = "a152013215dca273577e18d2bf00fa862b89b24169fb78c4c95aeb07992c9cec" dependencies = [ "unicode-xid", ] [[package]] name = "qdrant" -version = "0.2.1" +version = "0.3.0" dependencies = [ "actix-web", "collection", @@ -2098,7 +2300,7 @@ dependencies = [ "rand 0.7.3", "schemars", "segment", - "serde 1.0.123", + "serde 1.0.125", "serde_json", "serde_yaml", "storage", @@ -2113,6 +2315,15 @@ version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" +[[package]] +name = "quick-xml" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26aab6b48e2590e4a64d1ed808749ba06257882b461d01ca71baeb747074a6dd" +dependencies = [ + "memchr 2.3.4", +] + [[package]] name = "quote" version = "1.0.9" @@ -2265,9 +2476,9 @@ version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ab346ac5921dc62ffa9f89b7a773907511cdfa5490c572ae9be1be33e8afa4a" dependencies = [ - "crossbeam-channel 0.5.0", + "crossbeam-channel 0.5.1", "crossbeam-deque", - "crossbeam-utils 0.8.2", + "crossbeam-utils 0.8.3", "lazy_static 1.4.0", "num_cpus", ] @@ -2283,13 +2494,23 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94341e4e44e24f6b591b59e47a8a027df12e008d73fd5672dbea9cc22f4507d9" +checksum = "8270314b5ccceb518e7e578952f0b72b88222d02e8f77f5ecf7abbb673539041" dependencies = [ "bitflags", ] +[[package]] +name = "redox_users" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64" +dependencies = [ + "getrandom 0.2.2", + "redox_syscall", +] + [[package]] name = "regex" version = "0.1.80" @@ -2299,20 +2520,19 @@ dependencies = [ "aho-corasick 0.5.3", "memchr 0.1.11", "regex-syntax 0.3.9", - "thread_local 0.2.7", + "thread_local", "utf8-ranges", ] [[package]] name = "regex" -version = "1.4.3" +version = "1.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9251239e129e16308e70d853559389de218ac275b515068abc96829d05b948a" +checksum = "2a26af418b574bd56588335b3a3659a65725d4e636eb1016c2f9e3b38c7cc759" dependencies = [ "aho-corasick 0.7.15", "memchr 2.3.4", - "regex-syntax 0.6.22", - "thread_local 1.1.3", + "regex-syntax 0.6.23", ] [[package]] @@ -2321,7 +2541,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae1ded71d66a4a97f5e961fd0cb25a5f366a42a41570d16a763a69c092c26ae4" dependencies = [ - "byteorder 1.4.2", + "byteorder 1.4.3", ] [[package]] @@ -2332,9 +2552,9 @@ checksum = "f9ec002c35e86791825ed294b50008eea9ddfc8def4420124fbc6b08db834957" [[package]] name = "regex-syntax" -version = "0.6.22" +version = "0.6.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5eb417147ba9860a96cfe72a0b93bf88fee1744b5636ec99ab20c1aa9376581" +checksum = "24d5f089152e60f62d28b835fbff2cd2e8dc0baf1ac13343bef92ab7eed84548" [[package]] name = "remove_dir_all" @@ -2355,13 +2575,22 @@ dependencies = [ "quick-error", ] +[[package]] +name = "rgb" +version = "0.8.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fddb3b23626145d1776addfc307e1a1851f60ef6ca64f376bcb889697144cf0" +dependencies = [ + "bytemuck", +] + [[package]] name = "rmp" version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4f55e5fa1446c4d5dd1f5daeed2a4fe193071771a2636274d0d7a3b082aa7ad6" dependencies = [ - "byteorder 1.4.2", + "byteorder 1.4.3", "num-traits 0.2.14", ] @@ -2371,9 +2600,9 @@ version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ce7d70c926fe472aed493b902010bccc17fa9f7284145cb8772fd22fdb052d8" dependencies = [ - "byteorder 1.4.2", + "byteorder 1.4.3", "rmp", - "serde 1.0.123", + "serde 1.0.125", ] [[package]] @@ -2412,9 +2641,9 @@ checksum = "3e52c148ef37f8c375d49d5a73aa70713125b7f19095948a923f80afdeb22ec2" [[package]] name = "rustc-demangle" -version = "0.1.18" +version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e3bad0ee36814ca07d7968269dd4b7ec89ec2da10c4bb613928d3077083c232" +checksum = "410f7acf3cb3a44527c5d9546bad4bf4e6c460915d5f9f2fc524498bfe8f70ce" [[package]] name = "rustc-hash" @@ -2437,12 +2666,6 @@ dependencies = [ "semver", ] -[[package]] -name = "rustversion" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb5d2a036dc6d2d8fd16fde3498b04306e29bd193bf306a57427019b823d5acd" - [[package]] name = "ryu" version = "1.0.5" @@ -2460,21 +2683,21 @@ dependencies = [ [[package]] name = "schemars" -version = "0.8.0" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "763f667253711994847f7e73befe859d6fff7bea2b7a7f01669d2c5b60765c37" +checksum = "bc6ab463ae35acccb5cba66c0084c985257b797d288b6050cc2f6ac1b266cb78" dependencies = [ "dyn-clone", "schemars_derive", - "serde 1.0.123", + "serde 1.0.125", "serde_json", ] [[package]] name = "schemars_derive" -version = "0.8.0" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1d457e2e37415f32b7628ddc5a7fea06ef63bd029ed180d65166e87ca25ce21" +checksum = "902fdfbcf871ae8f653bddf4b2c05905ddaabc08f69d32a915787e3be0d31356" dependencies = [ "proc-macro2", "quote", @@ -2482,41 +2705,45 @@ dependencies = [ "syn", ] -[[package]] -name = "scoped-tls" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2" - [[package]] name = "scopeguard" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +[[package]] +name = "seahash" +version = "3.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58f57ca1d128a43733fd71d583e837b1f22239a37ebea09cde11d8d9a9080f47" + [[package]] name = "segment" -version = "0.2.1" +version = "0.3.0" dependencies = [ "atomic_refcell", "atomicwrites", "bincode", + "bit-vec", "blas-src", "criterion", "env_logger 0.7.1", + "fasthash", "geo", "itertools 0.10.0", "log 0.4.14", + "lru", "memmap 0.7.0", "ndarray", "num-traits 0.2.14", "openblas-src", "ordered-float", + "parking_lot", + "pprof", "rand 0.8.3", - "rmp-serde", "rocksdb", "schemars", - "serde 1.0.123", + "serde 1.0.125", "serde_cbor", "serde_json", "tempdir", @@ -2547,9 +2774,9 @@ checksum = "9dad3f759919b92c3068c696c15c3d17238234498bbdcc80f2c469606f948ac8" [[package]] name = "serde" -version = "1.0.123" +version = "1.0.125" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92d5161132722baa40d802cc70b15262b98258453e85e5d1d365c757c73869ae" +checksum = "558dc50e1a5a5fa7112ca2ce4effcb321b0300c0d4ccf0776a9f60cd89031171" dependencies = [ "serde_derive", ] @@ -2563,7 +2790,7 @@ dependencies = [ "lazy_static 1.4.0", "linked-hash-map 0.3.0", "num-traits 0.1.43", - "regex 1.4.3", + "regex 1.4.6", "serde 0.8.23", ] @@ -2574,14 +2801,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e18acfa2f90e8b735b2836ab8d538de304cbb6729a7360729ea5a895d15a622" dependencies = [ "half", - "serde 1.0.123", + "serde 1.0.125", ] [[package]] name = "serde_derive" -version = "1.0.123" +version = "1.0.125" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9391c295d64fc0abb2c556bad848f33cb8296276b1ad2677d1ae1ace4f258f31" +checksum = "b093b7a2bb58203b5da3056c05b4ec1fed827dcfdb37347a8841695263b3d06d" dependencies = [ "proc-macro2", "quote", @@ -2601,13 +2828,13 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.62" +version = "1.0.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea1c6153794552ea7cf7cf63b1231a25de00ec90db326ba6264440fa08e31486" +checksum = "799e97dc9fdae36a5c8b8f2cae9ce2ee9fdce2058c57a93e6099d919fd982f79" dependencies = [ "itoa", "ryu", - "serde 1.0.123", + "serde 1.0.125", ] [[package]] @@ -2628,7 +2855,7 @@ dependencies = [ "form_urlencoded", "itoa", "ryu", - "serde 1.0.123", + "serde 1.0.125", ] [[package]] @@ -2639,7 +2866,7 @@ checksum = "15654ed4ab61726bf918a39cb8d98a2e2995b002387807fa6ba58fdf7f59bb23" dependencies = [ "dtoa", "linked-hash-map 0.5.4", - "serde 1.0.123", + "serde 1.0.125", "yaml-rust", ] @@ -2679,9 +2906,9 @@ dependencies = [ [[package]] name = "slab" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" +checksum = "f173ac3d1a7e3b28003f40de0b5ce7fe2710f9b9dc3fc38664cebee46b3b6527" [[package]] name = "sled" @@ -2691,7 +2918,7 @@ checksum = "1d0132f3e393bcb7390c60bb45769498cf4550bcb7a21d7f95c02b69f6362cdc" dependencies = [ "crc32fast", "crossbeam-epoch", - "crossbeam-utils 0.8.2", + "crossbeam-utils 0.8.3", "fs2 0.4.3", "fxhash", "libc", @@ -2724,9 +2951,9 @@ checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" [[package]] name = "standback" -version = "0.2.15" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2beb4d1860a61f571530b3f855a1b538d0200f7871c63331ecd6f17b1f014f8" +checksum = "e113fb6f3de07a243d434a56ec6f186dfd51cb08448239fe7bcae73f87ff28ff" dependencies = [ "version_check", ] @@ -2759,7 +2986,7 @@ checksum = "c87a60a40fccc84bef0652345bbbbbe20a605bf5d0ce81719fc476f5c03b50ef" dependencies = [ "proc-macro2", "quote", - "serde 1.0.123", + "serde 1.0.125", "serde_derive", "syn", ] @@ -2773,7 +3000,7 @@ dependencies = [ "base-x", "proc-macro2", "quote", - "serde 1.0.123", + "serde 1.0.125", "serde_derive", "serde_json", "sha1", @@ -2796,13 +3023,19 @@ dependencies = [ "rand 0.7.3", "schemars", "segment", - "serde 1.0.123", + "serde 1.0.125", "sled", "thiserror", "tokio 0.3.7", "wal", ] +[[package]] +name = "str_stack" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9091b6114800a5f2141aee1d1b9d6ca3592ac062dc5decb3764ec5895a47b4eb" + [[package]] name = "strsim" version = "0.5.2" @@ -2810,16 +3043,33 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67f84c44fbb2f91db7fef94554e6b2ac05909c9c0b0bc23bb98d3a1aebfe7f7c" [[package]] -name = "strsim" -version = "0.8.0" +name = "symbolic-common" +version = "8.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" +checksum = "be7dfa630954f18297ceae1ff2890cb7f5008a0b2d2106b0468dafc45b0b6b12" +dependencies = [ + "debugid", + "memmap 0.7.0", + "stable_deref_trait", + "uuid", +] + +[[package]] +name = "symbolic-demangle" +version = "8.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b4ba42bd1221803e965054767b1899f2db9a12c89969965c6cb3a02af7014eb" +dependencies = [ + "cpp_demangle", + "rustc-demangle", + "symbolic-common", +] [[package]] name = "syn" -version = "1.0.60" +version = "1.0.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c700597eca8a5a762beb35753ef6b94df201c81cca676604f547495a0d7f0081" +checksum = "b9505f307c872bab8eb46f77ae357c8eba1fdacead58ee5a850116b1d7f82883" dependencies = [ "proc-macro2", "quote", @@ -2846,6 +3096,20 @@ dependencies = [ "remove_dir_all", ] +[[package]] +name = "tempfile" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "rand 0.8.3", + "redox_syscall", + "remove_dir_all", + "winapi 0.3.9", +] + [[package]] name = "termcolor" version = "1.1.2" @@ -2913,15 +3177,6 @@ dependencies = [ "thread-id", ] -[[package]] -name = "thread_local" -version = "1.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8018d24e04c95ac8790716a5987d0fec4f8b27249ffa0f7d33f1369bdfb88cbd" -dependencies = [ - "once_cell", -] - [[package]] name = "threadpool" version = "1.8.1" @@ -2943,9 +3198,9 @@ dependencies = [ [[package]] name = "time" -version = "0.2.25" +version = "0.2.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1195b046942c221454c2539395f85413b33383a067449d78aab2b7b052a142f7" +checksum = "08a8cbfbf47955132d0202d1662f49b2423ae35862aee471f3ba4b133358f372" dependencies = [ "const_fn", "libc", @@ -2981,19 +3236,19 @@ dependencies = [ [[package]] name = "tinytemplate" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2ada8616fad06a2d0c455adc530de4ef57605a8120cc65da9653e0e9623ca74" +checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" dependencies = [ - "serde 1.0.123", + "serde 1.0.125", "serde_json", ] [[package]] name = "tinyvec" -version = "1.1.1" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "317cca572a0e89c3ce0ca1f1bdc9369547fe318a683418e42ac8f59d14701023" +checksum = "5b5220f05bb7de7f3f53c7c065e1199b3172696fe2db9f9c4d8ad9b4ee74c342" dependencies = [ "tinyvec_macros", ] @@ -3018,7 +3273,7 @@ dependencies = [ "memchr 2.3.4", "mio 0.6.23", "mio-uds", - "pin-project-lite 0.1.11", + "pin-project-lite 0.1.12", "signal-hook-registry", "slab", "winapi 0.3.9", @@ -3035,11 +3290,11 @@ dependencies = [ "futures-core", "libc", "memchr 2.3.4", - "mio 0.7.8", + "mio 0.7.11", "num_cpus", "once_cell", "parking_lot", - "pin-project-lite 0.2.4", + "pin-project-lite 0.2.6", "signal-hook-registry", "slab", "tokio-macros", @@ -3067,7 +3322,7 @@ dependencies = [ "futures-core", "futures-sink", "log 0.4.14", - "pin-project-lite 0.1.11", + "pin-project-lite 0.1.12", "tokio 0.2.25", ] @@ -3077,18 +3332,18 @@ version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa" dependencies = [ - "serde 1.0.123", + "serde 1.0.125", ] [[package]] name = "tracing" -version = "0.1.24" +version = "0.1.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f77d3842f76ca899ff2dbcf231c5c65813dea431301d6eb686279c15c4464f12" +checksum = "01ebdc2bb4498ab1ab5f5b73c5803825e60199229ccba0698170e3be0e7f959f" dependencies = [ "cfg-if 1.0.0", "log 0.4.14", - "pin-project-lite 0.2.4", + "pin-project-lite 0.2.6", "tracing-core", ] @@ -3107,18 +3362,18 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2" dependencies = [ - "pin-project 1.0.5", + "pin-project 1.0.7", "tracing", ] [[package]] name = "trust-dns-proto" -version = "0.19.6" +version = "0.19.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53861fcb288a166aae4c508ae558ed18b53838db728d4d310aad08270a7d4c2b" +checksum = "1cad71a0c0d68ab9941d2fb6e82f8fb2e86d9945b94e1661dd0aaea2b88215a9" dependencies = [ "async-trait", - "backtrace", + "cfg-if 1.0.0", "enum-as-inner", "futures", "idna", @@ -3133,11 +3388,10 @@ dependencies = [ [[package]] name = "trust-dns-resolver" -version = "0.19.6" +version = "0.19.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6759e8efc40465547b0dfce9500d733c65f969a4cbbfbe3ccf68daaa46ef179e" +checksum = "710f593b371175db53a26d0b38ed2978fafb9e9e8d3868b1acd753ea18df0ceb" dependencies = [ - "backtrace", "cfg-if 0.1.10", "futures", "ipconfig", @@ -3153,15 +3407,15 @@ dependencies = [ [[package]] name = "typenum" -version = "1.12.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33" +checksum = "879f6906492a7cd215bfa4cf595b600146ccfac0c79bcbd1f3000162af5e8b06" [[package]] name = "unicode-bidi" -version = "0.3.4" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" +checksum = "eeb8be209bb1c96b7c177c7420d26e04eccacb0eeae6b980e35fcb74678107e0" dependencies = [ "matches", ] @@ -3221,16 +3475,16 @@ dependencies = [ ] [[package]] -name = "vec_map" -version = "0.8.2" +name = "vcpkg" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" +checksum = "cbdbff6266a24120518560b5dc983096efb98462e51d0d68169895b237be3e5d" [[package]] name = "version_check" -version = "0.9.2" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed" +checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" [[package]] name = "void" @@ -3258,9 +3512,9 @@ dependencies = [ [[package]] name = "walkdir" -version = "2.3.1" +version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "777182bc735b6424e1a57516d35ed72cb8019d85c8c9bf536dccb3445c1a2f7d" +checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" dependencies = [ "same-file", "winapi 0.3.9", @@ -3281,9 +3535,9 @@ checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" [[package]] name = "wasm-bindgen" -version = "0.2.70" +version = "0.2.73" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55c0f7123de74f0dab9b7d00fd614e7b19349cd1e2f5252bbe9b1754b59433be" +checksum = "83240549659d187488f91f33c0f8547cbfef0b2088bc470c116d1d260ef623d9" dependencies = [ "cfg-if 1.0.0", "wasm-bindgen-macro", @@ -3291,9 +3545,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.70" +version = "0.2.73" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7bc45447f0d4573f3d65720f636bbcc3dd6ce920ed704670118650bcd47764c7" +checksum = "ae70622411ca953215ca6d06d3ebeb1e915f0f6613e3b495122878d7ebec7dae" dependencies = [ "bumpalo", "lazy_static 1.4.0", @@ -3306,9 +3560,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.70" +version = "0.2.73" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b8853882eef39593ad4174dd26fc9865a64e84026d223f63bb2c42affcbba2c" +checksum = "3e734d91443f177bfdb41969de821e15c516931c3c3db3d318fa1b68975d0f6f" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3316,9 +3570,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.70" +version = "0.2.73" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4133b5e7f2a531fa413b3a1695e925038a05a71cf67e87dafa295cb645a01385" +checksum = "d53739ff08c8a68b0fdbcd54c372b8ab800b1449ab3c9d706503bc7dd1621b2c" dependencies = [ "proc-macro2", "quote", @@ -3329,29 +3583,20 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.70" +version = "0.2.73" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd4945e4943ae02d15c13962b38a5b1e81eadd4b71214eee75af64a4d6a4fd64" +checksum = "d9a543ae66aa233d14bb765ed9af4a33e81b8b58d1584cf1b47ff8cd0b9e4489" [[package]] name = "web-sys" -version = "0.3.47" +version = "0.3.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c40dc691fc48003eba817c38da7113c15698142da971298003cac3ef175680b3" +checksum = "a905d57e488fec8861446d3393670fb50d27a262344013181c2cdf9fff5481be" dependencies = [ "js-sys", "wasm-bindgen", ] -[[package]] -name = "which" -version = "3.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d011071ae14a2f6671d0b74080ae0cd8ebf3a6f8c9589a2cd45f23126fe29724" -dependencies = [ - "libc", -] - [[package]] name = "widestring" version = "0.4.3" @@ -3420,6 +3665,15 @@ dependencies = [ "winapi-build", ] +[[package]] +name = "xoroshiro128" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0eeda34baec49c4f1eb2c04d59b761582fd6330010f9330ca696ca1a355dfcd" +dependencies = [ + "rand 0.4.6", +] + [[package]] name = "yaml-rust" version = "0.4.5" diff --git a/Cargo.toml b/Cargo.toml index b504c44b4f..eb720368e0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "qdrant" -version = "0.2.1" +version = "0.3.0" authors = ["Andrey Vasnetsov "] edition = "2018" doctest = false diff --git a/Dockerfile b/Dockerfile index 4046db48f1..343373c3e4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,10 +1,10 @@ -FROM rust:1.49 as builder +FROM rust:1.51 as builder COPY . ./qdrant WORKDIR ./qdrant ENV OPENBLAS_TARGET=CORE2 -RUN apt-get update ; apt-get install -y clang libopenblas-dev libgfortran-8-dev +RUN apt-get update ; apt-get install -y clang libopenblas-dev libgfortran-8-dev gfortran # Build actual target here RUN cargo build --release --bin qdrant diff --git a/QUICK_START.md b/QUICK_START.md index 382f21426f..a3eb0a4b4c 100644 --- a/QUICK_START.md +++ b/QUICK_START.md @@ -36,19 +36,33 @@ Expected response: ```json { "result": { + "status": "green", "vectors_count": 0, "segments_count": 5, "disk_data_size": 0, "ram_data_size": 0, "config": { - "vector_size": 4, - "index": { - "type": "plain", - "options": {} + "params": { + "vector_size": 4, + "distance": "Dot" }, - "distance": "Dot", - "storage_type": { - "type": "in_memory" + "hnsw_config": { + "m": 16, + "ef_construct": 100, + "full_scan_threshold": 10000 + }, + "optimizer_config": { + "deleted_threshold": 0.2, + "vacuum_min_vector_number": 1000, + "max_segment_number": 5, + "memmap_threshold": 50000, + "indexing_threshold": 20000, + "payload_indexing_threshold": 10000, + "flush_interval_sec": 1 + }, + "wal_config": { + "wal_capacity_mb": 32, + "wal_segments_ahead": 0 } } }, diff --git a/config/config.yaml b/config/config.yaml index c7dfc23423..283e2a55f7 100644 --- a/config/config.yaml +++ b/config/config.yaml @@ -39,6 +39,16 @@ storage: # Minimum interval between forced flushes. flush_interval_sec: 10 + # Default parameters of HNSW Index. Could be override for each collection individually + hnsw_index: + # Number of edges per node in the index graph. Larger the value - more accurate the search, more space required. + m: 16 + # Number of neighbours to consider during the index building. Larger the value - more accurate the search, more time required to build index. + ef_construct: 100 + # Minimal amount of points for additional payload-based indexing. + # If payload chunk is smaller than `full_scan_threshold` additional indexing won't be used - + # in this case full-scan search should be preferred by query planner and additional indexing is not required. + full_scan_threshold: 10000 service: diff --git a/docs/redoc/openapi.json b/docs/redoc/openapi.json index 1b276ee3ca..47818c6cdb 100644 --- a/docs/redoc/openapi.json +++ b/docs/redoc/openapi.json @@ -4,6 +4,7 @@ "AliasOperations": { "anyOf": [ { + "additionalProperties": false, "description": "Create alternative name for a collection. Collection will be available under both names for search, retrieve,", "properties": { "create_alias": { @@ -28,6 +29,7 @@ "type": "object" }, { + "additionalProperties": false, "description": "Delete alias if exists", "properties": { "delete_alias": { @@ -48,6 +50,7 @@ "type": "object" }, { + "additionalProperties": false, "description": "Change alias to a new one", "properties": { "rename_alias": { @@ -73,6 +76,29 @@ } ] }, + "CollectionConfig": { + "properties": { + "hnsw_config": { + "$ref": "#/components/schemas/HnswConfig" + }, + "optimizer_config": { + "$ref": "#/components/schemas/OptimizersConfig" + }, + "params": { + "$ref": "#/components/schemas/CollectionParams" + }, + "wal_config": { + "$ref": "#/components/schemas/WalConfig" + } + }, + "required": [ + "hnsw_config", + "optimizer_config", + "params", + "wal_config" + ], + "type": "object" + }, "CollectionDescription": { "properties": { "name": { @@ -88,7 +114,7 @@ "description": "Current statistics and configuration of the collection.", "properties": { "config": { - "$ref": "#/components/schemas/SegmentConfig" + "$ref": "#/components/schemas/CollectionConfig" }, "disk_data_size": { "description": "Disk space, used by collection", @@ -108,6 +134,9 @@ "minimum": 0, "type": "integer" }, + "status": { + "$ref": "#/components/schemas/CollectionStatus" + }, "vectors_count": { "description": "Number of vectors in collection", "format": "uint", @@ -120,10 +149,37 @@ "disk_data_size", "ram_data_size", "segments_count", + "status", "vectors_count" ], "type": "object" }, + "CollectionParams": { + "properties": { + "distance": { + "$ref": "#/components/schemas/Distance" + }, + "vector_size": { + "description": "Size of a vectors used", + "format": "uint", + "minimum": 0, + "type": "integer" + } + }, + "required": [ + "distance", + "vector_size" + ], + "type": "object" + }, + "CollectionStatus": { + "enum": [ + "green", + "yellow", + "red" + ], + "type": "string" + }, "CollectionUpdateOperations": { "anyOf": [ { @@ -254,6 +310,7 @@ "FieldIndexOperations": { "anyOf": [ { + "additionalProperties": false, "description": "Create index for payload field", "properties": { "create_index": { @@ -266,6 +323,7 @@ "type": "object" }, { + "additionalProperties": false, "description": "Delete index for the field", "properties": { "delete_index": { @@ -366,7 +424,8 @@ "minimum": 0, "type": "integer" }, - "type": "array" + "type": "array", + "uniqueItems": true } }, "required": [ @@ -374,65 +433,59 @@ ], "type": "object" }, - "Indexes": { - "anyOf": [ - { - "description": "Do not use any index, scan whole vector collection during search. Guarantee 100% precision, but may be time consuming on large collections.", - "properties": { - "options": { - "type": "object" - }, - "type": { - "enum": [ - "plain" - ], - "type": "string" - } - }, - "required": [ - "options", - "type" - ], - "type": "object" + "HnswConfig": { + "properties": { + "ef_construct": { + "description": "Number of neighbours to consider during the index building. Larger the value - more accurate the search, more time required to build index.", + "format": "uint", + "minimum": 0, + "type": "integer" }, - { - "description": "Use filterable HNSW index for approximate search. Is very fast even on a very huge collections, but require additional space to store index and additional time to build it.", - "properties": { - "options": { - "properties": { - "ef_construct": { - "description": "Number of neighbours to consider during the index building. Larger the value - more accurate the search, more time required to build index.", - "format": "uint", - "minimum": 0, - "type": "integer" - }, - "m": { - "description": "Number of edges per node in the index graph. Larger the value - more accurate the search, more space required.", - "format": "uint", - "minimum": 0, - "type": "integer" - } - }, - "required": [ - "ef_construct", - "m" - ], - "type": "object" - }, - "type": { - "enum": [ - "hnsw" - ], - "type": "string" - } - }, - "required": [ - "options", - "type" - ], - "type": "object" + "full_scan_threshold": { + "description": "Minimal amount of points for additional payload-based indexing. If payload chunk is smaller than `full_scan_threshold` additional indexing won't be used - in this case full-scan search should be preferred by query planner and additional indexing is not required.", + "format": "uint", + "minimum": 0, + "type": "integer" + }, + "m": { + "description": "Number of edges per node in the index graph. Larger the value - more accurate the search, more space required.", + "format": "uint", + "minimum": 0, + "type": "integer" } - ] + }, + "required": [ + "ef_construct", + "full_scan_threshold", + "m" + ], + "type": "object" + }, + "HnswConfigDiff": { + "properties": { + "ef_construct": { + "description": "Number of neighbours to consider during the index building. Larger the value - more accurate the search, more time required to build index.", + "format": "uint", + "minimum": 0, + "nullable": true, + "type": "integer" + }, + "full_scan_threshold": { + "description": "Minimal amount of points for additional payload-based indexing. If payload chunk is smaller than `full_scan_threshold` additional indexing won't be used - in this case full-scan search should be preferred by query planner and additional indexing is not required.", + "format": "uint", + "minimum": 0, + "nullable": true, + "type": "integer" + }, + "m": { + "description": "Number of edges per node in the index graph. Larger the value - more accurate the search, more space required.", + "format": "uint", + "minimum": 0, + "nullable": true, + "type": "integer" + } + }, + "type": "object" }, "Match": { "properties": { @@ -450,42 +503,131 @@ }, "type": "object" }, - "PayloadIndexType": { - "anyOf": [ - { - "description": "Do not index anything, just keep of what should be indexed later", - "properties": { - "type": { - "enum": [ - "plain" - ], - "type": "string" - } - }, - "required": [ - "type" - ], - "type": "object" + "OptimizersConfig": { + "properties": { + "deleted_threshold": { + "description": "The minimal fraction of deleted vectors in a segment, required to perform segment optimization", + "format": "double", + "type": "number" }, - { - "description": "Build payload index. Index is saved on disc, but index itself is in RAM", - "properties": { - "type": { - "enum": [ - "struct" - ], - "type": "string" - } - }, - "required": [ - "type" - ], - "type": "object" + "flush_interval_sec": { + "description": "Minimum interval between forced flushes.", + "format": "uint64", + "minimum": 0, + "type": "integer" + }, + "indexing_threshold": { + "description": "Maximum number of vectors allowed for plain index. Default value based on https://github.com/google-research/google-research/blob/master/scann/docs/algorithms.md", + "format": "uint", + "minimum": 0, + "type": "integer" + }, + "max_segment_number": { + "description": "If the number of segments exceeds this value, the optimizer will merge the smallest segments.", + "format": "uint", + "minimum": 0, + "type": "integer" + }, + "memmap_threshold": { + "description": "Maximum number of vectors to store in-memory per segment. Segments larger than this threshold will be stored as read-only memmaped file.", + "format": "uint", + "minimum": 0, + "type": "integer" + }, + "payload_indexing_threshold": { + "description": "Starting from this amount of vectors per-segment the engine will start building index for payload.", + "format": "uint", + "minimum": 0, + "type": "integer" + }, + "vacuum_min_vector_number": { + "description": "The minimal number of vectors in a segment, required to perform segment optimization", + "format": "uint", + "minimum": 0, + "type": "integer" } + }, + "required": [ + "deleted_threshold", + "flush_interval_sec", + "indexing_threshold", + "max_segment_number", + "memmap_threshold", + "payload_indexing_threshold", + "vacuum_min_vector_number" ], - "description": "Type of payload index" + "type": "object" + }, + "OptimizersConfigDiff": { + "properties": { + "deleted_threshold": { + "description": "The minimal fraction of deleted vectors in a segment, required to perform segment optimization", + "format": "double", + "nullable": true, + "type": "number" + }, + "flush_interval_sec": { + "description": "Minimum interval between forced flushes.", + "format": "uint64", + "minimum": 0, + "nullable": true, + "type": "integer" + }, + "indexing_threshold": { + "description": "Maximum number of vectors allowed for plain index. Default value based on https://github.com/google-research/google-research/blob/master/scann/docs/algorithms.md", + "format": "uint", + "minimum": 0, + "nullable": true, + "type": "integer" + }, + "max_segment_number": { + "description": "If the number of segments exceeds this value, the optimizer will merge the smallest segments.", + "format": "uint", + "minimum": 0, + "nullable": true, + "type": "integer" + }, + "memmap_threshold": { + "description": "Maximum number of vectors to store in-memory per segment. Segments larger than this threshold will be stored as read-only memmaped file.", + "format": "uint", + "minimum": 0, + "nullable": true, + "type": "integer" + }, + "payload_indexing_threshold": { + "description": "Starting from this amount of vectors per-segment the engine will start building index for payload.", + "format": "uint", + "minimum": 0, + "nullable": true, + "type": "integer" + }, + "vacuum_min_vector_number": { + "description": "The minimal number of vectors in a segment, required to perform segment optimization", + "format": "uint", + "minimum": 0, + "nullable": true, + "type": "integer" + } + }, + "type": "object" }, "PayloadInterface": { + "anyOf": [ + { + "$ref": "#/components/schemas/PayloadVariant_for_String" + }, + { + "$ref": "#/components/schemas/PayloadVariant_for_int64" + }, + { + "$ref": "#/components/schemas/PayloadVariant_for_double" + }, + { + "$ref": "#/components/schemas/PayloadInterfaceStrict" + } + ] + }, + "PayloadInterfaceStrict": { "anyOf": [ { "properties": { @@ -564,6 +706,7 @@ "PayloadOps": { "anyOf": [ { + "additionalProperties": false, "description": "Set payload value, overrides if it is already exists", "properties": { "set_payload": { @@ -597,6 +740,7 @@ "type": "object" }, { + "additionalProperties": false, "description": "Deletes specified payload values if they are assigned", "properties": { "delete_payload": { @@ -630,6 +774,7 @@ "type": "object" }, { + "additionalProperties": false, "description": "Drops all Payload values associated with given points.", "properties": { "clear_payload": { @@ -806,6 +951,7 @@ "PointInsertOperations": { "anyOf": [ { + "additionalProperties": false, "description": "Inset points from a batch.", "properties": { "batch": { @@ -853,6 +999,7 @@ "type": "object" }, { + "additionalProperties": false, "description": "Insert points from a list", "properties": { "points": { @@ -872,6 +1019,7 @@ "PointOperations": { "anyOf": [ { + "additionalProperties": false, "description": "Insert or update points", "properties": { "upsert_points": { @@ -884,6 +1032,7 @@ "type": "object" }, { + "additionalProperties": false, "description": "Delete point if exists", "properties": { "delete_points": { @@ -1096,32 +1245,17 @@ "type": "object" }, "SearchParams": { - "anyOf": [ - { - "description": "Params relevant to HNSW index", - "properties": { - "hnsw": { - "properties": { - "ef": { - "description": "Size of the beam in a beam-search. Larger the value - more accurate the result, more time required for search.", - "format": "uint", - "minimum": 0, - "type": "integer" - } - }, - "required": [ - "ef" - ], - "type": "object" - } - }, - "required": [ - "hnsw" - ], - "type": "object" + "description": "Additional parameters of the search", + "properties": { + "hnsw_ef": { + "description": "Params relevant to HNSW index /// Size of the beam in a beam-search. Larger the value - more accurate the result, more time required for search.", + "format": "uint", + "minimum": 0, + "nullable": true, + "type": "integer" } - ], - "description": "Additional parameters of the search" + }, + "type": "object" }, "SearchRequest": { "description": "Search request", @@ -1169,46 +1303,10 @@ ], "type": "object" }, - "SegmentConfig": { - "properties": { - "distance": { - "$ref": "#/components/schemas/Distance" - }, - "index": { - "$ref": "#/components/schemas/Indexes" - }, - "payload_index": { - "anyOf": [ - { - "$ref": "#/components/schemas/PayloadIndexType" - }, - { - "nullable": true - } - ], - "description": "Payload Indexes" - }, - "storage_type": { - "$ref": "#/components/schemas/StorageType" - }, - "vector_size": { - "description": "Size of a vectors used", - "format": "uint", - "minimum": 0, - "type": "integer" - } - }, - "required": [ - "distance", - "index", - "storage_type", - "vector_size" - ], - "type": "object" - }, "StorageOperations": { "anyOf": [ { + "additionalProperties": false, "description": "Create new collection and (optionally) specify index params", "properties": { "create_collection": { @@ -1216,23 +1314,46 @@ "distance": { "$ref": "#/components/schemas/Distance" }, - "index": { + "hnsw_config": { "anyOf": [ { - "$ref": "#/components/schemas/Indexes" + "$ref": "#/components/schemas/HnswConfigDiff" }, { "nullable": true } - ] + ], + "description": "Custom params for HNSW index. If none - values from service configuration file are used." }, "name": { "type": "string" }, + "optimizers_config": { + "anyOf": [ + { + "$ref": "#/components/schemas/OptimizersConfigDiff" + }, + { + "nullable": true + } + ], + "description": "Custom params for Optimizers. If none - values from service configuration file are used." + }, "vector_size": { "format": "uint", "minimum": 0, "type": "integer" + }, + "wal_config": { + "anyOf": [ + { + "$ref": "#/components/schemas/WalConfigDiff" + }, + { + "nullable": true + } + ], + "description": "Custom params for WAL. If none - values from service configuration file are used." } }, "required": [ @@ -1249,6 +1370,39 @@ "type": "object" }, { + "additionalProperties": false, + "description": "Update parameters of the existing collection", + "properties": { + "update_collection": { + "properties": { + "name": { + "type": "string" + }, + "optimizers_config": { + "anyOf": [ + { + "$ref": "#/components/schemas/OptimizersConfigDiff" + }, + { + "nullable": true + } + ], + "description": "Custom params for Optimizers. If none - values from service configuration file are used. This operation is blocking, it will only proceed ones all current optimizations are complete" + } + }, + "required": [ + "name" + ], + "type": "object" + } + }, + "required": [ + "update_collection" + ], + "type": "object" + }, + { + "additionalProperties": false, "description": "Delete collection with given name", "properties": { "delete_collection": { @@ -1261,6 +1415,7 @@ "type": "object" }, { + "additionalProperties": false, "description": "Perform changes of collection aliases. Alias changes are atomic, meaning that no collection modifications can happen between alias operations.", "properties": { "change_aliases": { @@ -1285,41 +1440,6 @@ } ] }, - "StorageType": { - "anyOf": [ - { - "description": "Store vectors in memory and use persistence storage only if vectors are changed", - "properties": { - "type": { - "enum": [ - "in_memory" - ], - "type": "string" - } - }, - "required": [ - "type" - ], - "type": "object" - }, - { - "description": "Use memmap to store vectors, a little slower than `InMemory`, but requires little RAM", - "properties": { - "type": { - "enum": [ - "mmap" - ], - "type": "string" - } - }, - "required": [ - "type" - ], - "type": "object" - } - ], - "description": "Type of vector storage" - }, "UpdateResult": { "properties": { "operation_id": { @@ -1344,6 +1464,46 @@ "completed" ], "type": "string" + }, + "WalConfig": { + "properties": { + "wal_capacity_mb": { + "description": "Size of a single WAL segment in MB", + "format": "uint", + "minimum": 0, + "type": "integer" + }, + "wal_segments_ahead": { + "description": "Number of WAL segments to create ahead of actually used ones", + "format": "uint", + "minimum": 0, + "type": "integer" + } + }, + "required": [ + "wal_capacity_mb", + "wal_segments_ahead" + ], + "type": "object" + }, + "WalConfigDiff": { + "properties": { + "wal_capacity_mb": { + "description": "Size of a single WAL segment in MB", + "format": "uint", + "minimum": 0, + "nullable": true, + "type": "integer" + }, + "wal_segments_ahead": { + "description": "Number of WAL segments to create ahead of actually used ones", + "format": "uint", + "minimum": 0, + "nullable": true, + "type": "integer" + } + }, + "type": "object" } } }, @@ -1355,13 +1515,13 @@ "contact": { "email": "andrey@vasnetsov.com" }, - "description": "\nAPI description for Qdrant vector search engine.\n\nThis document describes CRUD and search operations on collections of points (vectors with payload).\n\nQdrant supports any combinations of `should`, `must` and `must_not` conditions, which makes it possible to use in applications when object could not be described solely by vector. It could be location features, availability flags, and other custom properties businesses should take into account.\n## Examples\nThis examples cover the most basic use-cases - collection creation and basic vector search.\n### Create collection\nFirst - let's create a collection with dot-production metric.\n```\ncurl -X POST 'http://localhost:6333/collections' \\\n -H 'Content-Type: application/json' \\\n --data-raw '{\n \"create_collection\": {\n \"name\": \"test_collection\",\n \"vector_size\": 4,\n \"distance\": \"Dot\"\n }\n }'\n```\nExpected response:\n```\n{\n \"result\": true,\n \"status\": \"ok\",\n \"time\": 0.031095451\n}\n```\nWe can ensure that collection was created:\n```\ncurl 'http://localhost:6333/collections/test_collection'\n```\nExpected response:\n```\n{\n \"result\": {\n \"vectors_count\": 0,\n \"segments_count\": 5,\n \"disk_data_size\": 0,\n \"ram_data_size\": 0,\n \"config\": {\n \"vector_size\": 4,\n \"index\": {\n \"type\": \"plain\",\n \"options\": {}\n },\n \"distance\": \"Dot\",\n \"storage_type\": {\n \"type\": \"in_memory\"\n }\n }\n },\n \"status\": \"ok\",\n \"time\": 2.1199e-05\n}\n```\n\n### Add points\nLet's now add vectors with some payload:\n```\ncurl -L -X POST 'http://localhost:6333/collections/test_collection?wait=true' \\ -H 'Content-Type: application/json' \\ --data-raw '{\n \"upsert_points\": {\n \"points\": [\n {\"id\": 1, \"vector\": [0.05, 0.61, 0.76, 0.74], \"payload\": {\"city\": {\"type\": \"keyword\", \"value\": \"Berlin\"}}},\n {\"id\": 2, \"vector\": [0.19, 0.81, 0.75, 0.11], \"payload\": {\"city\": {\"type\": \"keyword\", \"value\": [\"Berlin\", \"London\"] }}},\n {\"id\": 3, \"vector\": [0.36, 0.55, 0.47, 0.94], \"payload\": {\"city\": {\"type\": \"keyword\", \"value\": [\"Berlin\", \"Moscow\"] }}},\n {\"id\": 4, \"vector\": [0.18, 0.01, 0.85, 0.80], \"payload\": {\"city\": {\"type\": \"keyword\", \"value\": [\"London\", \"Moscow\"]}}},\n {\"id\": 5, \"vector\": [0.24, 0.18, 0.22, 0.44], \"payload\": {\"count\": {\"type\": \"integer\", \"value\": [0]}}},\n {\"id\": 6, \"vector\": [0.35, 0.08, 0.11, 0.44]}\n ]\n }\n}'\n```\nExpected response:\n```\n{\n \"result\": {\n \"operation_id\": 0,\n \"status\": \"completed\"\n },\n \"status\": \"ok\",\n \"time\": 0.000206061\n}\n```\n### Search with filtering\nLet's start with a basic request:\n```\ncurl -L -X POST 'http://localhost:6333/collections/test_collection/points/search' \\ -H 'Content-Type: application/json' \\ --data-raw '{\n \"vector\": [0.2,0.1,0.9,0.7],\n \"top\": 3\n}'\n```\nExpected response:\n```\n{\n \"result\": [\n { \"id\": 4, \"score\": 1.362 },\n { \"id\": 1, \"score\": 1.273 },\n { \"id\": 3, \"score\": 1.208 }\n ],\n \"status\": \"ok\",\n \"time\": 0.000055785\n}\n```\nBut result is different if we add a filter:\n```\ncurl -L -X POST 'http://localhost:6333/collections/test_collection/points/search' \\ -H 'Content-Type: application/json' \\ --data-raw '{\n \"filter\": {\n \"should\": [\n {\n \"key\": \"city\",\n \"match\": {\n \"keyword\": \"London\"\n }\n }\n ]\n },\n \"vector\": [0.2, 0.1, 0.9, 0.7],\n \"top\": 3\n}'\n```\nExpected response:\n```\n{\n \"result\": [\n { \"id\": 4, \"score\": 1.362 },\n { \"id\": 2, \"score\": 0.871 }\n ],\n \"status\": \"ok\",\n \"time\": 0.000093972\n}\n```\n", + "description": "\nAPI description for Qdrant vector search engine.\n\nThis document describes CRUD and search operations on collections of points (vectors with payload).\n\nQdrant supports any combinations of `should`, `must` and `must_not` conditions, which makes it possible to use in applications when object could not be described solely by vector. It could be location features, availability flags, and other custom properties businesses should take into account.\n## Examples\nThis examples cover the most basic use-cases - collection creation and basic vector search.\n### Create collection\nFirst - let's create a collection with dot-production metric.\n```\ncurl -X POST 'http://localhost:6333/collections' \\\n -H 'Content-Type: application/json' \\\n --data-raw '{\n \"create_collection\": {\n \"name\": \"test_collection\",\n \"vector_size\": 4,\n \"distance\": \"Dot\"\n }\n }'\n```\nExpected response:\n```\n{\n \"result\": true,\n \"status\": \"ok\",\n \"time\": 0.031095451\n}\n```\nWe can ensure that collection was created:\n```\ncurl 'http://localhost:6333/collections/test_collection'\n```\nExpected response:\n```\n{\n \"result\": {\n \"status\": \"green\",\n \"vectors_count\": 0,\n \"segments_count\": 5,\n \"disk_data_size\": 0,\n \"ram_data_size\": 0,\n \"config\": {\n \"params\": {\n \"vector_size\": 4,\n \"distance\": \"Dot\"\n },\n \"hnsw_config\": {\n \"m\": 16,\n \"ef_construct\": 100,\n \"full_scan_threshold\": 10000\n },\n \"optimizer_config\": {\n \"deleted_threshold\": 0.2,\n \"vacuum_min_vector_number\": 1000,\n \"max_segment_number\": 5,\n \"memmap_threshold\": 50000,\n \"indexing_threshold\": 20000,\n \"payload_indexing_threshold\": 10000,\n \"flush_interval_sec\": 1\n },\n \"wal_config\": {\n \"wal_capacity_mb\": 32,\n \"wal_segments_ahead\": 0\n }\n }\n },\n \"status\": \"ok\",\n \"time\": 2.1199e-05\n}\n```\n\n### Add points\nLet's now add vectors with some payload:\n```\ncurl -L -X POST 'http://localhost:6333/collections/test_collection?wait=true' \\ -H 'Content-Type: application/json' \\ --data-raw '{\n \"upsert_points\": {\n \"points\": [\n {\"id\": 1, \"vector\": [0.05, 0.61, 0.76, 0.74], \"payload\": {\"city\": {\"type\": \"keyword\", \"value\": \"Berlin\"}}},\n {\"id\": 2, \"vector\": [0.19, 0.81, 0.75, 0.11], \"payload\": {\"city\": {\"type\": \"keyword\", \"value\": [\"Berlin\", \"London\"] }}},\n {\"id\": 3, \"vector\": [0.36, 0.55, 0.47, 0.94], \"payload\": {\"city\": {\"type\": \"keyword\", \"value\": [\"Berlin\", \"Moscow\"] }}},\n {\"id\": 4, \"vector\": [0.18, 0.01, 0.85, 0.80], \"payload\": {\"city\": {\"type\": \"keyword\", \"value\": [\"London\", \"Moscow\"]}}},\n {\"id\": 5, \"vector\": [0.24, 0.18, 0.22, 0.44], \"payload\": {\"count\": {\"type\": \"integer\", \"value\": [0]}}},\n {\"id\": 6, \"vector\": [0.35, 0.08, 0.11, 0.44]}\n ]\n }\n}'\n```\nExpected response:\n```\n{\n \"result\": {\n \"operation_id\": 0,\n \"status\": \"completed\"\n },\n \"status\": \"ok\",\n \"time\": 0.000206061\n}\n```\n### Search with filtering\nLet's start with a basic request:\n```\ncurl -L -X POST 'http://localhost:6333/collections/test_collection/points/search' \\ -H 'Content-Type: application/json' \\ --data-raw '{\n \"vector\": [0.2,0.1,0.9,0.7],\n \"top\": 3\n}'\n```\nExpected response:\n```\n{\n \"result\": [\n { \"id\": 4, \"score\": 1.362 },\n { \"id\": 1, \"score\": 1.273 },\n { \"id\": 3, \"score\": 1.208 }\n ],\n \"status\": \"ok\",\n \"time\": 0.000055785\n}\n```\nBut result is different if we add a filter:\n```\ncurl -L -X POST 'http://localhost:6333/collections/test_collection/points/search' \\ -H 'Content-Type: application/json' \\ --data-raw '{\n \"filter\": {\n \"should\": [\n {\n \"key\": \"city\",\n \"match\": {\n \"keyword\": \"London\"\n }\n }\n ]\n },\n \"vector\": [0.2, 0.1, 0.9, 0.7],\n \"top\": 3\n}'\n```\nExpected response:\n```\n{\n \"result\": [\n { \"id\": 4, \"score\": 1.362 },\n { \"id\": 2, \"score\": 0.871 }\n ],\n \"status\": \"ok\",\n \"time\": 0.000093972\n}\n```\n", "license": { "name": "Apache 2.0", "url": "http://www.apache.org/licenses/LICENSE-2.0.html" }, "title": "Qdrant API", - "version": "0.2.1" + "version": "0.3.0" }, "openapi": "3.0.1", "paths": { diff --git a/lib/collection/Cargo.toml b/lib/collection/Cargo.toml index c1388e7f8d..40f9a51530 100644 --- a/lib/collection/Cargo.toml +++ b/lib/collection/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "collection" -version = "0.1.0" +version = "0.3.0" authors = ["Andrey Vasnetsov "] edition = "2018" @@ -30,6 +30,7 @@ crossbeam-channel = "0.4.3" atomicwrites = "0.2.5" log = "0.4" env_logger = "0.7.1" +merge = "0.1.0" segment = {path = "../segment"} diff --git a/lib/collection/src/collection.rs b/lib/collection/src/collection.rs index 3e7f1b7eda..e140e879e4 100644 --- a/lib/collection/src/collection.rs +++ b/lib/collection/src/collection.rs @@ -1,77 +1,36 @@ -use thiserror::Error; -use crate::operations::CollectionUpdateOperations; -use segment::types::{PointIdType, ScoredPoint, SegmentConfig, VectorElementType, HasIdCondition}; -use std::result; -use crate::operations::types::{Record, CollectionInfo, UpdateResult, UpdateStatus, SearchRequest, RecommendRequest}; -use std::sync::Arc; -use crate::wal::{SerdeWal, WalError}; -use crate::segment_manager::segment_managers::{SegmentSearcher, SegmentUpdater}; -use segment::entry::entry_point::OperationError; -use tokio::task::JoinError; -use crossbeam_channel::{Sender, SendError}; -use crate::update_handler::update_handler::{UpdateHandler, UpdateSignal}; -use parking_lot::{Mutex, RwLock}; -use crate::segment_manager::holders::segment_holder::SegmentHolder; -use tokio::runtime::Runtime; -use itertools::Itertools; use std::collections::HashMap; -use segment::types::Filter; +use std::path::PathBuf; +use std::sync::Arc; + +use crossbeam_channel::{Sender}; +use itertools::Itertools; +use parking_lot::{Mutex, RwLock}; +use tokio::runtime::Runtime; + +use segment::types::{HasIdCondition, PointIdType, ScoredPoint, VectorElementType, SegmentType}; use segment::types::Condition; +use segment::types::Filter; - -#[derive(Error, Debug, Clone)] -#[error("{0}")] -pub enum CollectionError { - #[error("Wrong input: {description}")] - BadInput { description: String }, - #[error("No point with id {missed_point_id} found")] - NotFound { missed_point_id: PointIdType }, - #[error("Service internal error: {error}")] - ServiceError { error: String }, - #[error("Bad request: {description}")] - BadRequest { description: String }, -} - -impl From for CollectionError { - fn from(err: OperationError) -> Self { - match err { - OperationError::WrongVector { .. } => Self::BadInput { description: format!("{}", err) }, - OperationError::PointIdError { missed_point_id } => Self::NotFound { missed_point_id }, - OperationError::ServiceError { description } => Self::ServiceError { error: description }, - OperationError::TypeError { .. } => Self::BadInput { description: format!("{}", err) }, - } - } -} - -impl From for CollectionError { - fn from(err: JoinError) -> Self { - Self::ServiceError { error: format!("{}", err) } - } -} - -impl From for CollectionError { - fn from(err: WalError) -> Self { - Self::ServiceError { error: format!("{}", err) } - } -} - -impl From> for CollectionError { - fn from(_err: SendError) -> Self { - Self::ServiceError { error: format!("Can't reach one of the workers") } - } -} - -pub type CollectionResult = result::Result; +use crate::collection_builder::optimizers_builder::build_optimizers; +use crate::config::CollectionConfig; +use crate::operations::CollectionUpdateOperations; +use crate::operations::config_diff::{DiffConfig, OptimizersConfigDiff}; +use crate::operations::types::{CollectionError, CollectionInfo, CollectionResult, RecommendRequest, Record, SearchRequest, UpdateResult, UpdateStatus, CollectionStatus}; +use crate::segment_manager::holders::segment_holder::SegmentHolder; +use crate::segment_manager::segment_managers::{SegmentSearcher, SegmentUpdater}; +use crate::update_handler::update_handler::{UpdateHandler, UpdateSignal}; +use crate::wal::{SerdeWal}; pub struct Collection { pub segments: Arc>, - pub config: SegmentConfig, + pub config: Arc>, pub wal: Arc>>, pub searcher: Arc, - pub update_handler: Arc, + pub update_handler: Arc>, pub updater: Arc, pub runtime_handle: Arc, pub update_sender: Sender, + pub path: PathBuf } @@ -107,19 +66,24 @@ impl Collection { let mut segments_count = 0; let mut ram_size = 0; let mut disk_size = 0; + let mut status = CollectionStatus::Green; for (_idx, segment) in segments.iter() { segments_count += 1; let segment_info = segment.get().read().info(); + if segment_info.segment_type == SegmentType::Special { + status = CollectionStatus::Yellow; + } vectors_count += segment_info.num_vectors; disk_size += segment_info.disk_usage_bytes; ram_size += segment_info.ram_usage_bytes; } Ok(CollectionInfo { + status, vectors_count, segments_count, disk_data_size: disk_size, ram_data_size: ram_size, - config: self.config.clone(), + config: self.config.read().clone(), }) } @@ -230,6 +194,34 @@ impl Collection { self.search(Arc::new(search_request)) } + + /// Updates collection optimization params: + /// - Saves new params on disk + /// - Stops existing optimization loop + /// - Runs new optimizers with new params + pub fn update_optimizer_params(&self, optimizer_config_diff: OptimizersConfigDiff) -> CollectionResult<()> { + { + let mut config = self.config.write(); + config.optimizer_config = optimizer_config_diff.update(&config.optimizer_config)?; + config.save(self.path.as_path())?; + } + let config = self.config.read(); + let mut update_handler = self.update_handler.lock(); + self.stop()?; + update_handler.wait_worker_stops()?; + let new_optimizers = build_optimizers( + self.path.as_path(), + &config.params, + &config.optimizer_config, + &config.hnsw_config + ); + update_handler.optimizers = new_optimizers; + update_handler.flush_timeout_sec = config.optimizer_config.flush_interval_sec; + update_handler.run_worker(); + self.update_sender.send(UpdateSignal::Nop)?; + + Ok(()) + } } impl Drop for Collection { diff --git a/lib/collection/src/collection_builder/collection_builder.rs b/lib/collection/src/collection_builder/collection_builder.rs index ab704e41f9..e613e6c751 100644 --- a/lib/collection/src/collection_builder/collection_builder.rs +++ b/lib/collection/src/collection_builder/collection_builder.rs @@ -1,52 +1,34 @@ -use crate::collection::{Collection, CollectionResult, CollectionError}; -use crate::segment_manager::holders::segment_holder::SegmentHolder; -use segment::segment_constructor::simple_segment_constructor::build_simple_segment; -use std::path::Path; -use crate::wal::SerdeWal; -use crate::operations::CollectionUpdateOperations; -use wal::WalOptions; -use std::sync::Arc; -use tokio::runtime::Runtime; -use crate::segment_manager::simple_segment_searcher::SimpleSegmentSearcher; -use crate::segment_manager::simple_segment_updater::SimpleSegmentUpdater; -use crossbeam_channel::unbounded; -use crate::update_handler::update_handler::{UpdateHandler, Optimizer}; -use segment::types::SegmentConfig; use std::fs::create_dir_all; -use parking_lot::{RwLock, Mutex}; +use std::path::Path; +use std::sync::Arc; + +use crossbeam_channel::unbounded; +use parking_lot::{Mutex, RwLock}; +use tokio::runtime; +use tokio::runtime::Runtime; + +use segment::segment_constructor::simple_segment_constructor::build_simple_segment; +use segment::types::HnswConfig; + +use crate::collection::Collection; use crate::collection_builder::optimizers_builder::build_optimizers; use crate::collection_builder::optimizers_builder::OptimizersConfig; -use atomicwrites::OverwriteBehavior::AllowOverwrite; -use atomicwrites::AtomicFile; -use std::io::Write; -use tokio::runtime; - -const DEFAULT_SEGMENT_NUMBER: usize = 5; - -pub const COLLECTION_CONFIG_FILE: &str = "config.json"; - - -fn save_config(path: &Path, config: &SegmentConfig) -> CollectionResult<()> { - let config_path = path.join(COLLECTION_CONFIG_FILE); - let af = AtomicFile::new(&config_path, AllowOverwrite); - let state_bytes = serde_json::to_vec(config).unwrap(); - af.write(|f| { - f.write_all(&state_bytes) - }).or_else(move |err| - Err(CollectionError::ServiceError { - error: format!("Can't write {:?}, error: {}", config_path, err) - }) - )?; - Ok(()) -} +use crate::config::{CollectionConfig, CollectionParams, WalConfig}; +use crate::operations::CollectionUpdateOperations; +use crate::operations::types::{CollectionError, CollectionResult}; +use crate::segment_manager::holders::segment_holder::SegmentHolder; +use crate::segment_manager::simple_segment_searcher::SimpleSegmentSearcher; +use crate::segment_manager::simple_segment_updater::SimpleSegmentUpdater; +use crate::update_handler::update_handler::{Optimizer, UpdateHandler}; +use crate::wal::SerdeWal; pub fn construct_collection( segment_holder: SegmentHolder, - config: &SegmentConfig, + config: CollectionConfig, wal: SerdeWal, search_runtime: Arc, // from service optimizers: Arc>>, - flush_interval_sec: u64, + collection_path: &Path, ) -> Collection { let segment_holder = Arc::new(RwLock::new(segment_holder)); @@ -62,27 +44,28 @@ pub fn construct_collection( ); let updater = SimpleSegmentUpdater::new(segment_holder.clone()); - + // ToDo: Move tx-rx into updater, so Collection should not know about it. let (tx, rx) = unbounded(); - let update_handler = Arc::new(UpdateHandler::new( + let update_handler = Arc::new(Mutex::new(UpdateHandler::new( optimizers, rx, optimize_runtime.clone(), segment_holder.clone(), locked_wal.clone(), - flush_interval_sec, - )); + config.optimizer_config.flush_interval_sec, + ))); let collection = Collection { segments: segment_holder.clone(), - config: config.clone(), + config: Arc::new(RwLock::new(config)), wal: locked_wal, searcher: Arc::new(searcher), update_handler, updater: Arc::new(updater), runtime_handle: optimize_runtime, update_sender: tx, + path: collection_path.to_owned() }; return collection; @@ -92,10 +75,11 @@ pub fn construct_collection( /// Creates new empty collection with given configuration pub fn build_collection( collection_path: &Path, - wal_options: &WalOptions, // from config - segment_config: &SegmentConfig, // from user + wal_config: &WalConfig, // from config + collection_params: &CollectionParams, // from user search_runtime: Arc, // from service optimizers_config: &OptimizersConfig, + hnsw_config: &HnswConfig, ) -> CollectionResult { let wal_path = collection_path .join("wal"); @@ -114,31 +98,39 @@ pub fn build_collection( let mut segment_holder = SegmentHolder::new(); - for _sid in 0..DEFAULT_SEGMENT_NUMBER { + for _sid in 0..optimizers_config.max_segment_number { let segment = build_simple_segment( segments_path.as_path(), - segment_config.vector_size, - segment_config.distance.clone())?; + collection_params.vector_size, + collection_params.distance)?; segment_holder.add(segment); } - let wal: SerdeWal = SerdeWal::new(wal_path.to_str().unwrap(), wal_options)?; + let wal: SerdeWal = SerdeWal::new(wal_path.to_str().unwrap(), &wal_config.into())?; - save_config(collection_path, &segment_config)?; + let collection_config = CollectionConfig { + params: collection_params.clone(), + hnsw_config: hnsw_config.clone(), + optimizer_config: optimizers_config.clone(), + wal_config: wal_config.clone() + }; + + collection_config.save(collection_path)?; let optimizers = build_optimizers( collection_path, - &segment_config, + &collection_params, &optimizers_config, + &collection_config.hnsw_config ); let collection = construct_collection( segment_holder, - segment_config, + collection_config, wal, search_runtime, optimizers, - optimizers_config.flush_interval_sec, + collection_path, ); Ok(collection) diff --git a/lib/collection/src/collection_builder/collection_loader.rs b/lib/collection/src/collection_builder/collection_loader.rs index 64e6d94e1b..d42c435c99 100644 --- a/lib/collection/src/collection_builder/collection_loader.rs +++ b/lib/collection/src/collection_builder/collection_loader.rs @@ -1,41 +1,36 @@ -use crate::collection::{Collection, CollectionError}; +use std::fs::read_dir; use std::path::Path; -use tokio::runtime::Runtime; -use crate::segment_manager::holders::segment_holder::SegmentHolder; -use crate::wal::SerdeWal; -use crate::operations::CollectionUpdateOperations; -use wal::WalOptions; -use std::fs::{read_dir, File}; -use segment::segment_constructor::segment_constructor::load_segment; -use crate::collection_builder::collection_builder::{construct_collection, COLLECTION_CONFIG_FILE}; -use indicatif::ProgressBar; -use crate::collection_builder::optimizers_builder::OptimizersConfig; -use crate::collection_builder::optimizers_builder::build_optimizers; -use segment::types::SegmentConfig; -use std::io::Read; use std::sync::Arc; +use indicatif::ProgressBar; +use tokio::runtime::Runtime; -fn load_config(path: &Path) -> SegmentConfig { - let config_path = path.join(COLLECTION_CONFIG_FILE); - let mut contents = String::new(); - let mut file = File::open(config_path).unwrap(); - file.read_to_string(&mut contents).unwrap(); - serde_json::from_str(&contents).unwrap() -} +use segment::segment_constructor::segment_constructor::load_segment; +use crate::collection::Collection; +use crate::collection_builder::collection_builder::construct_collection; +use crate::collection_builder::optimizers_builder::build_optimizers; +use crate::config::CollectionConfig; +use crate::operations::CollectionUpdateOperations; +use crate::operations::types::CollectionError; +use crate::segment_manager::holders::segment_holder::SegmentHolder; +use crate::wal::SerdeWal; pub fn load_collection( collection_path: &Path, - wal_options: &WalOptions, // from config search_runtime: Arc, // from service - optimizers_config: &OptimizersConfig, ) -> Collection { let wal_path = collection_path.join("wal"); let segments_path = collection_path.join("segments"); let mut segment_holder = SegmentHolder::new(); - let wal: SerdeWal = SerdeWal::new(wal_path.to_str().unwrap(), wal_options).expect("Can't read WAL"); + let collection_config = CollectionConfig::load(&collection_path) + .expect(&format!("Can't read collection config at {}", collection_path.to_str().unwrap())); + + let wal: SerdeWal = SerdeWal::new( + wal_path.to_str().unwrap(), + &(&collection_config.wal_config).into() + ).expect("Can't read WAL"); let segment_dirs = read_dir(segments_path.as_path()) .expect(&format!("Can't read segments directory {}", segments_path.to_str().unwrap())); @@ -44,28 +39,26 @@ pub fn load_collection( let segments_path = entry.unwrap().path(); let segment = match load_segment(segments_path.as_path()) { Ok(x) => x, - Err(err) => panic!( - format!("Can't load segments from {}, error: {}", segments_path.to_str().unwrap(), err) - ), + Err(err) => panic!("Can't load segments from {}, error: {}", segments_path.to_str().unwrap(), err), }; segment_holder.add(segment); }; - let segment_config = load_config(&collection_path); let optimizers = build_optimizers( collection_path, - &segment_config, - &optimizers_config, + &collection_config.params, + &collection_config.optimizer_config, + &collection_config.hnsw_config, ); let collection = construct_collection( segment_holder, - &segment_config, + collection_config, wal, search_runtime, optimizers, - optimizers_config.flush_interval_sec, + collection_path ); { @@ -78,7 +71,7 @@ pub fn load_collection( match collection.updater.update(op_num, update) { Ok(_) => {} Err(err) => match err { - CollectionError::ServiceError { error } => panic!(format!("Can't apply WAL operation: {}", error)), + CollectionError::ServiceError { error } => panic!("Can't apply WAL operation: {}", error), _ => {} } } diff --git a/lib/collection/src/collection_builder/optimizers_builder.rs b/lib/collection/src/collection_builder/optimizers_builder.rs index cf30b8501e..3f41419e1d 100644 --- a/lib/collection/src/collection_builder/optimizers_builder.rs +++ b/lib/collection/src/collection_builder/optimizers_builder.rs @@ -1,31 +1,42 @@ use crate::update_handler::update_handler::Optimizer; use std::sync::Arc; use crate::segment_manager::optimizers::vacuum_optimizer::VacuumOptimizer; -use segment::types::SegmentConfig; +use segment::types::{HnswConfig}; use crate::segment_manager::optimizers::merge_optimizer::MergeOptimizer; use std::path::Path; use serde::{Deserialize, Serialize}; use schemars::{JsonSchema}; use crate::segment_manager::optimizers::indexing_optimizer::IndexingOptimizer; use crate::segment_manager::optimizers::segment_optimizer::OptimizerThresholds; +use crate::config::CollectionParams; #[derive(Debug, Deserialize, Serialize, JsonSchema, Clone)] pub struct OptimizersConfig { + /// The minimal fraction of deleted vectors in a segment, required to perform segment optimization pub deleted_threshold: f64, + /// The minimal number of vectors in a segment, required to perform segment optimization pub vacuum_min_vector_number: usize, + /// If the number of segments exceeds this value, the optimizer will merge the smallest segments. pub max_segment_number: usize, + /// Maximum number of vectors to store in-memory per segment. + /// Segments larger than this threshold will be stored as read-only memmaped file. pub memmap_threshold: usize, + /// Maximum number of vectors allowed for plain index. + /// Default value based on https://github.com/google-research/google-research/blob/master/scann/docs/algorithms.md pub indexing_threshold: usize, + /// Starting from this amount of vectors per-segment the engine will start building index for payload. pub payload_indexing_threshold: usize, + /// Minimum interval between forced flushes. pub flush_interval_sec: u64, } pub fn build_optimizers( collection_path: &Path, - segment_config: &SegmentConfig, + collection_params: &CollectionParams, optimizers_config: &OptimizersConfig, + hnsw_config: &HnswConfig, ) -> Arc>> { let segments_path = collection_path.join("segments"); let temp_segments_path = collection_path.join("temp_segments"); @@ -42,7 +53,8 @@ pub fn build_optimizers( threshold_config.clone(), segments_path.clone(), temp_segments_path.clone(), - segment_config.clone(), + collection_params.clone(), + hnsw_config.clone(), ) ), Box::new( @@ -51,7 +63,8 @@ pub fn build_optimizers( threshold_config.clone(), segments_path.clone(), temp_segments_path.clone(), - segment_config.clone(), + collection_params.clone(), + hnsw_config.clone(), ) ), Box::new(VacuumOptimizer::new( @@ -60,7 +73,8 @@ pub fn build_optimizers( threshold_config.clone(), segments_path.clone(), temp_segments_path.clone(), - segment_config.clone(), + collection_params.clone(), + hnsw_config.clone(), )) ]) } diff --git a/lib/collection/src/config.rs b/lib/collection/src/config.rs new file mode 100644 index 0000000000..e53e451df9 --- /dev/null +++ b/lib/collection/src/config.rs @@ -0,0 +1,80 @@ +use std::fs::File; +use std::io::{Read, Write}; +use std::path::Path; + +use atomicwrites::AtomicFile; +use atomicwrites::OverwriteBehavior::AllowOverwrite; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use wal::WalOptions; + +use segment::types::{Distance, HnswConfig}; + +use crate::collection_builder::optimizers_builder::OptimizersConfig; +use crate::operations::types::{CollectionError, CollectionResult}; + +pub const COLLECTION_CONFIG_FILE: &str = "config.json"; + +#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone)] +pub struct WalConfig { + /// Size of a single WAL segment in MB + pub wal_capacity_mb: usize, + /// Number of WAL segments to create ahead of actually used ones + pub wal_segments_ahead: usize, +} + +impl From<&WalConfig> for WalOptions { + fn from(config: &WalConfig) -> Self { + WalOptions { + segment_capacity: config.wal_capacity_mb * 1024 * 1024, + segment_queue_len: config.wal_segments_ahead + } + } +} + +impl Default for WalConfig { + fn default() -> Self { WalConfig { wal_capacity_mb: 32, wal_segments_ahead: 0 } } +} + + +#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone)] +#[serde(rename_all = "snake_case")] +pub struct CollectionParams { + /// Size of a vectors used + pub vector_size: usize, + /// Type of distance function used for measuring distance between vectors + pub distance: Distance +} + +#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone)] +pub struct CollectionConfig { + pub params: CollectionParams, + pub hnsw_config: HnswConfig, + pub optimizer_config: OptimizersConfig, + pub wal_config: WalConfig, +} + + +impl CollectionConfig { + pub fn save(&self, path: &Path) -> CollectionResult<()> { + let config_path = path.join(COLLECTION_CONFIG_FILE); + let af = AtomicFile::new(&config_path, AllowOverwrite); + let state_bytes = serde_json::to_vec(self).unwrap(); + af.write(|f| { + f.write_all(&state_bytes) + }).or_else(move |err| + Err(CollectionError::ServiceError { + error: format!("Can't write {:?}, error: {}", config_path, err) + }) + )?; + Ok(()) + } + + pub fn load(path: &Path) -> CollectionResult { + let config_path = path.join(COLLECTION_CONFIG_FILE); + let mut contents = String::new(); + let mut file = File::open(config_path)?; + file.read_to_string(&mut contents)?; + Ok(serde_json::from_str(&contents)?) + } +} diff --git a/lib/collection/src/lib.rs b/lib/collection/src/lib.rs index 8f85518580..baf01b0fff 100644 --- a/lib/collection/src/lib.rs +++ b/lib/collection/src/lib.rs @@ -1,4 +1,5 @@ pub mod collection_builder; +pub mod config; mod update_handler; pub mod operations; pub mod collection; diff --git a/lib/collection/src/operations/config_diff.rs b/lib/collection/src/operations/config_diff.rs new file mode 100644 index 0000000000..9b0c32dbc1 --- /dev/null +++ b/lib/collection/src/operations/config_diff.rs @@ -0,0 +1,118 @@ +use serde::{Deserialize, Serialize}; +use merge::Merge; +use serde::de::DeserializeOwned; +use schemars::{JsonSchema}; +use crate::operations::types::CollectionResult; +use segment::types::HnswConfig; +use crate::config::WalConfig; +use crate::collection_builder::optimizers_builder::OptimizersConfig; + +// Structures for partial update of collection params +// ToDo: Make auto-generated somehow... + +pub trait DiffConfig { + fn update(self, config: &T) -> CollectionResult + where Self: Sized, + Self: Serialize, + Self: DeserializeOwned, + Self: Merge { update_config(config, self) } +} + +#[derive(Debug, Deserialize, Serialize, JsonSchema, Copy, Clone, PartialEq, Eq, Merge)] +#[serde(rename_all = "snake_case")] +pub struct HnswConfigDiff { + /// Number of edges per node in the index graph. Larger the value - more accurate the search, more space required. + pub m: Option, + /// Number of neighbours to consider during the index building. Larger the value - more accurate the search, more time required to build index. + pub ef_construct: Option, + /// Minimal amount of points for additional payload-based indexing. + /// If payload chunk is smaller than `full_scan_threshold` additional indexing won't be used - + /// in this case full-scan search should be preferred by query planner and additional indexing is not required. + pub full_scan_threshold: Option, +} + +#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone, Merge)] +pub struct WalConfigDiff { + /// Size of a single WAL segment in MB + pub wal_capacity_mb: Option, + /// Number of WAL segments to create ahead of actually used ones + pub wal_segments_ahead: Option, +} + +#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone, Merge)] +pub struct OptimizersConfigDiff { + /// The minimal fraction of deleted vectors in a segment, required to perform segment optimization + pub deleted_threshold: Option, + /// The minimal number of vectors in a segment, required to perform segment optimization + pub vacuum_min_vector_number: Option, + /// If the number of segments exceeds this value, the optimizer will merge the smallest segments. + pub max_segment_number: Option, + /// Maximum number of vectors to store in-memory per segment. + /// Segments larger than this threshold will be stored as read-only memmaped file. + pub memmap_threshold: Option, + /// Maximum number of vectors allowed for plain index. + /// Default value based on https://github.com/google-research/google-research/blob/master/scann/docs/algorithms.md + pub indexing_threshold: Option, + /// Starting from this amount of vectors per-segment the engine will start building index for payload. + pub payload_indexing_threshold: Option, + /// Minimum interval between forced flushes. + pub flush_interval_sec: Option, +} + +impl DiffConfig for HnswConfigDiff {} + +impl DiffConfig for OptimizersConfigDiff {} + +impl DiffConfig for WalConfigDiff {} + +/// Hacky way to update configuration structures with diff-updates. +/// Intended to only be used in non critical for speed places. +/// ToDo: Replace with proc macro +pub fn update_config(config: &T, mut update: Y) -> CollectionResult { + let serialized = serde_json::to_vec(config)?; + let config_as_diff: Y = serde_json::from_slice(serialized.as_slice())?; + update.merge(config_as_diff); + let serialized = serde_json::to_vec(&update)?; + let res = serde_json::from_slice(serialized.as_slice())?; + Ok(res) +} + + +#[cfg(test)] +mod tests { + use super::*; + use segment::types::HnswConfig; + use crate::collection_builder::optimizers_builder::OptimizersConfig; + + #[test] + fn test_hnsw_update() { + let base_config = HnswConfig::default(); + let update: HnswConfigDiff = serde_json::from_str(&r#"{ "m": 32 }"#).unwrap(); + let new_config = update.update(&base_config).unwrap(); + assert_eq!(new_config.m, 32) + } + + #[test] + fn test_optimizer_update() { + let base_config = OptimizersConfig { + deleted_threshold: 0.9, + vacuum_min_vector_number: 1000, + max_segment_number: 10, + memmap_threshold: 100_000, + indexing_threshold: 50_000, + payload_indexing_threshold: 20_000, + flush_interval_sec: 30, + }; + let update: OptimizersConfigDiff = serde_json::from_str(&r#"{ "indexing_threshold": 10000 }"#).unwrap(); + let new_config = update.update(&base_config).unwrap(); + assert_eq!(new_config.indexing_threshold, 10000) + } + + #[test] + fn test_wal_config() { + let base_config = WalConfig::default(); + let update: WalConfigDiff = serde_json::from_str(&r#"{ "wal_segments_ahead": 2 }"#).unwrap(); + let new_config = update.update(&base_config).unwrap(); + assert_eq!(new_config.wal_segments_ahead, 2) + } +} diff --git a/lib/collection/src/operations/mod.rs b/lib/collection/src/operations/mod.rs index 5ab7223e0b..50818f5388 100644 --- a/lib/collection/src/operations/mod.rs +++ b/lib/collection/src/operations/mod.rs @@ -1,6 +1,7 @@ pub mod types; pub mod point_ops; pub mod payload_ops; +pub mod config_diff; use serde::{Deserialize, Serialize}; use schemars::{JsonSchema}; diff --git a/lib/collection/src/operations/types.rs b/lib/collection/src/operations/types.rs index 5d4770ed1d..6321d8e37f 100644 --- a/lib/collection/src/operations/types.rs +++ b/lib/collection/src/operations/types.rs @@ -1,11 +1,32 @@ -use segment::types::{VectorElementType, PointIdType, TheMap, PayloadKeyType, PayloadType, SeqNumberType, Filter, SearchParams, SegmentConfig}; +use crossbeam_channel::SendError; +use futures::io; +use schemars::JsonSchema; use serde; use serde::{Deserialize, Serialize}; -use schemars::{JsonSchema}; +use thiserror::Error; +use serde_json::Error as JsonError; +use tokio::task::JoinError; +use std::result; + +use segment::entry::entry_point::OperationError; +use segment::types::{Filter, PayloadKeyType, PayloadType, PointIdType, SearchParams, SeqNumberType, TheMap, VectorElementType}; + +use crate::config::CollectionConfig; +use crate::wal::WalError; /// Type of vector in API pub type VectorType = Vec; +#[derive(Debug, Deserialize, Serialize, JsonSchema, PartialEq)] +#[serde(rename_all = "snake_case")] +pub enum CollectionStatus { + /// Collection if completely ready for requests + Green, + /// Collection is available, but some segments might be under optimization + Yellow, + /// Something is not OK + Red +} #[derive(Debug, Deserialize, Serialize, JsonSchema)] #[serde(rename_all = "snake_case")] @@ -22,6 +43,8 @@ pub struct Record { #[derive(Debug, Deserialize, Serialize, JsonSchema)] /// Current statistics and configuration of the collection. pub struct CollectionInfo { + /// Status of the collection + pub status: CollectionStatus, /// Number of vectors in collection pub vectors_count: usize, /// Number of segments in collection @@ -31,7 +54,7 @@ pub struct CollectionInfo { /// RAM used by collection pub ram_data_size: usize, /// Collection settings - pub config: SegmentConfig, + pub config: CollectionConfig, } @@ -86,3 +109,60 @@ pub struct RecommendRequest { } +#[derive(Error, Debug, Clone)] +#[error("{0}")] +pub enum CollectionError { + #[error("Wrong input: {description}")] + BadInput { description: String }, + #[error("No point with id {missed_point_id} found")] + NotFound { missed_point_id: PointIdType }, + #[error("Service internal error: {error}")] + ServiceError { error: String }, + #[error("Bad request: {description}")] + BadRequest { description: String }, +} + +impl From for CollectionError { + fn from(err: OperationError) -> Self { + match err { + OperationError::WrongVector { .. } => Self::BadInput { description: format!("{}", err) }, + OperationError::PointIdError { missed_point_id } => Self::NotFound { missed_point_id }, + OperationError::ServiceError { description } => Self::ServiceError { error: description }, + OperationError::TypeError { .. } => Self::BadInput { description: format!("{}", err) }, + } + } +} + +impl From for CollectionError { + fn from(err: JoinError) -> Self { + Self::ServiceError { error: format!("{}", err) } + } +} + +impl From for CollectionError { + fn from(err: WalError) -> Self { + Self::ServiceError { error: format!("{}", err) } + } +} + +impl From> for CollectionError { + fn from(_err: SendError) -> Self { + Self::ServiceError { error: format!("Can't reach one of the workers") } + } +} + +impl From for CollectionError { + fn from(err: JsonError) -> Self { + CollectionError::ServiceError { error: format!("Json error: {}", err) } + } +} + +impl From for CollectionError { + fn from(err: io::Error) -> Self { + CollectionError::ServiceError { error: format!("File IO error: {}", err) } + } +} + +pub type CollectionResult = result::Result; + + diff --git a/lib/collection/src/segment_manager/holders/proxy_segment.rs b/lib/collection/src/segment_manager/holders/proxy_segment.rs index a7f87fdda4..d5a60842d0 100644 --- a/lib/collection/src/segment_manager/holders/proxy_segment.rs +++ b/lib/collection/src/segment_manager/holders/proxy_segment.rs @@ -328,7 +328,7 @@ mod tests { let mut seen_points: HashSet = Default::default(); for res in search_result { if seen_points.contains(&res.id) { - assert!(false, format!("point {} appears multiple times", res.id)); + assert!(false, "point {} appears multiple times", res.id); } seen_points.insert(res.id); } diff --git a/lib/collection/src/segment_manager/optimizers/indexing_optimizer.rs b/lib/collection/src/segment_manager/optimizers/indexing_optimizer.rs index 5d6c72f676..43c66a6ac8 100644 --- a/lib/collection/src/segment_manager/optimizers/indexing_optimizer.rs +++ b/lib/collection/src/segment_manager/optimizers/indexing_optimizer.rs @@ -1,15 +1,17 @@ use std::path::{PathBuf, Path}; -use segment::types::{SegmentConfig, SegmentType}; +use segment::types::{SegmentType, HnswConfig}; use crate::segment_manager::holders::segment_holder::{LockedSegmentHolder, SegmentId, LockedSegment}; use std::cmp::min; use crate::segment_manager::optimizers::segment_optimizer::{SegmentOptimizer, OptimizerThresholds}; +use crate::config::CollectionParams; pub struct IndexingOptimizer { thresholds_config: OptimizerThresholds, segments_path: PathBuf, collection_temp_dir: PathBuf, - config: SegmentConfig, + collection_params: CollectionParams, + hnsw_config: HnswConfig, } impl IndexingOptimizer { @@ -17,13 +19,15 @@ impl IndexingOptimizer { thresholds_config: OptimizerThresholds, segments_path: PathBuf, collection_temp_dir: PathBuf, - config: SegmentConfig, + collection_params: CollectionParams, + hnsw_config: HnswConfig, ) -> Self { IndexingOptimizer { thresholds_config, segments_path, collection_temp_dir, - config, + collection_params, + hnsw_config, } } @@ -61,8 +65,12 @@ impl SegmentOptimizer for IndexingOptimizer { self.collection_temp_dir.as_path() } - fn base_segment_config(&self) -> SegmentConfig { - self.config.clone() + fn collection_params(&self) -> CollectionParams { + self.collection_params.clone() + } + + fn hnsw_config(&self) -> HnswConfig { + self.hnsw_config.clone() } fn threshold_config(&self) -> &OptimizerThresholds { @@ -128,13 +136,11 @@ mod tests { }, segments_dir.path().to_owned(), segments_temp_dir.path().to_owned(), - SegmentConfig { + CollectionParams { vector_size: segment_config.vector_size, - index: Default::default(), - payload_index: Some(Default::default()), distance: segment_config.distance, - storage_type: StorageType::default(), }, + Default::default() ); let locked_holder = Arc::new(RwLock::new(holder)); @@ -156,7 +162,10 @@ mod tests { // ------ Plain -> Mmap & Indexed payload let suggested_to_optimize = index_optimizer.check_condition(locked_holder.clone()); assert!(suggested_to_optimize.contains(&large_segment_id)); + eprintln!("suggested_to_optimize = {:#?}", suggested_to_optimize); index_optimizer.optimize(locked_holder.clone(), suggested_to_optimize).unwrap(); + eprintln!("Done"); + // ------ Plain -> Indexed payload let suggested_to_optimize = index_optimizer.check_condition(locked_holder.clone()); diff --git a/lib/collection/src/segment_manager/optimizers/merge_optimizer.rs b/lib/collection/src/segment_manager/optimizers/merge_optimizer.rs index 858d5c5fdd..d10788b67a 100644 --- a/lib/collection/src/segment_manager/optimizers/merge_optimizer.rs +++ b/lib/collection/src/segment_manager/optimizers/merge_optimizer.rs @@ -1,8 +1,9 @@ use crate::segment_manager::optimizers::segment_optimizer::{SegmentOptimizer, OptimizerThresholds}; use crate::segment_manager::holders::segment_holder::{LockedSegmentHolder, SegmentId}; -use segment::types::{SegmentType, SegmentConfig}; +use segment::types::{SegmentType, HnswConfig}; use itertools::Itertools; use std::path::{PathBuf, Path}; +use crate::config::CollectionParams; /// Optimizer that tries to reduce number of segments until it fits configured value @@ -11,7 +12,8 @@ pub struct MergeOptimizer { thresholds_config: OptimizerThresholds, segments_path: PathBuf, collection_temp_dir: PathBuf, - config: SegmentConfig, + collection_params: CollectionParams, + hnsw_config: HnswConfig, } impl MergeOptimizer { @@ -20,13 +22,16 @@ impl MergeOptimizer { thresholds_config: OptimizerThresholds, segments_path: PathBuf, collection_temp_dir: PathBuf, - config: SegmentConfig) -> Self { + collection_params: CollectionParams, + hnsw_config: HnswConfig, + ) -> Self { return MergeOptimizer { max_segments, thresholds_config, segments_path, collection_temp_dir, - config, + collection_params, + hnsw_config, }; } } @@ -41,15 +46,18 @@ impl SegmentOptimizer for MergeOptimizer { self.collection_temp_dir.as_path() } - fn base_segment_config(&self) -> SegmentConfig { - self.config.clone() + fn collection_params(&self) -> CollectionParams { + self.collection_params.clone() + } + + fn hnsw_config(&self) -> HnswConfig { + self.hnsw_config.clone() } fn threshold_config(&self) -> &OptimizerThresholds { &self.thresholds_config } - fn check_condition(&self, segments: LockedSegmentHolder) -> Vec { let read_segments = segments.read(); @@ -82,7 +90,7 @@ mod tests { use super::*; use crate::segment_manager::fixtures::{random_segment}; use crate::segment_manager::holders::segment_holder::{SegmentHolder, LockedSegment}; - use segment::types::{Distance, Indexes}; + use segment::types::{Distance}; use std::sync::{Arc}; use tempdir::TempDir; use parking_lot::RwLock; @@ -119,13 +127,12 @@ mod tests { }, dir.path().to_owned(), temp_dir.path().to_owned(), - SegmentConfig { + CollectionParams { vector_size: 4, - index: Indexes::Plain {}, - payload_index: Some(Default::default()), distance: Distance::Dot, - storage_type: Default::default(), - }); + }, + Default::default() + ); let locked_holder = Arc::new(RwLock::new(holder)); diff --git a/lib/collection/src/segment_manager/optimizers/segment_optimizer.rs b/lib/collection/src/segment_manager/optimizers/segment_optimizer.rs index 56f58e2063..5ef52246b7 100644 --- a/lib/collection/src/segment_manager/optimizers/segment_optimizer.rs +++ b/lib/collection/src/segment_manager/optimizers/segment_optimizer.rs @@ -1,5 +1,5 @@ -use segment::types::{PointIdType, PayloadKeyType, SegmentConfig, Indexes, StorageType, PayloadIndexType}; -use crate::collection::CollectionResult; +use segment::types::{PointIdType, PayloadKeyType, SegmentConfig, Indexes, StorageType, PayloadIndexType, HnswConfig}; +use crate::operations::types::CollectionResult; use crate::segment_manager::holders::segment_holder::{SegmentId, LockedSegment, LockedSegmentHolder}; use std::sync::Arc; use segment::segment::Segment; @@ -12,6 +12,7 @@ use segment::segment_constructor::segment_builder::SegmentBuilder; use std::convert::TryInto; use std::path::Path; use segment::segment_constructor::simple_segment_constructor::build_simple_segment; +use crate::config::CollectionParams; #[derive(Debug, Clone)] @@ -29,7 +30,10 @@ pub trait SegmentOptimizer { fn temp_path(&self) -> &Path; /// Get basic segment config - fn base_segment_config(&self) -> SegmentConfig; + fn collection_params(&self) -> CollectionParams; + + /// Get HNSW config + fn hnsw_config(&self) -> HnswConfig; /// Get thresholds configuration for the current optimizer fn threshold_config(&self) -> &OptimizerThresholds; @@ -39,7 +43,14 @@ pub trait SegmentOptimizer { /// Build temp segment fn temp_segment(&self) -> CollectionResult { - let config = self.base_segment_config(); + let collection_params = self.collection_params(); + let config = SegmentConfig { + vector_size: collection_params.vector_size, + distance: collection_params.distance, + index: Indexes::Plain {}, + payload_index: Some(PayloadIndexType::Plain), + storage_type: StorageType::InMemory + }; Ok(LockedSegment::new(build_simple_segment( self.collection_path(), config.vector_size, @@ -55,31 +66,23 @@ pub trait SegmentOptimizer { let have_indexed_fields = optimizing_segments.iter() .any(|s| !s.get().read().get_indexed_fields().is_empty()); - let mut optimized_config = self.base_segment_config(); - let thresholds = self.threshold_config(); + let collection_params = self.collection_params(); - if total_vectors < thresholds.memmap_threshold { - optimized_config.storage_type = StorageType::InMemory; - } else { - optimized_config.storage_type = StorageType::Mmap; - } - - if total_vectors < thresholds.indexing_threshold { - optimized_config.index = Indexes::Plain {}; - } else { - optimized_config.index = match optimized_config.index { - Indexes::Plain { } => Indexes::default_hnsw(), - _ => optimized_config.index - } - } + let is_indexed = total_vectors >= thresholds.indexing_threshold; // Create structure index only if there is something to index - if total_vectors < thresholds.payload_indexing_threshold || !have_indexed_fields { - optimized_config.payload_index = Some(PayloadIndexType::Plain) - } else { - optimized_config.payload_index = Some(PayloadIndexType::Struct); - } + let is_payload_indexed = total_vectors >= thresholds.payload_indexing_threshold && have_indexed_fields; + + let is_on_disk = total_vectors >= thresholds.memmap_threshold; + + let optimized_config = SegmentConfig { + vector_size: collection_params.vector_size, + distance: collection_params.distance, + index: if is_indexed { Indexes::Hnsw(self.hnsw_config()) } else { Indexes::Plain {} }, + payload_index: Some(if is_payload_indexed { PayloadIndexType::Struct } else { PayloadIndexType::Plain }), + storage_type: if is_on_disk { StorageType::Mmap } else { StorageType::InMemory } + }; Ok(SegmentBuilder::new( self.collection_path(), diff --git a/lib/collection/src/segment_manager/optimizers/vacuum_optimizer.rs b/lib/collection/src/segment_manager/optimizers/vacuum_optimizer.rs index ad02a122e6..0615171c23 100644 --- a/lib/collection/src/segment_manager/optimizers/vacuum_optimizer.rs +++ b/lib/collection/src/segment_manager/optimizers/vacuum_optimizer.rs @@ -1,8 +1,9 @@ use crate::segment_manager::holders::segment_holder::{SegmentId, LockedSegment, LockedSegmentHolder}; -use segment::types::{SegmentType, SegmentConfig}; +use segment::types::{SegmentType, HnswConfig}; use ordered_float::OrderedFloat; use crate::segment_manager::optimizers::segment_optimizer::{SegmentOptimizer, OptimizerThresholds}; use std::path::{PathBuf, Path}; +use crate::config::CollectionParams; pub struct VacuumOptimizer { @@ -11,7 +12,8 @@ pub struct VacuumOptimizer { thresholds_config: OptimizerThresholds, segments_path: PathBuf, collection_temp_dir: PathBuf, - config: SegmentConfig, + collection_params: CollectionParams, + hnsw_config: HnswConfig, } @@ -21,14 +23,17 @@ impl VacuumOptimizer { thresholds_config: OptimizerThresholds, segments_path: PathBuf, collection_temp_dir: PathBuf, - config: SegmentConfig) -> Self { + collection_params: CollectionParams, + hnsw_config: HnswConfig, + ) -> Self { VacuumOptimizer { deleted_threshold, min_vectors_number, thresholds_config, segments_path, collection_temp_dir, - config, + collection_params, + hnsw_config } } @@ -64,8 +69,12 @@ impl SegmentOptimizer for VacuumOptimizer { self.collection_temp_dir.as_path() } - fn base_segment_config(&self) -> SegmentConfig { - self.config.clone() + fn collection_params(&self) -> CollectionParams { + self.collection_params.clone() + } + + fn hnsw_config(&self) -> HnswConfig { + self.hnsw_config.clone() } fn threshold_config(&self) -> &OptimizerThresholds { @@ -89,7 +98,7 @@ mod tests { use itertools::Itertools; use rand::Rng; use std::sync::Arc; - use segment::types::{Distance, Indexes, PayloadType, StorageType}; + use segment::types::{Distance, PayloadType}; use tempdir::TempDir; use parking_lot::RwLock; @@ -154,20 +163,18 @@ mod tests { let vacuum_optimizer = VacuumOptimizer::new( 0.2, 50, - OptimizerThresholds{ + OptimizerThresholds { memmap_threshold: 1000000, indexing_threshold: 1000000, - payload_indexing_threshold: 1000000 + payload_indexing_threshold: 1000000, }, dir.path().to_owned(), temp_dir.path().to_owned(), - SegmentConfig { + CollectionParams { vector_size: 4, - index: Indexes::Plain {}, - payload_index: Some(Default::default()), distance: Distance::Dot, - storage_type: StorageType::InMemory, }, + Default::default() ); let suggested_to_optimize = vacuum_optimizer.check_condition(locked_holder.clone()); diff --git a/lib/collection/src/segment_manager/segment_managers.rs b/lib/collection/src/segment_manager/segment_managers.rs index 51a57172bb..836ead380e 100644 --- a/lib/collection/src/segment_manager/segment_managers.rs +++ b/lib/collection/src/segment_manager/segment_managers.rs @@ -1,9 +1,10 @@ -use segment::types::{SeqNumberType, ScoredPoint, PointIdType}; -use crate::collection::{CollectionResult}; -use crate::operations::CollectionUpdateOperations; -use crate::operations::types::{Record, SearchRequest}; use std::sync::Arc; +use segment::types::{PointIdType, ScoredPoint, SeqNumberType}; + +use crate::operations::CollectionUpdateOperations; +use crate::operations::types::{CollectionResult, Record, SearchRequest}; + pub trait SegmentSearcher { fn search(&self, // Request is supposed to be a read only, that is why no mutex used diff --git a/lib/collection/src/segment_manager/simple_segment_searcher.rs b/lib/collection/src/segment_manager/simple_segment_searcher.rs index d37a34635f..d2c88ea5d9 100644 --- a/lib/collection/src/segment_manager/simple_segment_searcher.rs +++ b/lib/collection/src/segment_manager/simple_segment_searcher.rs @@ -1,7 +1,7 @@ use crate::segment_manager::holders::segment_holder::{LockedSegment, LockedSegmentHolder}; use std::sync::Arc; use crate::segment_manager::segment_managers::{SegmentSearcher}; -use crate::collection::CollectionResult; +use crate::operations::types::CollectionResult; use segment::types::{ScoredPoint, PointIdType, SeqNumberType}; use tokio::runtime::Runtime; use std::collections::{HashSet, HashMap}; @@ -55,8 +55,6 @@ impl SegmentSearcher for SimpleSegmentSearcher { return Ok(vec![]); } - let distance = some_segment.unwrap().1.get().read().config().distance; - let searches: Vec<_> = segments .iter() .map(|(_id, segment)| @@ -89,7 +87,6 @@ impl SegmentSearcher for SimpleSegmentSearcher { !res }), request.top, - &distance, ); Ok(top_scores) diff --git a/lib/collection/src/segment_manager/simple_segment_updater.rs b/lib/collection/src/segment_manager/simple_segment_updater.rs index 8e23c4d866..145378c81f 100644 --- a/lib/collection/src/segment_manager/simple_segment_updater.rs +++ b/lib/collection/src/segment_manager/simple_segment_updater.rs @@ -1,25 +1,24 @@ -use std::sync::Mutex; -use crate::segment_manager::holders::segment_holder::{LockedSegmentHolder}; -use crate::segment_manager::segment_managers::SegmentUpdater; -use crate::operations::{CollectionUpdateOperations, FieldIndexOperations}; -use crate::collection::{CollectionResult, CollectionError}; -use segment::types::{SeqNumberType, PointIdType, PayloadKeyType, PayloadInterface}; -use std::collections::{HashSet, HashMap}; -use crate::operations::types::VectorType; +use std::collections::{HashMap, HashSet}; -use crate::operations::point_ops::{PointOperations, PointInsertOperations}; +use segment::types::{PayloadKeyType, PointIdType, SeqNumberType, PayloadInterface}; + +use crate::operations::{CollectionUpdateOperations, FieldIndexOperations}; +use crate::operations::point_ops::{PointInsertOperations, PointOperations}; +use crate::operations::types::{CollectionError, CollectionResult, VectorType}; +use crate::segment_manager::holders::segment_holder::LockedSegmentHolder; +use crate::segment_manager::segment_managers::SegmentUpdater; use crate::operations::payload_ops::PayloadOps; pub struct SimpleSegmentUpdater { segments: LockedSegmentHolder, - update_lock: Mutex, + // update_lock: Mutex, } impl SimpleSegmentUpdater { pub fn new(segments: LockedSegmentHolder) -> Self { SimpleSegmentUpdater { segments, - update_lock: Mutex::new(false), + // update_lock: Mutex::new(false), } } @@ -279,12 +278,14 @@ impl SegmentUpdater for SimpleSegmentUpdater { #[cfg(test)] mod tests { - use super::*; - use crate::segment_manager::fixtures::{build_searcher}; - use crate::segment_manager::segment_managers::SegmentSearcher; - use segment::types::PayloadVariant; use tempdir::TempDir; + use segment::types::PayloadVariant; + use crate::segment_manager::fixtures::build_searcher; + use crate::segment_manager::segment_managers::SegmentSearcher; + + use super::*; + #[test] fn test_point_ops() { let dir = TempDir::new("segment_dir").unwrap(); @@ -293,7 +294,6 @@ mod tests { let updater = SimpleSegmentUpdater { segments: searcher.segments.clone(), - update_lock: Mutex::new(false), }; let points = vec![1, 500]; @@ -349,7 +349,6 @@ mod tests { let updater = SimpleSegmentUpdater { segments: searcher.segments.clone(), - update_lock: Mutex::new(false), }; let mut payload: HashMap = Default::default(); diff --git a/lib/collection/src/update_handler/update_handler.rs b/lib/collection/src/update_handler/update_handler.rs index e5fcd1203e..3523ef0208 100644 --- a/lib/collection/src/update_handler/update_handler.rs +++ b/lib/collection/src/update_handler/update_handler.rs @@ -10,22 +10,27 @@ use crate::operations::CollectionUpdateOperations; use tokio::time::{Duration, Instant}; use tokio::runtime::Runtime; use log::debug; +use crate::operations::types::CollectionResult; pub type Optimizer = dyn SegmentOptimizer + Sync + Send; pub enum UpdateSignal { + /// Info that operation with Operation(SeqNumberType), + /// Stop all optimizers and listening Stop, + /// Empty signal used to trigger optimizers + Nop, } pub struct UpdateHandler { - optimizers: Arc>>, + pub optimizers: Arc>>, + pub flush_timeout_sec: u64, segments: LockedSegmentHolder, receiver: Receiver, worker: Option>, runtime_handle: Arc, wal: Arc>>, - flush_timeout_sec: u64, } @@ -63,6 +68,35 @@ impl UpdateHandler { )); } + /// Gracefully wait before all optimizations stop. + /// If some optimization is in progress - it will be finished before shutdown. + /// Blocking function. + pub fn wait_worker_stops(&mut self) -> CollectionResult<()> { + let res = match &mut self.worker { + None => (), + Some(handle) => self.runtime_handle.block_on(handle)? + }; + + self.worker = None; + Ok(res) + } + + fn process_optimization( + optimizers: Arc>>, + segments: LockedSegmentHolder, + ) { + for optimizer in optimizers.iter() { + let mut nonoptimal_segment_ids = optimizer.check_condition(segments.clone()); + while !nonoptimal_segment_ids.is_empty() { + debug!("Start optimization on segments: {:?}", nonoptimal_segment_ids); + // If optimization fails, it could not be reported to anywhere except for console. + // So the only recovery here is to stop optimization and await for restart + optimizer.optimize(segments.clone(), nonoptimal_segment_ids).unwrap(); + nonoptimal_segment_ids = optimizer.check_condition(segments.clone()); + } + } + } + async fn worker_fn( optimizers: Arc>>, receiver: Receiver, @@ -77,15 +111,13 @@ impl UpdateHandler { match recv_res { Ok(signal) => { match signal { + UpdateSignal::Nop => { + Self::process_optimization(optimizers.clone(), segments.clone()); + } UpdateSignal::Operation(operation_id) => { debug!("Performing update operation: {}", operation_id); - for optimizer in optimizers.iter() { - let unoptimal_segment_ids = optimizer.check_condition(segments.clone()); - if !unoptimal_segment_ids.is_empty() { - debug!("Start optimization on segments: {:?}", unoptimal_segment_ids); - optimizer.optimize(segments.clone(), unoptimal_segment_ids).unwrap(); - } - } + Self::process_optimization(optimizers.clone(), segments.clone()); + let elapsed = last_flushed.elapsed(); if elapsed > flush_timeout { debug!("Performing flushing: {}", operation_id); diff --git a/lib/collection/tests/collection_test.rs b/lib/collection/tests/collection_test.rs index 6e040a0703..a428994901 100644 --- a/lib/collection/tests/collection_test.rs +++ b/lib/collection/tests/collection_test.rs @@ -3,14 +3,13 @@ mod common; use collection::operations::CollectionUpdateOperations; use collection::operations::point_ops::{PointOperations, PointStruct}; -use crate::common::{simple_collection_fixture, TEST_OPTIMIZERS_CONFIG}; +use crate::common::{simple_collection_fixture}; use collection::operations::types::{UpdateStatus, SearchRequest, RecommendRequest}; use std::sync::Arc; use collection::operations::payload_ops::PayloadOps; use std::collections::HashMap; use segment::types::{PayloadKeyType, PayloadVariant, PayloadInterface}; use collection::collection_builder::collection_loader::load_collection; -use wal::WalOptions; use tempdir::TempDir; use tokio::runtime; use collection::operations::point_ops::PointInsertOperations::{BatchPoints, PointsList}; @@ -42,7 +41,7 @@ fn test_collection_updater() { Ok(res) => { assert_eq!(res.status, UpdateStatus::Completed) } - Err(err) => assert!(false, format!("operation failed: {:?}", err)), + Err(err) => assert!(false, "operation failed: {:?}", err), } let search_request = Arc::new(SearchRequest { @@ -60,7 +59,7 @@ fn test_collection_updater() { assert_eq!(res.len(), 3); assert_eq!(res[0].id, 2); } - Err(err) => assert!(false, format!("search failed: {:?}", err)), + Err(err) => assert!(false, "search failed: {:?}", err), } } @@ -105,12 +104,6 @@ fn test_collection_loading() { collection.update(assign_payload, true).unwrap(); } - - let wal_options = WalOptions { - segment_capacity: 100, - segment_queue_len: 0, - }; - let rt = Arc::new(runtime::Builder::new_multi_thread() .max_threads(2) .build().unwrap()); @@ -120,9 +113,7 @@ fn test_collection_loading() { let loaded_collection = load_collection( collection_dir.path(), - &wal_options, rt.clone(), - &TEST_OPTIMIZERS_CONFIG, ); let retrieved = loaded_collection.retrieve(&vec![1, 2], true, true).unwrap(); @@ -195,9 +186,9 @@ fn test_deserialization2() { eprintln!("read_obj = {:#?}", read_obj); - let crob_bytes = rmp_serde::to_vec(&insert_points).unwrap(); + let raw_bytes = rmp_serde::to_vec(&insert_points).unwrap(); - let read_obj2: CollectionUpdateOperations = rmp_serde::from_read_ref(&crob_bytes).unwrap(); + let read_obj2: CollectionUpdateOperations = rmp_serde::from_read_ref(&raw_bytes).unwrap(); eprintln!("read_obj2 = {:#?}", read_obj2); } diff --git a/lib/collection/tests/common/mod.rs b/lib/collection/tests/common/mod.rs index dc887d7100..c6cbd958e4 100644 --- a/lib/collection/tests/common/mod.rs +++ b/lib/collection/tests/common/mod.rs @@ -1,13 +1,13 @@ use collection::collection_builder::collection_builder::build_collection; -use wal::WalOptions; use collection::collection::Collection; -use segment::types::{Distance, SegmentConfig, Indexes}; +use segment::types::{Distance}; use tokio::runtime::Runtime; use tokio::runtime; use std::sync::Arc; use std::path::Path; use collection::collection_builder::optimizers_builder::OptimizersConfig; use collection::collection_builder::collection_loader::load_collection; +use collection::config::{WalConfig, CollectionParams}; pub const TEST_OPTIMIZERS_CONFIG: OptimizersConfig = OptimizersConfig { @@ -23,11 +23,6 @@ pub const TEST_OPTIMIZERS_CONFIG: OptimizersConfig = OptimizersConfig { #[allow(dead_code)] pub fn load_collection_fixture(collection_path: &Path) -> (Arc, Collection) { - let wal_options = WalOptions { - segment_capacity: 100, - segment_queue_len: 0, - }; - let threaded_rt = Arc::new(runtime::Builder::new_multi_thread() .max_threads(2) .build().unwrap()); @@ -35,29 +30,21 @@ pub fn load_collection_fixture(collection_path: &Path) -> (Arc, Collect let collection = load_collection( collection_path, - &wal_options, threaded_rt.clone(), - &TEST_OPTIMIZERS_CONFIG, ); return (threaded_rt, collection); } pub fn simple_collection_fixture(collection_path: &Path) -> (Arc, Collection) { - let wal_options = WalOptions { - segment_capacity: 100, - segment_queue_len: 0, + let wal_config = WalConfig { + wal_capacity_mb: 1, + wal_segments_ahead: 0 }; - let collection_config = SegmentConfig { + let collection_params = CollectionParams { vector_size: 4, - index: Indexes::Hnsw { - m: 16, - ef_construct: 128, - }, - payload_index: Some(Default::default()), distance: Distance::Dot, - storage_type: Default::default(), }; let threaded_rt = Arc::new(runtime::Builder::new_multi_thread() @@ -67,10 +54,11 @@ pub fn simple_collection_fixture(collection_path: &Path) -> (Arc, Colle let collection = build_collection( collection_path, - &wal_options, - &collection_config, + &wal_config, + &collection_params, threaded_rt.clone(), &TEST_OPTIMIZERS_CONFIG, + &Default::default() ).unwrap(); return (threaded_rt, collection); diff --git a/lib/segment/Cargo.toml b/lib/segment/Cargo.toml index b670d14827..5c77757106 100644 --- a/lib/segment/Cargo.toml +++ b/lib/segment/Cargo.toml @@ -1,23 +1,23 @@ [package] name = "segment" -version = "0.2.1" +version = "0.3.0" authors = ["Andrey Vasnetsov "] edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dev-dependencies] +pprof = { version = "0.4", features = ["flamegraph"] } tempdir = "0.3.7" criterion = "0.3" -rand = "0.8" - [dependencies] -ndarray = { version = "0.14", features = ["blas"] } -blas-src = { version = "0.6.1", default-features = false, features = ["openblas"] } -openblas-src = { version = "0.9", default-features = false, features = ["cblas", "static"] } +ndarray = { version = "0.15", features = ["blas"] } +blas-src = { version = "0.8", default-features = false, features = ["openblas"] } +openblas-src = { version = "0.10", default-features = false, features = ["cblas", "static"] } +parking_lot = "0.11" itertools = "0.10" rocksdb = "0.15.0" uuid = { version = "0.8", features = ["v4"] } @@ -25,7 +25,6 @@ bincode = "1.3" serde = { version = "~1.0", features = ["derive", "rc"] } serde_json = "~1.0" serde_cbor = "0.11.1" -rmp-serde = "~0.14" ordered-float = "1.0" thiserror = "1.0" atomic_refcell = "0.1.6" @@ -36,7 +35,15 @@ log = "0.4" env_logger = "0.7.1" geo = "0.17.0" num-traits = "0.2.14" +rand = "0.8" +lru = "0.6.5" +bit-vec = "0.6" +fasthash = "0.4" [[bench]] name = "vector_search" -harness = false \ No newline at end of file +harness = false + +[[bench]] +name = "hnsw_build_graph" +harness = false diff --git a/lib/segment/benches/hnsw_build_graph.rs b/lib/segment/benches/hnsw_build_graph.rs new file mode 100644 index 0000000000..3f68761cb3 --- /dev/null +++ b/lib/segment/benches/hnsw_build_graph.rs @@ -0,0 +1,53 @@ +mod prof; + +use criterion::{Criterion, criterion_group, criterion_main}; +use segment::types::{Distance, PointOffsetType}; +use rand::thread_rng; +use segment::index::hnsw_index::graph_layers::GraphLayers; +use segment::index::hnsw_index::point_scorer::FilteredScorer; +use segment::fixtures::index_fixtures::{TestRawScorerProducer, FakeConditionChecker}; + + + +const NUM_VECTORS: usize = 10000; +const DIM: usize = 32; +const M: usize = 16; +const EF_CONSTRUCT: usize = 64; +const USE_HEURISTIC: bool = true; + + +fn hnsw_benchmark(c: &mut Criterion) { + let vector_holder = TestRawScorerProducer::new(DIM, NUM_VECTORS, Distance::Cosine); + let mut group = c.benchmark_group("hnsw-index-build-group"); + group.sample_size(10); + group.bench_function("hnsw_index", |b| { + b.iter(|| { + let mut rng = thread_rng(); + let mut graph_layers = GraphLayers::new( + NUM_VECTORS, M, M * 2, EF_CONSTRUCT, 10, USE_HEURISTIC, + ); + let fake_condition_checker = FakeConditionChecker {}; + for idx in 0..(NUM_VECTORS as PointOffsetType) { + let added_vector = vector_holder.vectors[idx as usize].to_vec(); + let raw_scorer = vector_holder.get_raw_scorer(added_vector); + let scorer = FilteredScorer { + raw_scorer: &raw_scorer, + condition_checker: &fake_condition_checker, + filter: None, + }; + let level = graph_layers.get_random_layer(&mut rng); + graph_layers.link_new_point(idx, level, &scorer); + } + }) + }); + group.finish(); +} + + +criterion_group!{ + name = benches; + config = Criterion::default().with_profiler(prof::FlamegraphProfiler::new(100)); + targets = hnsw_benchmark +} + +criterion_main!(benches); \ No newline at end of file diff --git a/lib/segment/benches/prof.rs b/lib/segment/benches/prof.rs new file mode 100644 index 0000000000..48e06d94fb --- /dev/null +++ b/lib/segment/benches/prof.rs @@ -0,0 +1,79 @@ +use std::{fs::File, os::raw::c_int, path::Path}; + +use criterion::profiler::Profiler; +use pprof::ProfilerGuard; +use pprof::flamegraph::TextTruncateDirection; + + +/// Small custom profiler that can be used with Criterion to create a flamegraph for benchmarks. +/// Also see [the Criterion documentation on this][custom-profiler]. +/// +/// ## Example on how to enable the custom profiler: +/// +/// ``` +/// mod perf; +/// use perf::FlamegraphProfiler; +/// +/// fn fibonacci_profiled(criterion: &mut Criterion) { +/// // Use the criterion struct as normal here. +/// } +/// +/// fn custom() -> Criterion { +/// Criterion::default().with_profiler(FlamegraphProfiler::new()) +/// } +/// +/// criterion_group! { +/// name = benches; +/// config = custom(); +/// targets = fibonacci_profiled +/// } +/// ``` +/// +/// The neat thing about this is that it will sample _only_ the benchmark, and not other stuff like +/// the setup process. +/// +/// Further, it will only kick in if `--profile-time