Hacktoberfest 2026: le issue che i maintainer hanno segnato per ottobre, aperte e adatte ai principianti. Sfoglia le issue Hacktoberfest

feat(kv): high-throughput bulk-insert path for the KV store (COPY-based)

Aperta
#184 0 commenti 0 reazioni 0 assegnatari Vedi su GitHub

Nessuno ha ancora preso questa issue.

Valutazione

Difficoltà
5/5
Tempo stimato
Più di una settimana
Idoneità per principianti
28/100
Tipo di issue
Funzionalità
Chiarezza
Da chiarire
Stato di attività
Tranquilla
Stack tecnologico
rust, sql

Direzione di ricerca

Start with hyperdb-api/src/kv_store.rs:187-208 and :398-416, then read inserter.rs:135-139 and :192 to understand the existing write and COPY paths. The issue is ready only after a concrete use case selects and scopes one bulk-loading option, including its TCP-only behavior and correctness guarantees.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Descrizione

enhancement

Summary

The KV store's write path (set / set_batch) tops out around 1,000–2,000 inserts/sec because every key goes through an UPDATE-then-conditional-INSERT upsert over the extended-query (text SQL) protocol. For workloads that need to load millions of new KV entries quickly, this is the bottleneck. The repo already has a binary-COPY Inserter that hits 22M+ rows/sec, so a bulk-insert path for the KV store is feasible — but it interacts with a correctness constraint that shapes the whole design.

This issue captures the design so it can be prioritized if real use cases surface. Not scheduled work today.

Background: why set_batch is slow

  • set performs a manual upsert: it always issues UPDATE {t} SET value = $3 WHERE store_name = $1 AND key = $2, and only when the UPDATE affects 0 rows issues a guarded INSERT INTO {t} (store_name, key, value) SELECT $1, $2, $3 WHERE NOT EXISTS (SELECT 1 FROM {t} WHERE store_name = $4 AND key = $5) (hyperdb-api/src/kv_store.rs:187-208). So a new key costs 2 round-trips.
  • set_batch wraps all per-key upserts in one BEGIN/COMMIT (kv_store.rs:398-416), which helps, but it's still 1–2 text-SQL statements per key. That per-row overhead is the ceiling.

The correctness constraint (this is the crux)

The backing table _hyperdb_kv_store(store_name TEXT NOT NULL, key TEXT NOT NULL, value TEXT) has no PRIMARY KEY, no UNIQUE, no index — Hyper rejects all of them (0A000: Index support is disabled). Uniqueness of (store_name, key) is therefore an application-side invariant, enforced only by the UPDATE-then-conditional-INSERT ... WHERE NOT EXISTS guard (kv_store.rs:62-75 DDL, :187-208 guard).

A raw binary-COPY bypasses that guard entirely (COPY is pure append-only — no UPDATE, no conflict handling). If duplicate (store_name, key) rows ever land in the table, the read side silently returns wrong results:

Method Behavior under duplicate keys
get / get_as returns one arbitrary value (no LIMIT/ORDER BY/aggregate)
size() inflatedCOUNT(*) counts physical rows, not distinct keys
keys() returns duplicate keys (no DISTINCT)
pop() returns one value, but its DELETE removes all duplicate rows for that key — silently drops the others
exists() still correct (only checks for ≥1 row)

So the design question is not throughput — it's who guarantees the keys are new and unique. Also note: COPY is TCP-only (inserter.rs:135-139), not available over gRPC.

Proposed options

The two options below are complementary, not mutually exclusive — a real use case can decide which to prioritize (or both).

Option A — load_new: maximally fast, caller guarantees uniqueness

Add an append-only bulk method that writes straight through the Inserter. Inserter::from_table(conn, KV_TABLE) (inserter.rs:192) picks up the existing 3-column schema from the catalog — no new DDL, no schema change — and add_str covers all three TEXT columns.

/// Appends entries via the binary COPY path (orders of magnitude faster than `set_batch`).
///
/// **Caller contract:** every key MUST be new and unique within this store — both
/// absent from the store already AND distinct within `entries`. This path does NO
/// upsert and NO duplicate check; violating the contract inserts duplicate rows,
/// which silently corrupt `get`/`size`/`keys`/`pop`. Use `set_batch` if keys may collide.
pub fn load_new(&self, entries: &[(&str, &str)]) -> Result<u64>
  • Internally: Inserter::from_table → per entry add_str(store_name); add_str(key); add_str(value); end_row()execute().
  • Name deliberately signals append-only / new-keys-only semantics at the call site (not set_batch_fast).
  • Sync + async twins (async via AsyncArrowInserter / the async COPY path).
  • Trade-off: fastest possible, but a contract violation is silent corruption — squarely the caller's responsibility.
Option B — safe bulk merge: fast, dedup-preserving, no caller contract

Keep the uniqueness invariant intact by staging + merging:

  1. COPY the batch into a temporary staging table via the Inserter.
  2. INSERT INTO _hyperdb_kv_store (store_name, key, value) SELECT ... FROM stage s WHERE NOT EXISTS (SELECT 1 FROM _hyperdb_kv_store t WHERE t.store_name = s.store_name AND t.key = s.key) — and dedup within the staging batch itself (e.g. DISTINCT ON).
  • Trade-off: one extra pass so not raw-COPY speed, but far faster than millions of per-key upserts, and it cannot create duplicates. A good default helper for callers who can't promise clean keys.
  • Could be surfaced as a helper API alongside load_new.

Recommendation / decision guidance

  • One-shot bulk load of millions of unique rows you'll query with SQL → arguably skip the KV store entirely: create a normal typed table and use the Inserter directly. Full COPY throughput, real column types, no store_name overhead, and no uniqueness trap (you're not pretending it's a KV store). The KV store's value is the handle semantics (get/set/pop/exists over a named store) — a bulk analytical dump uses none of that.
  • Data must remain a live KV store you'll get/pop against afterwardload_new (Option A) if the caller can guarantee new/unique keys, or the safe merge (Option B) otherwise.

Scope notes

  • No schema change and no change to existing methods required for either option.
  • TCP-only (COPY constraint) — document it; gRPC connections can't use this path.
  • Natural fit for a post-M2 milestone; prioritize when concrete use cases appear.

Filed from an investigation of the KV write path, the COPY Inserter API, and the KV read-side invariants. File:line references are against main at time of filing.

Lingua principale
Rust
Stelle
2
Fork
2
Merge medio
12h 2m
PR unite (30g)
60

Guida per i contributori

Apri la guida per i contributori

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Altre issue di tableau/hyper-api-rust

Tutte le issue di tableau/hyper-api-rust

Issue simili

Altre issue su Rust

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.