Skip to content

feat: add use_row_insert to add_texts; single insert for one-document calls by default - #17

Merged
minseokpark-CL merged 8 commits into
mainfrom
feat/row-insert-option
Sep 23, 2026
Merged

minseokpark-CL merged 8 commits into
mainfrom
feat/row-insert-option

Conversation

@minseokpark-CL

@minseokpark-CL minseokpark-CL commented Sep 21, 2026 •

Copy link
Copy Markdown
Contributor

add_texts 에 use_row_insert 인자 추가

(English below.)

무엇이 달라지나

  • add_texts / add_documents 에 use_row_insert 인자가 생긴다. True 면 EnVector 의 single
    insert, False 면 batch insert 로 넣는다. 지금까지는 **kwargs 로만 전달됐다.
  • 기본값(None): 호출에 문서가 1개면 single insert, 2개 이상이면 batch insert.
  • WriteSettings.use_row_insert 로 store 전체의 기본을 정할 수 있고, 호출 인자가 그보다 우선한다.
  • ids= 를 준 호출에서 살아 있는 row 를 못 찾아 다시 insert 하는 경우에도 같은 규칙을 따른다.
  • README 의 Limitations 한 줄과 add-documents 예제 옆 안내, docstring 에 위 내용을 적었다.

검증

  • python -m pytest tests -m "not integration" — 95 passed. 새 테스트: 기본 규칙(1개 single, 2개
    이상 batch), store 설정과 호출 override, add_documents 경유 전달, ids= 재삽입 경로.
  • pre-commit 은 이 기계에서 못 돌렸다 (black hook 이 python3.11 을 요구, venv 는 3.10). CI 통과.
  • 기본 동작이 바뀌므로 버전(0.3.0)을 올려야 할 것 같다. 이 PR 에서는 건드리지 않았다.

Add use_row_insert to add_texts

What changes

  • add_texts / add_documents gain a use_row_insert argument: True inserts with EnVector's
    single insert, False with batch insert. Until now it only reached the SDK through **kwargs.
  • Default (None): a call with one document uses single insert, a call with two or more uses
    batch insert.
  • WriteSettings.use_row_insert sets a store-wide default; the call argument overrides it.
  • The re-insert inside an ids= call (for ids that matched no live row) follows the same rule.
  • Documented in the README Limitations line, the add-documents example and the docstrings.

Verification

  • python -m pytest tests -m "not integration" — 95 passed. New tests: the default rule (one
    document single, two or more batch), the store setting and the per-call override, forwarding
    through add_documents, the ids= re-insert arm.
  • pre-commit could not run on this machine (its black hook pins python3.11, this venv is 3.10);
    CI passes.
  • The default behaviour changes, so a version bump from 0.3.0 seems due; not done in this PR.

🤖 Generated with Claude Code

minseokpark-CL and others added 5 commits September 18, 2026 14:31
EnVector has two insert paths and the row one is the cheaper choice when a
call carries only a few documents, which is what `add_texts` is for in a RAG
pipeline: documents arriving a handful at a time into an index that already
holds data. Until now the flag only reached the SDK by slipping through
`**kwargs` — undiscoverable, untyped and undocumented.

It is now a named argument, defaulting to the bulk path as before. EnVector
applies the row path only to calls carrying fewer rows than the index
dimension and silently inserts in bulk otherwise, so asking for it above that
raises a UserWarning instead of leaving the caller believing it happened. The
flag also survives the re-insert inside the `ids=` upsert arm.

Which path is faster at which size is measured separately and documented in
docs/insert-modes.md.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Adds docs/insert-modes.md with the boundary, a README pointer, and a
correction to the add_texts docstring.

Measured against an index already holding 8192 documents, six dimensions from
256 to 1536, calls of 1 to 32 documents, three repetitions each, every call's
merge allowed to finish before the next:

- The bulk path is flat in the number of documents. Across ten sizes each
  dimension stayed inside a 0.4s band.
- The row path is never ahead on the thing callers notice. At one document it
  is within ~10% of bulk; at two it is 1.5x slower; at eight, 5x. Documents
  become searchable a fraction of a second after either call returns, so the
  same holds there.
- It also writes about twice as much to storage per call, at every size and
  dimension.
- What it does buy is a faster merge after a very small call: 2x at one
  document at dim 256, shrinking with dimension until at 1536 there is no size
  where it helps. That matters only when something waits for the merge, which
  is await_completion=True or an update/upsert soon after the insert.

So the recommendation is to leave it off, with a per-dimension table for the
case where the merge is on the critical path.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The timings in insert-modes.md were taken with the client on the same machine
as the server, which hides the one place the two paths differ by a lot. The
bulk path uploads a block sized by the index dimension and not by the number
of documents: 31.5 MB at dim 1024, to add one document or a thousand. The row
path uploads 61.5 KB per document, a factor of 500 at one document.

Adding transfer time to the measured timings moves the boundary with the link
and barely with the dimension: bulk everywhere at 1 Gbps or on the same host,
row up to 1-2 documents at 100 Mbps, 2-3 at 50, 9-12 at 10.

Also answers the question that prompted this, which the page did not address:
adding a few documents at a time does not leave the index fragmented. After 45
small calls on an index of 8192 documents, every dimension held three shards of
4096, 4096 and 402 - the small inserts are folded into the last partly-filled
shard. A shard's fixed cost is paid once per 4096 documents, not per call, and
the merged result is byte-identical on both paths.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Requested by the enVector team: their concern is what an insert sends over
the network, more than how long it takes. The bulk path uploads one block
sized by the index dimension no matter how many documents the call carries
(31.5 MB at dim 1024, to add one document or a thousand); the row path uploads
about 60 KB per document. So calls of fewer documents than the dimension now
take the row path, and calls at or above it take bulk.

The decision is made on the whole call, not per 4096-row encryption chunk as
the SDK does it, so a large call whose last chunk happens to be short never
sends that tail on the row path (~2 s per row server-side; a 900-row tail
would be half an hour). WriteSettings.use_row_insert turns the default off
for a store whose client sits next to the server and cares about latency;
add_texts(use_row_insert=...) overrides per call either way. Asking for the
row path explicitly at or above dim still warns; the default falling back to
bulk there is the rule, so it does not.

The cost is latency and it is documented, not hidden: about 2 s of server
time per document at dim 1024, so 8 documents take 19 s where bulk took 4 s.
Documents are searchable right after the call returns on both paths.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…page

Findings from an independent review of the branch, and the user-facing text
brought back to what the reader has to do.

- docs/insert-modes.md is gone. It opened by calling bulk the default and row
  opt-in, the opposite of what the last commit made true, and the rest of it
  was a measurement report on the row path's cost — a working note, not user
  documentation. What a user needs is the rule and the switch, and those are
  in the README line, the add_texts docstring and WriteSettings. The numbers
  stay in the internal notes.
- README and docstrings no longer carry measurements (31.5 MB, 2 s per row):
  they say the row path sends less and takes longer per document, and how to
  turn it off.
- add_texts' docstring states that id-less rows in an ids= call are inserted
  by Index.upsert, which offers no choice of path, so use_row_insert governs
  only its re-insert of ids that matched no live row. It used to claim False
  forces bulk for the whole call.
- The explicit-True warning is emitted before the insert runs, so it no
  longer says the rows "were inserted"; they "go on the bulk path instead".
- Tests: the default is checked strictly above dim, not only at it, and
  use_row_insert is checked through add_documents, the API the README shows.

Left as found: the review's first finding is that the default itself makes a
call of hundreds of documents below dim take minutes rather than seconds.
That is the traffic-first trade the enVector team asked for and it is stated
in the PR; it is a decision for the reviewers, not a defect to patch here.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Comment thread README.md Outdated
minseokpark-CL and others added 3 commits September 21, 2026 18:05
The line now states the behaviour the way the review put it — single insert
below the index dimension, batch insert from there up — and keeps only the
switch to always use batch insert. The SDK's own default is batch insert and
needs use_row_insert=True to take single insert below dim; this integration
passes that True itself, which is why the README describes the result rather
than the flag.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The previous commits compared the call's document count with the index
dimension here and passed the SDK a decided bool, so that a large call's
short last encryption chunk would never take the row path. That was this
integration's own rule layered on the SDK's, and the ask was to behave the
same as the SDK. Now the flag goes through as given: None means
WriteSettings.use_row_insert (on), an explicit bool is passed as is, and the
SDK applies its per-chunk rule from there. The warning for an explicit True
at or above dim goes with it, since the wrapper no longer looks at dim.

Whether the SDK's per-chunk decision is intended for a call that spans more
than one chunk is a question for the SDK, raised separately.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…therwise

add_texts picks the insert path per call when nothing is specified: a call
with one document uses EnVector's single insert, a call with two or more
uses batch insert. WriteSettings.use_row_insert (now Optional, default None)
sets a store-wide value instead, and the add_texts argument overrides both.

Docs and docstrings state the argument, what each value does, and the
default rule; nothing else.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@minseokpark-CL minseokpark-CL changed the title feat: expose use_row_insert on add_texts, and take the row path by default below dim feat: add use_row_insert to add_texts; single insert for one-document calls by default Sep 23, 2026
@minseokpark-CL
minseokpark-CL merged commit 0d542c1 into main Sep 23, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants