Commit Graph

41 Commits

Author SHA1 Message Date
qdrant-cloud-bot
7e15d343c2 Introduce EdgeShardConfig for edge shard (#8322)
* Introduce EdgeShardConfig for edge shard

- Add EdgeShardConfig and EdgeOptimizersConfig in lib/edge/src/config.rs
  - Segment config (vector_data, sparse_vector_data, payload_storage_type)
  - Global hnsw_config and per-vector HNSW in segment config
  - Optimizer params: deleted_threshold, vacuum_min_vector_number,
    default_segment_number, max_segment_size, indexing_threshold,
    prevent_unoptimized (excludes memmap_threshold, flush_interval_sec,
    max_optimization_threads)
- Persist/load as edge_config.json in shard path
- EdgeShard uses RwLock<EdgeShardConfig>; load() accepts Option<EdgeShardConfig>,
  falls back to file or infer from segments; compatibility checked on load
- load_with_segment_config() for backward compatibility (SegmentConfig -> EdgeShardConfig)
- optimize() uses EdgeShardConfig for hnsw and optimizer thresholds
- Public methods: set_hnsw_config(), set_vector_hnsw_config(), set_optimizers_config()
  (update and persist)
- Python and examples use load_with_segment_config with existing config API

Made-with: Cursor

* Refactor EdgeShardConfig: user-facing params only, config module

- Replace SegmentConfig inside EdgeShardConfig with user-facing fields:
  - on_disk_payload (bool) instead of payload_storage_type
  - vectors: HashMap<VectorNameBuf, EdgeVectorParams> with on_disk per vector,
    no per-vector quantization; global quantization_config only
  - sparse_vectors: HashMap<VectorNameBuf, EdgeSparseVectorParams> with on_disk
- EdgeVectorParams / EdgeSparseVectorParams use on_disk (bool) instead of
  storage_type; conversion to VectorDataConfig/SparseVectorDataConfig in
  to_segment_config()
- Add config module: mod.rs, optimizers.rs, vectors.rs, shard.rs
- from_segment_config(&SegmentConfig) fills all inferrable params
- to_segment_config() builds SegmentConfig for segments and optimize()
- load_with_segment_config takes Option<SegmentConfig>, uses from_segment_config

Made-with: Cursor

* Move optimizer threshold helpers to shard crate

- Add get_number_segments, get_indexing_threshold_kb, get_max_segment_size_kb,
  get_deferred_points_threshold_bytes in shard::optimizers::config
- Collection OptimizersConfig and edge EdgeOptimizersConfig delegate to these
- Single place for threshold logic; collection and edge use shard helpers

Made-with: Cursor

* Use destructuring in config conversions to avoid missing new fields

- EdgeVectorParams: destructure VectorDataConfig in from_*, destructure self in to_vector_data_config
- EdgeSparseVectorParams: destructure SparseVectorDataConfig and SparseIndexConfig in from_*, destructure self in to_sparse_vector_data_config
- EdgeShardConfig: destructure SegmentConfig in from_segment_config, destructure self in to_segment_config
Adding new fields to source structs will now cause compile errors until conversions are updated.

Made-with: Cursor

* refactor: centralize on_disk_payload→payload_storage_type, on_disk→storage_type, and appendable quantization logic

- PayloadStorageType::from_on_disk_payload(bool) in segment (Mmap/InRamMmap)
- VectorStorageType::from_on_disk(bool) in segment (ChunkedMmap/InRamChunkedMmap)
- QuantizationConfig::for_appendable_segment(Option<&Self>) in segment (feature flag + supports_appendable)
- collection: use from_on_disk_payload in non-rocksdb branch
- edge shard/vectors: use new helpers; remove duplicated conditionals
- shard optimizers: use from_on_disk and for_appendable_segment

Made-with: Cursor

* refactor(edge): use EdgeShardConfig directly, drop segment_config

- Add plain_segment_config() for create_appendable_segment (no HNSW)
- Add segment_optimizer_config() built from EdgeShardConfig for blocking optimizers
- Add vector_data_config(name) for query/MMR
- build_blocking_optimizers: use segment_optimizer_config() instead of SegmentConfig
- create_appendable_segment: use plain_segment_config()
- search/query: use config().vectors and vector_data_config() instead of segment_config()
- Remove segment_config() from EdgeShardConfig and EdgeShard
- Add to_plain_vector_data_config on EdgeVectorParams

Made-with: Cursor

* [manual] review changes

* refactor(edge-py): wrap EdgeShardConfig, add EdgeVectorParams/EdgeSparseVectorParams

- PyEdgeConfig now wraps EdgeShardConfig (vectors, sparse_vectors, on_disk_payload, etc.)
- PyEdgeVectorParams / PyEdgeSparseVectorParams wrap edge config types
- PyEdgeOptimizersConfig for optional optimizer settings
- EdgeShard.load() uses EdgeShardConfig; edge::config made pub for Python crate
- cargo fmt + clippy (remove map_identity)

Made-with: Cursor

* refactor(edge-py): simplify config API, remove unused Py* types, add EdgeConfig

- Remove unused PyPayloadStorageType, PyVectorDataConfig, PyVectorStorageType,
  PySparseVectorDataConfig, PySparseVectorStorageType from Python bindings
- Move PyEdgeOptimizersConfig to lib/edge/python/src/config/optimizers.rs
- Update qdrant_edge.pyi: EdgeConfig with vectors/sparse_vectors,
  EdgeVectorParams, EdgeSparseVectorParams, EdgeOptimizersConfig
- Update examples (common.py, repr.py) to use new config API
- Run cargo fmt

Made-with: Cursor

* [manual] review changes

* [manual] review changes

* [manual] fix test

* Address CodeRabbit review comments for PR 8322 (#8324)

* Address CodeRabbit review comments for PR 8322

- Python examples: explicit imports (repr.py, common.py) and new EdgeConfig API
- HnswIndexConfig: add max_indexing_threads param and property in .pyi and Rust bindings
- EdgeConfig: make vectors optional for sparse-only configs; validate at least one of vectors/sparse_vectors
- EdgeShardConfig::load: use try_exists(), propagate I/O errors
- from_segment_config: infer hnsw_config from per-vector HNSW when all agree
- EdgeShard setters: atomic clone-mutate-save-then-replace; persist config save errors
- Segment compat: prefix vector name in error messages; resolve None datatype to Float32
- max_indexing_threads: preserve 0 (auto) sentinel in trait default; remove per-optimizer overrides
- SegmentOptimizerConfig:🆕 build plain and optimizer maps in single pass
- config_mismatch_optimizer tests: use VectorNameBuf::from() instead of .into()
- vectors.rs: doc updates for per-vector quantization

Made-with: Cursor

* Address @generall review: SaveOnDisk for config, resolve num_rayon_threads in optimizer

- Use SaveOnDisk<EdgeShardConfig> for EdgeShard config (generall: 'We have SaveOnDisk struct for this')
  - Create via SaveOnDisk::new() after resolving config; setters use .write() for atomic persist
  - set_vector_hnsw_config: clone then mutate then write (fallible setter)
- max_indexing_threads: resolve 0 (auto) via num_rayon_threads inside impl (generall: 'proper solution would be to resolve num_rayon_threads inside the optimizer impl')
  - max_indexing_threads_sentinel_aware() now returns Some(num_rayon_threads(raw)) so callers get actual thread count

Made-with: Cursor

* [manual] reorganize num_rayon_threads -> get_num_indexing_threads to better account per-vector configuration

---------

Co-authored-by: Cursor Agent <agent@cursor.com>
Co-authored-by: generall <andrey@vasnetsov.com>

* update docstring and pyi

* fmt

* fmt

* clipy

---------

Co-authored-by: Cursor Agent <agent@cursor.com>
Co-authored-by: generall <andrey@vasnetsov.com>
2026-03-10 00:10:04 +01:00
Ivan Boldyrev
56799122b0 Split SegmentEntry into appendable and non-appendable part (#8047)
* Split `SegmentEntry`

Add `ImmutableSegmentEntry` for operations that can be applied to
immutable segments, and make the `SegmentEntry` as it subtrait.

* Rename to `NonAppendableSegmentEntry`

It differs semantically from `ImmutableSegmentEntry` by allowing point
deletion.  Move point deletion to the trait too.

* Fix docstring

* use NonAppendableSegmentEntry where possible

---------

Co-authored-by: Andrey Vasnetsov <andrey@vasnetsov.com>
2026-02-05 15:46:58 +01:00
xzfc
77c71712db Indexing progress (#7625)
* Add progress_tracker.rs

* Pass progress tracker around

* Populate progress tracker with actual data

* Expose progress on `/collections/{name}/optimizations` endpoint
2025-12-09 00:15:36 +00:00
xzfc
95b4bbf978 Rename HnswConfig::{copy_vectors -> inline_storage} (#7389) 2025-10-13 10:08:06 +00:00
xzfc
22df6bc30a Integrate hnsw_with_vectors (#7232)
* StorageGraphLinksVectors::try_new, make GraphLinksVectorsLayout non-fallible

* Integrate hnsw_with_vectors

* remove unused function

---------

Co-authored-by: generall <andrey@vasnetsov.com>
2025-09-18 17:36:48 +00:00
xzfc
706571d8b4 Configurable HNSW healing threshold (#6756) 2025-06-25 10:22:18 +00:00
Luis Cossío
93dea164e8 Deterministic HNSW builder (#6477)
* pass a RNG to hnsw index building

* use single-threaded build for accuracy-measuring tests

* clippy
2025-05-07 11:20:19 -04:00
xzfc
89d399ef99 Incremental HNSW index building: append-only case (#6325)
* Pass FeatureFlags into VectorIndexBuildArgs

* Incremental HNSW index building: append-only case

* Use debug_assert

* first_few_ids

* Check deleted_point_count

* Drop unused method
2025-04-09 18:56:53 +00:00
Jojii
ae64690c7a Measure Payload Index IO Writes (#6137)
* Prepare measurement of index creation + Remove vector deletion
measurement

* add hw_counter to add_point functions

* Adjust add_point(..) function signatures

* Add new measurement type: payload index IO write

* Measure payload index IO writes

* Some Hw measurement performance improvements

* Review remarks

* Fix measurements in distributed setups

* review fixes

---------

Co-authored-by: generall <andrey@vasnetsov.com>
2025-03-24 19:39:17 +01:00
Tim Visée
3e536347e1 Bump Rust edition to 2024 (#6042)
* Bump Rust edition to 2024

* gen is a reserved keyword now

* Remove ref mut on references

* Mark extern C as unsafe

* Wrap unsafe function bodies in unsafe block

* Geo hash implements Copy, don't reference but pass by value instead

* Replace secluded self import with parent

* Update execute_cluster_read_operation with new match semantics

* Fix lifetime issue

* Replace map_or with is_none_or

* set_var is unsafe now

* Reformat
2025-02-25 11:21:25 +01:00
Andrey Vasnetsov
f7d0814ab6 IO resource usage permit (#6015)
* rename cpu_budget -> resource_budget

* clippy

* add io budget to resources

* fmt

* move budget structures into a separate file

* add extend permit function

* dont extend existing permit

* switch from IO to CPU permit

* do not release resource before aquiring an extension

* fmt

* Review remarks

* Improve resource permit number assertion

* Make resource permit replace_with only acquire extra needed permits

* Remove obsolete drop implementation

* allocate IO budget same as CPU

* review fixes

---------

Co-authored-by: timvisee <tim@visee.me>
2025-02-20 09:05:00 +01:00
xzfc
b23cd8cb87 Use simple_segment_constructor (#5919)
* VECTOR1_NAME and VECTOR2_NAME

* Use simple_segment_constructor
2025-01-30 20:15:33 +00:00
xzfc
34b039e9b1 Add payload_json! macro (#5881)
* Add payload_json! macro

* Replace usage of `json!({...})` with `payload_json! {...}`

* Drop `impl From<Value> for Payload`
2025-01-28 10:35:02 +01:00
xzfc
054273c44b Pass old_indices to HNSWIndex::new (#5835) 2025-01-27 06:54:13 +00:00
xzfc
bf981e525f Split HNSWIndex::open and HNSWIndex::build (#5853)
* HNSWSearchesTelemetry::new()

* Split HNSWIndex::open and HNSWIndex::build
2025-01-24 00:09:57 +00:00
Jojii
fd4705c29d Measure payload read IO (#5773)
* Measure read io for payload storage

* Add Hardware Counter to update functions

* Fix tests and benches

* Rename (some) *_measured functions back to original
2025-01-16 14:25:55 +01:00
xzfc
694ec10f3d GraphLinks: replace trait with enum (#5651)
* GraphLinks: replace trait with enum

* Vec::with_capacity
2024-12-23 18:59:53 +00:00
Jojii
e0d0e233d8 Timeout aware hardware counter (#5555)
* Make hardware counting timeout aware

* improve test

* rebuild everything

* fmt

* post-rebase fixes

* upd tests

* fix tests

---------

Co-authored-by: generall <andrey@vasnetsov.com>
2024-12-10 12:12:36 +01:00
Ivan Pleshkov
73259267ae GPU HNSW integration (#5535)
* gpu hnsw

---------

Co-authored-by: Andrey Vasnetsov <andrey@vasnetsov.com>
2024-12-05 00:58:49 +01:00
Jojii
98ef428878 Hw counter utilization checks (#5288)
* Enforce usage of hardware counter values

* improve comments

* log a warning in release mode

* some minor improvements

* avoid cloning for hardware counter

* fmt

---------

Co-authored-by: generall <andrey@vasnetsov.com>
2024-10-25 09:52:18 +02:00
Jojii
73a785f145 Add HardwareCounterCell + CPU measurement for sparse vector search (#5239)
* Add HardwareCounterCell and counting for sparse plain search

* Add measurement for indexed sparse vector

* add tests for calculations

* move SegmentQueryContent higher in the call stack

* move hardware counters inside the query context

* fix clippy

* Fix applying hardware measurements and add test for this

---------

Co-authored-by: generall <andrey@vasnetsov.com>
2024-10-17 16:24:13 +02:00
xzfc
69b63baa07 Drop JsonPathString (#4621)
* drop some code

* Drop JsonPathString

* Fix test_remove_key

Drop failing tests:
- Deleting array indices is not idempotent, so we don't support it.
- Empty JSONPath is not supported.

* Make json_path::path() non-generic

* Remove references to JsonPathV2

* Drop JsonPathInterface

* Move json_path::v2 code into json_path

* Drop validate_not_empty

* Drop JsonPath::head() as being unused

* Replace path() with JsonPath::new()

* Restore comments

* Move tests to json_path

* Use json() consistently in tests

* Replace many into calls with Into trait

---------

Co-authored-by: timvisee <tim@visee.me>
2024-07-11 04:06:40 +00:00
xzfc
cde39fb8a2 Move build_index out of VectorIndex (#4490)
* Move build_index out of VectorIndex

* Build index in HNSWIndex::open()

* Introduce HnswIndexOpenArgs

* Proper deletion

* Improve tests

* HNSW::open(): add warn, comment and assert

* Revert to making up the config if it does not exist
2024-06-24 14:44:34 +02:00
Andrey Vasnetsov
d4807dcc8b Api consistency update (#4533)
* rename search_params -> params

* rename multivector_config + generate schema

* upd tests
2024-06-23 23:56:42 +02:00
xzfc
cb31c533bf Introduce Cargo feature "testing" (#4192) 2024-05-07 16:14:46 +00:00
Arnaud Gourlay
571143ae87 Simplify MaxSim configuration (#4171)
* Simplify MaxSim configuration

* enable extension of multivectorconfig

* rename multi_vec_config to multivec_config
2024-05-06 14:19:42 +02:00
Andrey Vasnetsov
4038a0082e Faster deleted filter in proxy segments (#4148)
* [WIP] introduce internal has-id check

* fmt

* update value of the deleted_mask in proxy

* use deleted_points from the context, if present

* fmt

* move stopped flag into query context

* fmt

* segment-specific query context

* enable custom deleted mask in proxy

* remove unused HasIdConditionInternal

* fix tests

* remove debug
2024-05-06 11:51:50 +02:00
Andrey Vasnetsov
c173a9f5e5 Sparse idf dot (#4126)
* introduce QueryContext, which accumulates runtime info needed for executing search

* fmt

* propagate query context into segment internals

* [WIP] prepare idf stats for search query context

* Split SparseVector and RemmapedSparseVector to guarantee we will not mix them up on the type level

* implement filling of the query context with IDF statistics

* implement re-weighting of the sparse query with idf

* fmt

* update idf param only if explicitly specified (more consistent with diff param update

* replace idf bool with modifier enum, improve further extensibility

* test and fixes

* Update lib/collection/src/operations/types.rs

Co-authored-by: Arnaud Gourlay <arnaud.gourlay@gmail.com>

* review fixes

* fmt

---------

Co-authored-by: Arnaud Gourlay <arnaud.gourlay@gmail.com>
2024-04-29 14:54:14 +02:00
Ivan Pleshkov
224e4f600a Byte storage integration into segment (#4049)
* byte storage with quantization

raw scorer integration

config and test

are you happy fmt

fn renamings

cow refactor

use quantization branch

quantization update

* are you happy clippy

* don't use distance in quantized scorers

* fix build

* add fn quantization_preprocess

* apply preprocessing for only cosine float metric

* fix sparse vectors tests

* update openapi

* more complicated integration test

* update openapi comment

* mmap byte storages support

* fix async test

* move .unwrap closer to the actual check of the vector presence

* fmt

* remove distance similarity function

* avoid copying data while working with cow

---------

Co-authored-by: generall <andrey@vasnetsov.com>
2024-04-18 00:42:17 +02:00
Andrey Vasnetsov
c1c1ebf478 remove search method from serment trait to simplify usage in tests and prevent accidental usage in release (#3999) 2024-04-11 22:52:01 +02:00
Andrey Vasnetsov
ebc9ba0172 Fix indexed-only behavior for proxy shard (#3998)
* move check for unindexed segment size inside the segment to allow proxy shard decide where to search better

* fmt

* move indexed_only check inside the plain index
2024-04-11 13:16:56 +02:00
Arnaud Gourlay
b49000858a Multivec knob for SegmentConfig (#3963)
* Multivec knob for SegmentConfig

* regen openapi

* add TODO for next step

* introduce multivecconfig to support more similarity aggregation

* update openapi
2024-04-04 16:38:09 +02:00
xzfc
0ed86ce057 Introduce JsonPathString (#3674)
* Introduce JsonPathString

* Fix fomatting
2024-02-23 15:38:40 +01:00
Tim Visée
0301e39943 Dynamic CPU saturation internals (#3364)
* Move CPU count function to common, fix wrong CPU count in visited list

* Change default number of rayon threads to 8

* Use CPU budget and CPU permits for optimizer tasks to limit utilization

* Respect configured thread limits, use new sane defaults in config

* Fix spelling issues

* Fix test compilation error

* Improve breaking if there is no CPU budget

* Block optimizations until CPU budget, fix potentially getting stuck

Our optimization worker now blocks until CPU budget is available to
perform the task.

Fix potential issue where optimization worker could get stuck. This
would happen if no optimization task is started because there's no
available CPU budget. This ensures the worker is woken up again to
retry.

* Utilize n-1 CPUs with optimization tasks

* Better handle situations where CPU budget is drained

* Dynamically scale rayon CPU count based on CPU size

* Fix incorrect default for max_indexing_threads conversion

* Respect max_indexing_threads for collection

* Make max_indexing_threads optional, use none to set no limit

* Update property documentation and comments

* Property max_optimization_threads is per shard, not per collection

* If we reached shard optimization limit, skip further checks

* Add remaining TODOs

* Fix spelling mistake

* Align gRPC comment blocks

* Fix compilation errors since last rebase

* Make tests aware of CPU budget

* Use new CPU budget calculation function everywhere

* Make CPU budget configurable in settings, move static budget to common

* Do not use static CPU budget, instance it and pass it through

* Update CPU budget description

* Move heuristic into defaults

* Fix spelling issues

* Move cpu_budget property to a better place

* Move some things around

* Minor review improvements

* Use range match statement for CPU count heuristics

* Systems with 1 or 2 CPUs do not keep cores unallocated by default

* Fix compilation errors since last rebase

* Update lib/segment/src/types.rs

Co-authored-by: Luis Cossío <luis.cossio@qdrant.com>

* Update lib/storage/src/content_manager/toc/transfer.rs

Co-authored-by: Luis Cossío <luis.cossio@qdrant.com>

* Rename cpu_budget to optimizer_cpu_budget

* Update OpenAPI specification

* Require at least half of the desired CPUs for optimizers

This prevents running optimizations with just one CPU, which could be
very slow.

* Don't use wildcard in CPU heuristic match statements

* Rename cpu_budget setting to optimizer_cpu_budget

* Update CPU budget comments

* Spell acquire correctly

* Change if-else into match

Co-authored-by: Luis Cossío <luis.cossio@qdrant.com>

* Rename max_rayon_threads to num_rayon_threads, add explanation

* Explain limit in update handler

* Remove numbers for automatic selection of indexing threads

* Inline max_workers variable

* Remove CPU budget from ShardTransferConsensus trait, it is in collection

* small allow(dead_code) => cfg(test)

* Remove now obsolete lazy_static

* Fix incorrect CPU calculation in CPU saturation test

* Make waiting for CPU budget async, don't block current thread

* Prevent deadlock on optimizer signal channel

Do not block the optimization worker task anymore to wait for CPU budget
to be available. That prevents our optimizer signal channel from being
drained, blocking incoming updates because the cannot send another
optimizer signal. Now, prevent blocking this task all together and
retrigger the optimizers separately when CPU budget is available again.

* Fix incorrect CPU calculation in optimization cancel test

* Rename CPU budget wait function to notify

* Detach API changes from CPU saturation internals

This allows us to merge into a patch version of Qdrant. We can
reintroduce the API changes in the upcoming minor release to make all of
it fully functional.

---------

Co-authored-by: Luis Cossío <luis.cossio@qdrant.com>
Co-authored-by: Luis Cossío <luis.cossio@outlook.com>
2024-01-31 11:56:34 +01:00
Ivan Pleshkov
336efea59e Sparse index segment and collection config (#2802)
* quantization storage as separate entity

sparse index try to extend segment types

fix build

fix async scorer

codespell

update openapi

update vector index

remove code duplications

more fixes

more fixes

fix build

fix deserialization test

remove transform_into

are you happy clippy

update openapi

update openapi

are you happy clippy

fix build

optional serialize

more defaults

update openapi

fix comments

generic transpose_map_into_named_vector

rename fields in tests

remove obsolete parts

only named sparse config

VectorStruct without unnamed sparse

NamedVectorStruct without unnamed sparse

remove obsolete test

update openapi

mmap index

revert preprocess function

are you happy fmt

update openapi

fix build

fix tests

are you happy fmt

fix for client generation

fix sparse segment creation

fix basic sparse test

fix conflicts

remove obsolete convertion

fix build

config diffs

update openapi

review remarks

update openapi

fix batch upsert

add failing test showing bad ids matching

fix sparse vector insertion

remove on_disk flag

update openapi

revert debug assert

simplify conversions

update openapi

remove on disk storage flag

update openapi

default for vector config

update openapi comment

remove diffs

update openapi

* enable consensus test

* add comment

* update openapi
2023-12-01 13:10:58 +01:00
Ivan Pleshkov
cb917ea0db raw scorer with operation result (#2897) 2023-10-31 23:44:20 +01:00
Ivan Pleshkov
8cdeda26d8 Quantization storage as separate entity (#2797)
* quantization storage as separate entity

* simplify max threads calculation
2023-10-12 11:21:34 +02:00
Luis Cossío
b25fc4c8ea Refactor batch search to allow different scorers (#2529)
* add enum for vector query on segment search

* rename newly introduced types

* fix: handle QueryVector on async scorer

* handle QueryVector in QuantizedVectors impl

* fix async scorer test after refactor

* rebase + refactor on queue_proxy_shard.rs

* constrain refactor propagation to segment_searcher

* fmt

* fix after rebase
2023-09-05 09:26:24 -03:00
Andrey Vasnetsov
49b3ae57b2 Search timeout (#2293)
* pass atomic bool from local shard to raw scorer

* pass atomic bool from local shard to raw scorer

* is_stopped in async scorer

* fmt

* is_stopped in quantized scorer

* terminating scorer if stopped

* enable timeout in local_shard

* allow timeout configuration

* use tokio spawn to ensure timeout handling if request is dropped

* Revert "use tokio spawn to ensure timeout handling if request is dropped"

This reverts commit 1068cf48d4.

* use stopping guard instead of task

* report error if search request is stopped

* fmt

* refactor transient error handelling
2023-07-24 12:45:33 +02:00
Ivan Pleshkov
12423f910d Add missed vector preprocess (#2203)
* test missed preprocess after segment update

* missed preprocess

* remove preprocess_named_vectors fn

* are you happy clippy

* fix integration tests

---------

Co-authored-by: generall <andrey@vasnetsov.com>
2023-07-05 00:30:15 +02:00
Andrey Vasnetsov
a75420ddcc Fix batch request with duplicated filter (#2051)
* fix double usage of iterator

* tests
2023-06-09 00:05:00 +02:00