Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ cargo build --release --features vector
| command | description |
|---------|-------------|
| `VADD key element f32 [f32 ...] [METRIC COSINE\|L2\|IP] [QUANT F32\|F16\|Q8] [M n] [EF n]` | add a vector to the set |
| `VADD_BATCH key DIM n elem1 f32... elem2 f32... [METRIC\|QUANT\|M\|EF]` | add multiple vectors in one command |
| `VSIM key f32 [f32 ...] COUNT k [EF n] [WITHSCORES]` | k nearest neighbors |
| `VREM key element` | remove a vector |
| `VGET key element` | retrieve stored vector values |
Expand Down Expand Up @@ -303,7 +304,7 @@ contributions welcome — see [CONTRIBUTING.md](CONTRIBUTING.md).
| 4 | clustering (raft, gossip, slots, migration) | ✅ complete |
| 5 | developer experience (observability, CLI, clients) | 🚧 in progress |

**current**: 106 commands, 796+ tests, ~31k lines of code (excluding tests)
**current**: 107 commands, 796+ tests, ~31k lines of code (excluding tests)

## security

Expand Down
14 changes: 7 additions & 7 deletions bench/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ tested on GCP c2-standard-8 (8 vCPU Intel Xeon @ 3.10GHz), Ubuntu 22.04.

dragonfly in particular offers features ember simply doesn't have:

- full Redis API compatibility (200+ commands vs ember's ~106)
- full Redis API compatibility (200+ commands vs ember's ~107)
- sophisticated memory management (dashtable for ~25% of Redis memory usage)
- transactional semantics (MULTI/EXEC, Lua scripting)
- fork-free snapshotting
Expand Down Expand Up @@ -121,13 +121,13 @@ HNSW index: M=16, ef_construction=64 for all systems. tested on GCP c2-standard-

| metric | ember (RESP) | ember (gRPC) | chromadb | pgvector | qdrant |
|--------|-------------|-------------|----------|----------|--------|
| insert (vectors/sec) | 963 | 1,009 | **3,891** | 1,617 | **7,747** |
| query (queries/sec) | 1,264 | **1,462** | 390 | 831 | 596 |
| query p50 (ms) | 0.79ms | **0.68ms** | 2.56ms | 1.18ms | 1.67ms |
| query p99 (ms) | 0.93ms | **0.83ms** | 2.76ms | 1.56ms | 1.88ms |
| memory (MB) | **36 MB** | — | 122 MB | 179 MB | 121 MB |
| insert (vectors/sec) | 1,483 | 2,374 | 3,679 | 1,513 | **7,382** |
| query (queries/sec) | 1,212 | **1,452** | 383 | 843 | 589 |
| query p50 (ms) | 0.82ms | **0.68ms** | 2.60ms | 1.16ms | 1.69ms |
| query p99 (ms) | 1.00ms | **0.86ms** | 2.82ms | 1.61ms | 1.93ms |
| memory (MB) | **38 MB** | — | 123 MB | 179 MB | 122 MB |

ember's query throughput is 3.2x chromadb, 1.5x pgvector, and 2.1x qdrant, with 3-5x lower memory usage. gRPC queries are 16% faster than RESP due to lower serialization overhead. insert throughput is lower due to per-vector protocol overhead — qdrant's batch API gives it a significant edge on ingestion.
ember's query throughput is 3.2x chromadb, 1.4x pgvector, and 2.1x qdrant, with 3-5x lower memory usage. gRPC queries are 20% faster than RESP due to lower serialization overhead. insert throughput uses VADD_BATCH (batches of 500 vectors per command) — the gRPC path benefits most since packed floats avoid string parsing entirely.

#### SIFT1M recall accuracy (128-dim, 1M vectors, 10k queries)

Expand Down
24 changes: 14 additions & 10 deletions bench/bench-memory.sh
Original file line number Diff line number Diff line change
Expand Up @@ -326,18 +326,22 @@ dim = int(sys.argv[3])

r = redis.Redis(host="127.0.0.1", port=port, decode_responses=True)

for i in range(count):
vec = [random.gauss(0, 1) for _ in range(dim)]
norm = sum(v * v for v in vec) ** 0.5
vec = [v / norm for v in vec]

args = ["VADD", "vectors", f"v{i}"] + [str(v) for v in vec]
if i == 0:
batch_size = 500
for start in range(0, count, batch_size):
end = min(start + batch_size, count)
args = ["vectors", "DIM", str(dim)]
for i in range(start, end):
vec = [random.gauss(0, 1) for _ in range(dim)]
norm = sum(v * v for v in vec) ** 0.5
vec = [v / norm for v in vec]
args.append(f"v{i}")
args.extend(str(v) for v in vec)
if start == 0:
args += ["METRIC", "COSINE"]
r.execute_command(*args)
r.execute_command("VADD_BATCH", *args)

if (i + 1) % 10000 == 0:
print(f" inserted {i + 1}/{count} vectors", file=sys.stderr)
if end % 10000 == 0 or end == count:
print(f" inserted {end}/{count} vectors", file=sys.stderr)
PYEOF

# vector requires sharded mode
Expand Down
18 changes: 8 additions & 10 deletions bench/bench-vector.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,14 +75,13 @@ def setup(self, dim: int, metric: str = "cosine"):
self.conn.delete(self.key)

def insert_batch(self, ids: list, vectors: np.ndarray):
pipe = self.conn.pipeline(transaction=False)
dim = vectors.shape[1]
args = [self.key, "DIM", str(dim)]
for i, vid in enumerate(ids):
vec = vectors[i]
# VADD key element v1 v2 ... METRIC COSINE M 16 EF 64
args = [self.key, vid] + [str(float(v)) for v in vec]
args += ["METRIC", "COSINE", "M", "16", "EF", "64"]
pipe.execute_command("VADD", *args)
pipe.execute()
args.append(vid)
args.extend(str(float(v)) for v in vectors[i])
args += ["METRIC", "COSINE", "M", "16", "EF", "64"]
self.conn.execute_command("VADD_BATCH", *args)

def query(self, vector: np.ndarray, k: int) -> list:
args = [self.key] + [str(float(v)) for v in vector]
Expand Down Expand Up @@ -116,9 +115,8 @@ def setup(self, dim: int, metric: str = "cosine"):
self.client.flushdb()

def insert_batch(self, ids: list, vectors: np.ndarray):
for i, vid in enumerate(ids):
vec = vectors[i].tolist()
self.client.vadd(self.key, vid, vec, metric="cosine", m=16, ef=64)
entries = [(vid, vectors[i].tolist()) for i, vid in enumerate(ids)]
self.client.vadd_batch(self.key, entries, metric="cosine", m=16, ef=64)

def query(self, vector: np.ndarray, k: int) -> list:
results = self.client.vsim(self.key, vector.tolist(), count=k)
Expand Down
4 changes: 2 additions & 2 deletions bench/bench-vector.sh
Original file line number Diff line number Diff line change
Expand Up @@ -242,10 +242,10 @@ elif [[ "$QDRANT" == "true" ]] && ! python3 -c "import qdrant_client" 2>/dev/nul
pip install --quiet $REQUIRED_DEPS
fi

# install ember-py for gRPC benchmarks
# install ember-py for gRPC benchmarks (also install base deps into venv)
if [[ "$EMBER_GRPC" == "true" ]]; then
ensure_venv
pip install --quiet ./clients/ember-py
pip install --quiet $REQUIRED_DEPS ./clients/ember-py
fi

# build ember if needed
Expand Down
Loading
Loading