alibaba / alibaba/zvec

Higher optimize latency on Windows than Linux

Open
#743 0 comments 1 reaction 2 assignees Claimed by @JalinWang View on GitHub
Dominant language
C++
Stars
15.9k
Forks
998
Avg merge
4d 7h
Merged PRs (30d)
34

Description

## Problem

We observed higher optimization latency on Windows than Linux through the `@zvec/zvec@0.7.0` Node.js bindings. A minimal collection with one indexed string field and one document takes a median 191.83 ms to optimize on Windows versus 26.03 ms on Linux.

This also affects a real indexing workflow in zvec-grep: four optimization calls total 4.77 s on Windows versus 1.61 s on Linux, accounting for approximately 46% of the Windows workflow's 10.30 s runtime. Setting optimization concurrency to 1 does not improve the Windows results.

The measurements isolate the latency to the native API boundary. We have not yet identified whether the underlying cause is in the core implementation, bindings, filesystem operations, or runner environment.

## Environment

Both platforms ran the same commit and lockfile on GitHub-hosted runners, with Node.js 24.20.0, `@zvec/zvec` 0.7.0, and the corresponding prebuilt x64 bindings.

| | Linux | Windows |
| --- | --- | --- |
| Runner | `ubuntu-latest` | `windows-latest` |
| OS release reported by Node | `6.17.0-1022-azure` | `10.0.26100` |
| Runner image version | `20260907.300.1` | `20260907.229.1` |
| Reported CPU model | AMD EPYC 9V74 | AMD EPYC 9V74 |
| Logical CPUs | 4 | 4 |
| RAM | ~16 GiB | ~16 GiB |

The CPU model matches in this run, but these are separate VMs; storage and host load are not controlled. These results do not establish an OS-only cause or a regression from an earlier version.

## Minimal reproduction

1. In a scratch directory, install `npm install --save-exact @zvec/zvec@0.7.0`.
2. Save the [complete measured reproduction script](https://github.com/zvec-ai/zvec-grep/blob/f02d60d8f5ad5184cf153feaa614ef3fc359a925/.github/scripts/measure-zvec-minimal.mjs) as `measure-zvec-minimal.mjs` in that directory.
3. Run `node measure-zvec-minimal.mjs` on Windows and Linux with Node 24.20.0. Measurements are printed and saved to `minimal-results.jsonl`.

The script creates a fresh collection for each measurement, with this schema and operation sequence:

```js
const schema = new ZVecCollectionSchema({
name: "minimal",
fields: [{
name: "file_id",
dataType: ZVecDataType.STRING,
nullable: false,
indexParams: { indexType: ZVecIndexType.INVERT },
}],
});
const collection = ZVecCreateAndOpen(collectionPath, schema);
collection.upsertSync({ id: "one", fields: { file_id: "alpha.ts" } });

const start = performance.now();
collection.optimizeSync({ concurrency }); // Test 0 (automatic) and 1.
const optimizeMs = performance.now() - start;

assert.equal(collection.fetchSync({ ids: ["one"] }).one.fields.file_id, "alpha.ts");
collection.closeSync();
```

The full script measures create, upsert, optimize, fetch, close, and cleanup separately. It runs 5 iterations per concurrency setting in one process, alternating the configuration order and using a new temporary directory each time. Module loading and collection creation are outside the optimize timer. Logging occurs after the measured operations. No vector field, embedding model, document parsing, service layer, or network request is involved.

## Results

All values below are medians across 5 measurements.

### Minimal native collection

| Operation | Linux, concurrency=0 | Windows, concurrency=0 | Linux, concurrency=1 | Windows, concurrency=1 |
| --- | ---: | ---: | ---: | ---: |
| optimizeSync | 26.03 ms | 191.83 ms | 26.30 ms | 198.80 ms |
| upsertSync | 0.14 ms | 0.65 ms | 0.14 ms | 0.69 ms |
| fetchSync | 0.10 ms | 0.18 ms | 0.08 ms | 0.14 ms |
| closeSync | 0.50 ms | 15.53 ms | 0.48 ms | 17.72 ms |

### Application impact

A separate service-level reproduction performs initial indexing, search, automatic refresh after a file change, and index deletion. It uses a deterministic fake embedding model, retains result assertions, and runs each of its 5 measurements in a fresh Node process with a fresh directory.

| Measurement | Linux default | Windows default | Linux concurrency=1 | Windows concurrency=1 |
| --- | ---: | ---: | ---: | ---: |
| Complete workflow, excluding imports | 4.885 s | 10.303 s | 4.212 s | 10.324 s |
| Four optimize calls combined | 1.611 s | 4.769 s | 1.779 s | 4.777 s |

These four calls consist of two async optimizations on the content collection and two synchronous optimizations on the file-metadata collection. The application explicitly requests metadata optimization before closing a modified collection; this is not a claim that the native close API implicitly optimizes.

Application groups ran in default-then-single-thread order, so the Linux total-time difference is not a controlled estimate of a concurrency benefit. Optimization latency did not improve on either platform. The minimal experiment alternates order and likewise shows no benefit.

An earlier run on different CPU models showed the same direction: four optimizations totaled 5.952 s on Windows versus 1.067 s on Linux. The ratio varies by runner, but optimization remains a substantial cost.

## Evidence and investigation request

- [Main measurement run](https://github.com/zvec-ai/zvec-grep/actions/runs/34445586331): both platform jobs passed.
- [Earlier measurement run](https://github.com/zvec-ai/zvec-grep/actions/runs/34445244022): both platform jobs passed.
- [Workflow at the measured commit](https://github.com/zvec-ai/zvec-grep/blob/f02d60d8f5ad5184cf153feaa614ef3fc359a925/.github/workflows/ci.yml).
- [Service-level instrumentation](https://github.com/zvec-ai/zvec-grep/blob/f02d60d8f5ad5184cf153feaa614ef3fc359a925/.github/scripts/measure-ci-phases.mjs).
- The main run's `phases-ubuntu-latest` and `phases-windows-latest` artifacts contain `minimal-results.jsonl`, `phase-results.jsonl`, and `single-thread-results.jsonl`, including individual wall-clock and process CPU timings.

No native API errors or application-level open retries were recorded in the service reproduction. Cleanup is measured separately and does not account for the optimization timings.

Is this level of optimization overhead expected for a one-document scalar collection on Windows? Could we profile the native optimize path to distinguish index construction, file synchronization, and thread/wait overhead, and determine whether the Windows path can be improved? We would also appreciate guidance on safe optimization frequency for small, frequently updated metadata collections.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.