Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ LOG_LEVEL=INFO
LOG_FILE="logs/vectordb_bench.log"
# TIMEZONE=

# NUM_PER_BATCH=
# DEFAULT_DATASET_URL=

DATASET_LOCAL_DIR="/tmp/vectordb_bench/dataset"
Expand Down
16 changes: 8 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -287,7 +287,7 @@ OpenSearch Serverless (AOSS) is a serverless deployment option for Amazon OpenSe
**Example: Run performance test on OpenSearch Serverless**

```shell
NUM_PER_BATCH=100 vectordbbench awsopensearch --db-label aoss \
vectordbbench awsopensearch --db-label aoss --insert-batch-size 100 \
--serverless --aws-region us-east-1 \
--host <collection-id>.aoss.us-east-1.on.aws --port 443 \
--case-type Performance768D1M \
Expand All @@ -303,13 +303,13 @@ OpenSearch Serverless-specific options:
|--------|-------------|
| `--serverless` | Enable OpenSearch Serverless mode (uses AWS SigV4 auth) |
| `--aws-region` | AWS region for the AOSS collection (default: `us-east-1`) |
| `NUM_PER_BATCH` | Number of vectors per Serverless bulk request (default: `100`) |
| `--insert-batch-size` | Number of vectors per Serverless bulk request (default: `100`) |

> **Notes:**
> - `--user` and `--password` are not needed for Serverless mode
> - `--engine` is accepted but ignored internally (AOSS manages the engine)
> - `--force-merge-enabled`, `--refresh-interval`, `--flush-threshold-size`, and `--cb-threshold` are ignored for Serverless
> - Keep `NUM_PER_BATCH` small enough for the Serverless bulk API request limits
> - Keep `--insert-batch-size` small enough for the Serverless bulk API request limits

### Run Elastic Cloud from command line

Expand Down Expand Up @@ -478,7 +478,7 @@ pip install 'vectordb-bench[hologres]' 'psycopg[binary]' pgvector
Execute tests for the index types: HGraph.

```shell
NUM_PER_BATCH=10000 vectordbbench hologreshgraph --host Hologres_Endpoint --port 80 \
vectordbbench hologreshgraph --host Hologres_Endpoint --port 80 --insert-batch-size 10000 \
--user ACCESS_ID --password ACCESS_KEY --database DATABASE_NAME \
--m 64 --ef-construction 400 --case-type Performance768D10M \
--index-type HGraph --ef-search 400 --k 10 --num-concurrency 1,60,70,75,80,90,95,100,105,110,115,120,125,130 \
Expand Down Expand Up @@ -532,12 +532,12 @@ To list the options for zvec, execute vectordbbench zvec --help
Doris supports ann index with type hnsw from version 4.0.x

```shell
NUM_PER_BATCH=1000000 vectordbbench doris --http-port=8030 --port=9030 --db-name=vector_test --case-type=Performance768D1M --stream-load-rows-per-batch=500000
vectordbbench doris --http-port=8030 --port=9030 --db-name=vector_test --case-type=Performance768D1M --insert-batch-size=1000000 --stream-load-rows-per-batch=500000
```

Using flag `--session-var`, if you want to test doris with some customized session variables. For example:
```shell
NUM_PER_BATCH=1000000 vectordbbench doris --http-port=8030 --port=9030 --db-name=vector_test --case-type=Performance768D1M --stream-load-rows-per-batch=500000 --session-var enable_profile=True
vectordbbench doris --http-port=8030 --port=9030 --db-name=vector_test --case-type=Performance768D1M --insert-batch-size=1000000 --stream-load-rows-per-batch=500000 --session-var enable_profile=True
```

Mote options:
Expand All @@ -558,8 +558,8 @@ Mote options:
--session-var TEXT Session variable key=value applied to each
SQL session (repeatable)
--stream-load-rows-per-batch INTEGER
Rows per single stream load request; default
uses NUM_PER_BATCH
Rows per Doris stream-load request; when
omitted, the Doris client default is used
--no-index Create table without ANN index
```

Expand Down
2 changes: 1 addition & 1 deletion docs/release/2026-05-cloud-leaderboard.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ vectordbbench zillizautoindex \
--uri "$ZILLIZ_URI" \
--token "$ZILLIZ_TOKEN" \
--collection-name cloud_insert_laion100m_bs10k \
--cloud-insert-batch-size 10000 \
--insert-batch-size 10000 \
--load-concurrency 16 \
--skip-search-serial \
--skip-search-concurrent \
Expand Down
15 changes: 6 additions & 9 deletions tests/test_aws_opensearch.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,19 @@

import pytest

from vectordb_bench import config
from vectordb_bench.backend.clients.aws_opensearch.aws_opensearch import AWSOpenSearch


def test_serverless_insert_uses_configured_batch_size(monkeypatch) -> None:
def test_serverless_insert_uses_configured_batch_size() -> None:
bulk_requests = []

def bulk(*, body):
def bulk(*, body: list) -> None:
bulk_requests.append(body)

monkeypatch.setattr(config, "NUM_PER_BATCH", 2)

db = object.__new__(AWSOpenSearch)
db.client = SimpleNamespace(bulk=bulk)
db._is_serverless = True
db._insert_batch_size = 2
db.index_name = "test-index"
db.vector_col_name = "embedding"
db.with_scalar_labels = False
Expand All @@ -33,13 +31,12 @@ def bulk(*, body):


@pytest.mark.parametrize("batch_size", [0, -1])
def test_serverless_insert_rejects_non_positive_batch_size(monkeypatch, batch_size: int) -> None:
monkeypatch.setattr(config, "NUM_PER_BATCH", batch_size)

def test_serverless_insert_rejects_non_positive_batch_size(batch_size: int) -> None:
db = object.__new__(AWSOpenSearch)
db._is_serverless = True
db._insert_batch_size = batch_size

with pytest.raises(ValueError, match="NUM_PER_BATCH must be greater than 0"):
with pytest.raises(ValueError, match="insert_batch_size must be greater than 0"):
db._insert_with_single_client(
embeddings=[[0.1]],
metadata=[1],
Expand Down
12 changes: 12 additions & 0 deletions tests/test_case_runner_reuse.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from pydantic import SecretStr

from vectordb_bench import config
from vectordb_bench.backend.clients import DB
from vectordb_bench.backend.clients.api import EmptyDBCaseConfig, MetricType
from vectordb_bench.backend.clients.doris.config import DorisCaseConfig, DorisConfig
Expand All @@ -12,6 +13,8 @@
from vectordb_bench.metric import Metric
from vectordb_bench.models import CaseConfig, CaseType, TaskConfig, TaskStage, TestResult

DEFAULT_INSERT_BATCH_SIZE = config.DEFAULT_INSERT_BATCH_SIZE


def make_runner(
*,
Expand All @@ -21,6 +24,7 @@ def make_runner(
db_config=None,
db_case_config=None,
stages: list[TaskStage] | None = None,
insert_batch_size: int = DEFAULT_INSERT_BATCH_SIZE,
) -> CaseRunner:
if db_config is None:
if db == DB.TurboPuffer:
Expand All @@ -45,6 +49,7 @@ def make_runner(
db_case_config=db_case_config,
case_config=CaseConfig(case_id=case_id, custom_case=custom_case or {}),
stages=stages or [TaskStage.DROP_OLD, TaskStage.LOAD, TaskStage.SEARCH_SERIAL],
insert_batch_size=insert_batch_size,
)
return CaseRunner(
run_id="run-id",
Expand Down Expand Up @@ -111,6 +116,13 @@ def test_reuse_key_preserves_safe_payload_reuse():
assert hash(ids_only) == hash(vector)


def test_reuse_key_distinguishes_insert_batch_size():
assert_not_reusable(
make_runner(insert_batch_size=100),
make_runner(insert_batch_size=200),
)


def test_reuse_key_distinguishes_physical_db_targets():
assert_not_reusable(
make_runner(db_config=TurboPufferConfig(api_key="key", region="aws-us-east-1", namespace="namespace_a")),
Expand Down
45 changes: 22 additions & 23 deletions tests/test_cloud_insert_case.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,13 +69,13 @@ def iter_batches(self, batch_size):


def test_cloud_insert_case_defaults_to_laion_100m():
case = CloudInsertCase(batch_size=1000)
case = CloudInsertCase()

assert case.case_id == CaseType.CloudInsertCase
assert case.label == CaseLabel.CloudInsert
assert case.dataset.data.name == "LAION"
assert case.dataset.data.size == 100_000_000
assert case.batch_size == 1000
assert not hasattr(case, "batch_size")
assert case.duration is None
assert case.readiness_timeout is None

Expand All @@ -84,14 +84,13 @@ def test_case_config_builds_cloud_insert_case_from_custom_case():
case = CaseConfig(
case_id=CaseType.CloudInsertCase,
custom_case={
"batch_size": 5000,
"duration": 1800,
"dataset_with_size_type": DatasetWithSizeType.CohereMedium.value,
},
).case

assert isinstance(case, CloudInsertCase)
assert case.batch_size == 5000
assert not hasattr(case, "batch_size")
assert case.duration == 1800
assert case.dataset.data.name == "Cohere"
assert case.dataset.data.size == 1_000_000
Expand All @@ -101,13 +100,12 @@ def test_case_config_builds_cloud_insert_case_from_laion_100m_dataset_option():
case = CaseConfig(
case_id=CaseType.CloudInsertCase,
custom_case={
"batch_size": 10_000,
"dataset_with_size_type": "Large LAION (768dim, 100M)",
},
).case

assert isinstance(case, CloudInsertCase)
assert case.batch_size == 10_000
assert not hasattr(case, "batch_size")
assert case.dataset_with_size_type == DatasetWithSizeType.LAIONLarge
assert case.dataset.data.name == "LAION"
assert case.dataset.data.size == 100_000_000
Expand All @@ -122,15 +120,13 @@ def test_laion_100m_dataset_option_uses_100m_timeouts():
def test_cli_builds_cloud_insert_custom_case_config():
params = {
"case_type": "CloudInsertCase",
"cloud_insert_batch_size": 10_000,
"cloud_insert_duration": 1800,
"cloud_insert_readiness_timeout": 7200,
"cloud_insert_readiness_poll_interval": 10,
"dataset_with_size_type": DatasetWithSizeType.CohereMedium.value,
}

assert get_custom_case_config(params) == {
"batch_size": 10_000,
"duration": 1800,
"readiness_timeout": 7200,
"readiness_poll_interval": 10,
Expand All @@ -142,7 +138,6 @@ def test_cli_builds_cloud_insert_custom_case_config_with_laion_100m_dataset():
cfg = get_custom_case_config(
{
"case_type": "CloudInsertCase",
"cloud_insert_batch_size": 10_000,
"cloud_insert_duration": None,
"cloud_insert_readiness_timeout": None,
"cloud_insert_readiness_poll_interval": None,
Expand All @@ -151,7 +146,6 @@ def test_cli_builds_cloud_insert_custom_case_config_with_laion_100m_dataset():
)

assert cfg == {
"batch_size": 10_000,
"duration": None,
"dataset_with_size_type": DatasetWithSizeType.LAIONLarge.value,
}
Expand All @@ -166,7 +160,6 @@ def test_cli_builds_cloud_insert_custom_case_config_with_default_dataset():
cfg = get_custom_case_config(
{
"case_type": "CloudInsertCase",
"cloud_insert_batch_size": 10_000,
"cloud_insert_duration": None,
"cloud_insert_readiness_timeout": None,
"cloud_insert_readiness_poll_interval": None,
Expand All @@ -175,7 +168,6 @@ def test_cli_builds_cloud_insert_custom_case_config_with_default_dataset():
)

assert cfg == {
"batch_size": 10_000,
"duration": None,
"dataset_with_size_type": DatasetWithSizeType.CohereMedium.value,
}
Expand Down Expand Up @@ -243,11 +235,11 @@ def test_assembler_schedules_cloud_insert_case():
case_config=CaseConfig(
case_id=CaseType.CloudInsertCase,
custom_case={
"batch_size": 1000,
"dataset_with_size_type": DatasetWithSizeType.CohereMedium.value,
},
),
stages=[TaskStage.DROP_OLD, TaskStage.LOAD],
insert_batch_size=1000,
)

runner = Assembler.assemble_all("run-id", "task-label", [task], DatasetSource.S3)
Expand Down Expand Up @@ -311,10 +303,11 @@ def test_cloud_insert_result_file_uses_insert_only_metrics(tmp_path: Path):
db_case_config=EmptyDBCaseConfig(),
case_config=CaseConfig(
case_id=CaseType.CloudInsertCase,
custom_case={"batch_size": 1000, "duration": None},
custom_case={"duration": None},
),
stages=[TaskStage.DROP_OLD, TaskStage.LOAD],
load_concurrency=0,
insert_batch_size=1000,
),
metrics=Metric(
inserted_count=100_000_000,
Expand Down Expand Up @@ -343,14 +336,16 @@ def test_cloud_insert_result_file_uses_insert_only_metrics(tmp_path: Path):
}
assert written["results"][0]["task_config"]["db_config"]["api_key"] == "**********"
assert written["results"][0]["task_config"]["db_config"]["index_name"] == "laion100m"
assert written["results"][0]["task_config"]["insert_batch_size"] == 1000
assert written["results"][0]["task_config"]["case_config"] == {
"case_id": 600,
"custom_case": {"batch_size": 1000, "duration": None},
"custom_case": {"duration": None},
}

read_back = TestResult.read_file(result_file)
assert read_back.results[0].task_config.case_config.case_id == CaseType.CloudInsertCase
assert read_back.results[0].task_config.case_config.custom_case == {"batch_size": 1000, "duration": None}
assert read_back.results[0].task_config.case_config.custom_case == {"duration": None}
assert read_back.results[0].task_config.insert_batch_size == 1000

collected = ResultCollector.collect(tmp_path)
assert len(collected) == 1
Expand Down Expand Up @@ -423,7 +418,7 @@ def write(self, **kwargs):
def test_milvus_insert_readiness_uses_entity_count_and_index_progress():
db = Milvus.__new__(Milvus)
db.collection_name = "c"
db._vector_index_name = "vector_idx"
db._main_index_name = "vector_idx"
db.client = type(
"Client",
(),
Expand Down Expand Up @@ -697,9 +692,9 @@ def poll_insert_readiness(self, expected_count):

db = DB()
monkeypatch.setattr("vectordb_bench.backend.task_runner.time.sleep", lambda _: None)
case = CloudInsertCase(batch_size=2)
case = CloudInsertCase()
case.dataset = Dataset()
config = type("Config", (), {"load_concurrency": 1})()
config = type("Config", (), {"load_concurrency": 1, "insert_batch_size": 2})()
runner = CaseRunner.construct(ca=case, db=db, config=config)

metric = runner._run_cloud_insert_case()
Expand Down Expand Up @@ -752,9 +747,13 @@ def fail_on_sleep(_seconds):

monkeypatch.setattr("vectordb_bench.backend.task_runner.ConcurrentInsertRunner", FakeConcurrentInsertRunner)
monkeypatch.setattr("vectordb_bench.backend.task_runner.time.sleep", fail_on_sleep)
case = CloudInsertCase(batch_size=1, readiness_timeout=0, readiness_poll_interval=0)
case = CloudInsertCase(readiness_timeout=0, readiness_poll_interval=0)
case.dataset = Dataset()
runner = CaseRunner.construct(ca=case, db=DB(), config=type("Config", (), {"load_concurrency": 1})())
runner = CaseRunner.construct(
ca=case,
db=DB(),
config=type("Config", (), {"load_concurrency": 1, "insert_batch_size": 1})(),
)

with pytest.raises(TimeoutError, match="fully_searchable.*last_status.*stalled"):
runner._run_cloud_insert_case()
Expand Down Expand Up @@ -798,9 +797,9 @@ def poll_insert_readiness(self, expected_count):
return {"fully_searchable": True, "fully_indexed": True, "additional_parameters": {}}

monkeypatch.setattr("vectordb_bench.backend.task_runner.ConcurrentInsertRunner", FakeConcurrentInsertRunner)
case = CloudInsertCase(batch_size=1000, duration=60)
case = CloudInsertCase(duration=60)
case.dataset = Dataset()
config = type("Config", (), {"load_concurrency": 7})()
config = type("Config", (), {"load_concurrency": 7, "insert_batch_size": 1000})()
runner = CaseRunner.construct(ca=case, db=DB(), config=config)

metric = runner._run_cloud_insert_case()
Expand Down
Loading
Loading