Skip to content

feat(minidb): switch agent-core-v2 query-store to ClusterDb with 16 shards - #1907

Merged
sailist merged 1 commit into
MoonshotAI:mainfrom
sailist:feat/minidb-cluster-query-store
Jul 19, 2026
Merged

feat(minidb): switch agent-core-v2 query-store to ClusterDb with 16 shards#1907
sailist merged 1 commit into
MoonshotAI:mainfrom
sailist:feat/minidb-cluster-query-store

Conversation

@sailist

@sailist sailist commented Jul 19, 2026

Copy link
Copy Markdown
Collaborator

Related Issue

No linked issue — the problem is explained in the next section.

Problem

The minidb-backed derived read model (IQueryStore, behind the experimental persistence_minidb_readmodel flag) currently runs on a single MiniDb with a database-wide single-writer lock. Any second kimi process sharing the same home fails with storage.locked, and the read model is then permanently disabled for that process (falling back to legacy N+1 disk reads).

minidb already shipped ClusterDb — hash-routed shards over ordinary MiniDb directories with a per-shard lock pool, built for exactly this multi-process scenario — but its v1 cut left out the two pieces the query-store backend needs: the unified query() and compound index management. This PR completes those pieces and moves the query-store onto a 16-shard ClusterDb.

What changed

1. minidb: complete the ClusterDb API needed by the query-store (pure additions, no behavior change)

Problem: ClusterDb had no query() and no compound index management, so the backend could not migrate.

What was done:

  • query(): every shard runs the full MiniDb.query locally with skip=0 and limit=skip+limit (index-assisted candidate pruning included — the global top-(skip+limit) is contained in each shard's local top-(skip+limit)); merged results are re-sorted globally (explicit sort, else key bytes to match scan) and skip/limit applies at the end. Text scoring stays per-shard (approximate global ranking), the same caveat search() already documents.
  • Compound index management: createCompoundIndex / dropCompoundIndex / listCompoundIndexes, fanned out to every shard via the cluster registry with rollback on partial fan-out and catch-up on shard open. cluster.indexes.json gains a compoundIndexes field, normalized with ?? [] on both read paths so old and new files load either way.

2. agent-core-v2: MiniDbQueryStore now runs on a 16-shard ClusterDb

Problem: the single-MiniDb backend locked out every concurrent process via storage.locked.

What was done:

  • Open a ClusterDb (shardCount: 16) at the same <cacheDir>/query-store location with the same codec/durability settings; lockAcquireTimeoutMs lowered to 1s (a cache read must not hang behind a contended shard).
  • Multi-process coexistence replaces the single-writer lockout: peers share the read model, and per-shard LockError propagates as a transient error instead of permanently disabling the read model through storage.locked (the FileSessionIndex fallback wiring stays as defense in depth).
  • Corruption handling lifts openOrRebuild's predicate (SyntaxError / CorruptFrameError) to the cluster: one process-lifetime wipe-and-reopen rebuild with a single retry of the triggering op; consumers reproject from checkpoints. batch is atomic per shard and best-effort across shards (the ClusterDb default).
  • Tests: cluster query merge correctness (global top-k with all winners concentrated on one shard, key-byte order without sort), compound fan-out and reopen round-trip; store coexistence with a peer instance, corrupt-registry rebuild self-heal, 16-shard topology assertion; the sessionIndex locked-fallback test now drives a stub IQueryStore since the backend no longer produces storage.locked.

The feature remains gated behind the experimental persistence_minidb_readmodel flag (default off). No changeset: internal packages only (@moonshot-ai/minidb is private, agent-core-v2 is not published), per maintainer request.

Checklist

  • I have read the CONTRIBUTING document.
  • I have linked a related issue, or explained the problem above.
  • I have added tests that prove my feature works.
  • Ran gen-changesets skill, or this PR needs no changeset.
  • Ran gen-docs skill, or this PR needs no doc update.

…hards

- ClusterDb: add query() (per-shard fan-out with skip=0 and
  limit=skip+limit, global re-sort, then skip/limit) and compound index
  management (create/drop/list through the cluster registry, fanned out
  to every shard and caught up on shard open)
- MiniDbQueryStore: replace the single MiniDb with a 16-shard ClusterDb,
  so multiple kimi processes share the read model instead of failing
  with storage.locked; per-shard LockError now propagates as a transient
  error rather than permanently disabling the read model
- corruption: lift openOrRebuild's predicate (SyntaxError /
  CorruptFrameError) to one process-lifetime wipe-and-reopen rebuild
- lower lockAcquireTimeoutMs to 1s for the cache read model
- tests: cluster query merge and compound fan-out; store coexistence
  with a peer instance, corrupt-registry rebuild, 16-shard topology;
  sessionIndex locked-fallback test now drives a stub IQueryStore
@changeset-bot

changeset-bot Bot commented Jul 19, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: df6c020

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@pkg-pr-new

pkg-pr-new Bot commented Jul 19, 2026

Copy link
Copy Markdown
pnpm dlx https://pkg.pr.new/@moonshot-ai/kimi-code@df6c020
npx https://pkg.pr.new/@moonshot-ai/kimi-code@df6c020

commit: df6c020

@sailist
sailist merged commit 8b9916c into MoonshotAI:main Jul 19, 2026
15 checks passed

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: df6c020179

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

const needed = skip + limit;
const all: ScanEntry<V>[] = [];
for (const id of this.router.shardIds()) {
const rows = await this.reader(id, (db) => db.query({ ...q, skip: 0, limit: needed }));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Apply projection after the global query merge

When a caller combines sort with a project that omits the sort field, this forwards project into every shard query. MiniDb.query projects after its local sort, so the rows returned to the cluster no longer contain the field needed by the global merge comparator; query({ sort: { age: -1 }, project: ['name'], limit: 10 }) can therefore return shard-order results instead of the highest ages when matches span shards. Fan out without project, then apply projection after the global sort and slice.

Useful? React with 👍 / 👎.

Comment on lines +289 to +290
const av = getPath(a.value, p) as number | string;
const bv = getPath(b.value, p) as number | string;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Sort dt-backed query merges by entry metadata

For MiniDb.query, sorting on the same column supplied in q.dt is supported by the dt-ordered fast path, and the column is carried on ScanEntry.dt rather than inside value. This merge comparator only reads a.value, so ClusterDb.query({ dt: { ts: ... }, sort: { ts: -1 }, limit: ... }) compares every row as equal when documents do not duplicate ts in their JSON value, then slices in shard order. Use entry.dt?.[p] for dt-backed sort keys or merge the per-shard dt order explicitly.

Useful? React with 👍 / 👎.

Comment on lines +296 to +297
} else {
all.sort(compareEntries);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve text query ranking before applying limit

When q.text is set without an explicit sort, MiniDb.query returns each shard's matches in text-rank order, but this no-sort branch reorders all merged matches by key bytes before the final limit. For ClusterDb.query({ text: ..., limit: n }), lexicographically early but less relevant keys can displace more relevant hits from other shards, so the public text-query semantics diverge from MiniDb even before considering approximate cross-shard IDF. Carry/merge text ranks, or only fall back to key-byte ordering when there is no text ranking.

Useful? React with 👍 / 👎.

7723qqq added a commit to 7723qqq/kimi-code that referenced this pull request Jul 20, 2026
Upstream changes:
- feat(cli): replace kimi server command tree with kimi web (MoonshotAI#1826)
  - Removed daemon/server lifecycle (svc/ layer for launchd/systemd/schtasks)
  - Multi-server instance registry replaces single-writer lock
  - /web TUI command uses server picker instead of daemon logic
- feat(minidb): switch agent-core-v2 query-store to ClusterDb with 16 shards (MoonshotAI#1907)
  - Multiple kimi processes can share the read model concurrently
- feat: scope thinking effort to current session (MoonshotAI#1933)
- feat: add thinking_effort to turn telemetry events (MoonshotAI#1916)
- fix(agent-core-v2): host fs stat follows symlinks, add explicit lstat (MoonshotAI#1843)
- feat(web): add cache invalidation note to model switcher (MoonshotAI#1940)
- ci: release packages (MoonshotAI#1868)

Conflicts resolved:
- 9 modify/delete conflicts: accepted upstream deletion (server/*, svc/*)
- 6 content conflicts: merged while preserving fork's i18n t() calls
  and fork-specific test suites (config edge cases)
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.

1 participant