ydb-qdrant 4.8.0 → 4.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/repositories/collectionsRepo.js +3 -2
- package/dist/repositories/collectionsRepo.multi-table.js +3 -2
- package/dist/repositories/collectionsRepo.one-table.js +83 -6
- package/dist/repositories/pointsRepo.multi-table.js +9 -6
- package/dist/repositories/pointsRepo.one-table.js +9 -5
- package/dist/utils/retry.js +13 -1
- package/dist/ydb/client.d.ts +7 -3
- package/dist/ydb/client.js +13 -2
- package/package.json +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { TypedValues, withSession } from "../ydb/client.js";
|
|
1
|
+
import { TypedValues, withSession, createExecuteQuerySettings, } from "../ydb/client.js";
|
|
2
2
|
import { mapDistanceToIndexParam } from "../utils/distance.js";
|
|
3
3
|
import { COLLECTION_STORAGE_MODE, isOneTableMode, } from "../config/env.js";
|
|
4
4
|
import { GLOBAL_POINTS_TABLE } from "../ydb/schema.js";
|
|
@@ -20,9 +20,10 @@ export async function getCollectionMeta(metaKey) {
|
|
|
20
20
|
WHERE collection = $collection;
|
|
21
21
|
`;
|
|
22
22
|
const res = await withSession(async (s) => {
|
|
23
|
+
const settings = createExecuteQuerySettings();
|
|
23
24
|
return await s.executeQuery(qry, {
|
|
24
25
|
$collection: TypedValues.utf8(metaKey),
|
|
25
|
-
});
|
|
26
|
+
}, undefined, settings);
|
|
26
27
|
});
|
|
27
28
|
const rowset = res.resultSets?.[0];
|
|
28
29
|
if (!rowset || rowset.rows?.length !== 1)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Types, TypedValues, withSession, TableDescription, Column, } from "../ydb/client.js";
|
|
1
|
+
import { Types, TypedValues, withSession, TableDescription, Column, createExecuteQuerySettings, } from "../ydb/client.js";
|
|
2
2
|
import { upsertCollectionMeta } from "./collectionsRepo.shared.js";
|
|
3
3
|
export async function createCollectionMultiTable(metaKey, dim, distance, vectorType, tableName) {
|
|
4
4
|
await withSession(async (s) => {
|
|
@@ -18,6 +18,7 @@ export async function deleteCollectionMultiTable(metaKey, tableName) {
|
|
|
18
18
|
DELETE FROM qdr__collections WHERE collection = $collection;
|
|
19
19
|
`;
|
|
20
20
|
await withSession(async (s) => {
|
|
21
|
-
|
|
21
|
+
const settings = createExecuteQuerySettings();
|
|
22
|
+
await s.executeQuery(delMeta, { $collection: TypedValues.utf8(metaKey) }, undefined, settings);
|
|
22
23
|
});
|
|
23
24
|
}
|
|
@@ -1,6 +1,63 @@
|
|
|
1
|
-
import { TypedValues, withSession } from "../ydb/client.js";
|
|
1
|
+
import { TypedValues, Types, withSession, createExecuteQuerySettings, } from "../ydb/client.js";
|
|
2
2
|
import { GLOBAL_POINTS_TABLE, ensureGlobalPointsTable } from "../ydb/schema.js";
|
|
3
3
|
import { upsertCollectionMeta } from "./collectionsRepo.shared.js";
|
|
4
|
+
import { withRetry, isTransientYdbError } from "../utils/retry.js";
|
|
5
|
+
const DELETE_COLLECTION_BATCH_SIZE = 10000;
|
|
6
|
+
function isOutOfBufferMemoryYdbError(error) {
|
|
7
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
8
|
+
if (/Out of buffer memory/i.test(msg)) {
|
|
9
|
+
return true;
|
|
10
|
+
}
|
|
11
|
+
if (typeof error === "object" && error !== null) {
|
|
12
|
+
const issues = error.issues;
|
|
13
|
+
if (issues !== undefined) {
|
|
14
|
+
const issuesText = typeof issues === "string" ? issues : JSON.stringify(issues);
|
|
15
|
+
return /Out of buffer memory/i.test(issuesText);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
async function deletePointsForUidInChunks(s, uid) {
|
|
21
|
+
const selectYql = `
|
|
22
|
+
DECLARE $uid AS Utf8;
|
|
23
|
+
DECLARE $limit AS Uint32;
|
|
24
|
+
SELECT point_id
|
|
25
|
+
FROM ${GLOBAL_POINTS_TABLE}
|
|
26
|
+
WHERE uid = $uid
|
|
27
|
+
LIMIT $limit;
|
|
28
|
+
`;
|
|
29
|
+
const deleteBatchYql = `
|
|
30
|
+
DECLARE $uid AS Utf8;
|
|
31
|
+
DECLARE $ids AS List<Utf8>;
|
|
32
|
+
DELETE FROM ${GLOBAL_POINTS_TABLE}
|
|
33
|
+
WHERE uid = $uid AND point_id IN $ids;
|
|
34
|
+
`;
|
|
35
|
+
// Best‑effort loop: stop when there are no more rows for this uid.
|
|
36
|
+
// Each iteration only touches a limited number of rows to avoid
|
|
37
|
+
// hitting YDB's per‑operation buffer limits.
|
|
38
|
+
let iterations = 0;
|
|
39
|
+
const MAX_ITERATIONS = 1000;
|
|
40
|
+
const settings = createExecuteQuerySettings();
|
|
41
|
+
while (iterations++ < MAX_ITERATIONS) {
|
|
42
|
+
const rs = (await s.executeQuery(selectYql, {
|
|
43
|
+
$uid: TypedValues.utf8(uid),
|
|
44
|
+
$limit: TypedValues.uint32(DELETE_COLLECTION_BATCH_SIZE),
|
|
45
|
+
}, undefined, settings));
|
|
46
|
+
const rowset = rs.resultSets?.[0];
|
|
47
|
+
const rows = rowset?.rows ?? [];
|
|
48
|
+
const ids = rows
|
|
49
|
+
.map((row) => row.items?.[0]?.textValue)
|
|
50
|
+
.filter((id) => typeof id === "string");
|
|
51
|
+
if (ids.length === 0) {
|
|
52
|
+
break;
|
|
53
|
+
}
|
|
54
|
+
const idsValue = TypedValues.list(Types.UTF8, ids);
|
|
55
|
+
await s.executeQuery(deleteBatchYql, {
|
|
56
|
+
$uid: TypedValues.utf8(uid),
|
|
57
|
+
$ids: idsValue,
|
|
58
|
+
}, undefined, settings);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
4
61
|
export async function createCollectionOneTable(metaKey, dim, distance, vectorType) {
|
|
5
62
|
await upsertCollectionMeta(metaKey, dim, distance, vectorType, GLOBAL_POINTS_TABLE);
|
|
6
63
|
}
|
|
@@ -10,16 +67,36 @@ export async function deleteCollectionOneTable(metaKey, uid) {
|
|
|
10
67
|
DECLARE $uid AS Utf8;
|
|
11
68
|
DELETE FROM ${GLOBAL_POINTS_TABLE} WHERE uid = $uid;
|
|
12
69
|
`;
|
|
13
|
-
await withSession(async (s) => {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
70
|
+
await withRetry(() => withSession(async (s) => {
|
|
71
|
+
const settings = createExecuteQuerySettings();
|
|
72
|
+
try {
|
|
73
|
+
await s.executeQuery(deletePointsYql, {
|
|
74
|
+
$uid: TypedValues.utf8(uid),
|
|
75
|
+
}, undefined, settings);
|
|
76
|
+
}
|
|
77
|
+
catch (err) {
|
|
78
|
+
if (!isOutOfBufferMemoryYdbError(err)) {
|
|
79
|
+
throw err;
|
|
80
|
+
}
|
|
81
|
+
await deletePointsForUidInChunks(s, uid);
|
|
82
|
+
}
|
|
83
|
+
}), {
|
|
84
|
+
isTransient: isTransientYdbError,
|
|
85
|
+
context: {
|
|
86
|
+
operation: "deleteCollectionOneTable",
|
|
87
|
+
tableName: GLOBAL_POINTS_TABLE,
|
|
88
|
+
metaKey,
|
|
89
|
+
uid,
|
|
90
|
+
},
|
|
17
91
|
});
|
|
18
92
|
const delMeta = `
|
|
19
93
|
DECLARE $collection AS Utf8;
|
|
20
94
|
DELETE FROM qdr__collections WHERE collection = $collection;
|
|
21
95
|
`;
|
|
22
96
|
await withSession(async (s) => {
|
|
23
|
-
|
|
97
|
+
const settings = createExecuteQuerySettings();
|
|
98
|
+
await s.executeQuery(delMeta, {
|
|
99
|
+
$collection: TypedValues.utf8(metaKey),
|
|
100
|
+
}, undefined, settings);
|
|
24
101
|
});
|
|
25
102
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Types, TypedValues, withSession } from "../ydb/client.js";
|
|
1
|
+
import { Types, TypedValues, withSession, createExecuteQuerySettings, } from "../ydb/client.js";
|
|
2
2
|
import { buildVectorParam } from "../ydb/helpers.js";
|
|
3
3
|
import { logger } from "../logging/logger.js";
|
|
4
4
|
import { notifyUpsert } from "../indexing/IndexScheduler.js";
|
|
@@ -15,6 +15,7 @@ export async function upsertPointsMultiTable(tableName, points, dimension) {
|
|
|
15
15
|
}
|
|
16
16
|
let upserted = 0;
|
|
17
17
|
await withSession(async (s) => {
|
|
18
|
+
const settings = createExecuteQuerySettings();
|
|
18
19
|
for (let i = 0; i < points.length; i += UPSERT_BATCH_SIZE) {
|
|
19
20
|
const batch = points.slice(i, i + UPSERT_BATCH_SIZE);
|
|
20
21
|
const ddl = `
|
|
@@ -44,7 +45,7 @@ export async function upsertPointsMultiTable(tableName, points, dimension) {
|
|
|
44
45
|
const params = {
|
|
45
46
|
$rows: rowsValue,
|
|
46
47
|
};
|
|
47
|
-
await withRetry(() => s.executeQuery(ddl, params), {
|
|
48
|
+
await withRetry(() => s.executeQuery(ddl, params, undefined, settings), {
|
|
48
49
|
isTransient: isTransientYdbError,
|
|
49
50
|
context: { tableName, batchSize: batch.length },
|
|
50
51
|
});
|
|
@@ -64,6 +65,7 @@ export async function searchPointsMultiTable(tableName, queryVector, top, withPa
|
|
|
64
65
|
$qf: qf,
|
|
65
66
|
$k2: TypedValues.uint32(top),
|
|
66
67
|
};
|
|
68
|
+
const settings = createExecuteQuerySettings();
|
|
67
69
|
const buildQuery = (useIndex) => `
|
|
68
70
|
DECLARE $qf AS List<Float>;
|
|
69
71
|
DECLARE $k2 AS Uint32;
|
|
@@ -77,7 +79,7 @@ export async function searchPointsMultiTable(tableName, queryVector, top, withPa
|
|
|
77
79
|
if (VECTOR_INDEX_BUILD_ENABLED) {
|
|
78
80
|
try {
|
|
79
81
|
rs = await withSession(async (s) => {
|
|
80
|
-
return await s.executeQuery(buildQuery(true), params);
|
|
82
|
+
return await s.executeQuery(buildQuery(true), params, undefined, settings);
|
|
81
83
|
});
|
|
82
84
|
logger.info({ tableName }, "vector index found; using index for search");
|
|
83
85
|
}
|
|
@@ -87,7 +89,7 @@ export async function searchPointsMultiTable(tableName, queryVector, top, withPa
|
|
|
87
89
|
if (indexUnavailable) {
|
|
88
90
|
logger.info({ tableName }, "vector index not available (missing or building); falling back to table scan");
|
|
89
91
|
rs = await withSession(async (s) => {
|
|
90
|
-
return await s.executeQuery(buildQuery(false), params);
|
|
92
|
+
return await s.executeQuery(buildQuery(false), params, undefined, settings);
|
|
91
93
|
});
|
|
92
94
|
}
|
|
93
95
|
else {
|
|
@@ -97,7 +99,7 @@ export async function searchPointsMultiTable(tableName, queryVector, top, withPa
|
|
|
97
99
|
}
|
|
98
100
|
else {
|
|
99
101
|
rs = await withSession(async (s) => {
|
|
100
|
-
return await s.executeQuery(buildQuery(false), params);
|
|
102
|
+
return await s.executeQuery(buildQuery(false), params, undefined, settings);
|
|
101
103
|
});
|
|
102
104
|
}
|
|
103
105
|
const rowset = rs.resultSets?.[0];
|
|
@@ -128,6 +130,7 @@ export async function searchPointsMultiTable(tableName, queryVector, top, withPa
|
|
|
128
130
|
export async function deletePointsMultiTable(tableName, ids) {
|
|
129
131
|
let deleted = 0;
|
|
130
132
|
await withSession(async (s) => {
|
|
133
|
+
const settings = createExecuteQuerySettings();
|
|
131
134
|
for (const id of ids) {
|
|
132
135
|
const yql = `
|
|
133
136
|
DECLARE $id AS Utf8;
|
|
@@ -136,7 +139,7 @@ export async function deletePointsMultiTable(tableName, ids) {
|
|
|
136
139
|
const params = {
|
|
137
140
|
$id: TypedValues.utf8(String(id)),
|
|
138
141
|
};
|
|
139
|
-
await s.executeQuery(yql, params);
|
|
142
|
+
await s.executeQuery(yql, params, undefined, settings);
|
|
140
143
|
deleted += 1;
|
|
141
144
|
}
|
|
142
145
|
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { TypedValues, Types, withSession } from "../ydb/client.js";
|
|
1
|
+
import { TypedValues, Types, withSession, createExecuteQuerySettings, } from "../ydb/client.js";
|
|
2
2
|
import { buildVectorParam, buildVectorBinaryParams } from "../ydb/helpers.js";
|
|
3
3
|
import { notifyUpsert } from "../indexing/IndexScheduler.js";
|
|
4
4
|
import { mapDistanceToKnnFn, mapDistanceToBitKnnFn, } from "../utils/distance.js";
|
|
@@ -25,6 +25,7 @@ export async function upsertPointsOneTable(tableName, points, dimension, uid) {
|
|
|
25
25
|
}
|
|
26
26
|
let upserted = 0;
|
|
27
27
|
await withSession(async (s) => {
|
|
28
|
+
const settings = createExecuteQuerySettings();
|
|
28
29
|
for (let i = 0; i < points.length; i += UPSERT_BATCH_SIZE) {
|
|
29
30
|
const batch = points.slice(i, i + UPSERT_BATCH_SIZE);
|
|
30
31
|
let ddl;
|
|
@@ -120,7 +121,7 @@ export async function upsertPointsOneTable(tableName, points, dimension, uid) {
|
|
|
120
121
|
})),
|
|
121
122
|
},
|
|
122
123
|
}, "one_table upsert: executing YQL");
|
|
123
|
-
await withRetry(() => s.executeQuery(ddl, params), {
|
|
124
|
+
await withRetry(() => s.executeQuery(ddl, params, undefined, settings), {
|
|
124
125
|
isTransient: isTransientYdbError,
|
|
125
126
|
context: { tableName, batchSize: batch.length },
|
|
126
127
|
});
|
|
@@ -193,7 +194,8 @@ async function searchPointsOneTableExact(tableName, queryVector, top, withPayloa
|
|
|
193
194
|
vectorPreview: queryVector.slice(0, 3),
|
|
194
195
|
},
|
|
195
196
|
}, "one_table search (exact): executing YQL");
|
|
196
|
-
const
|
|
197
|
+
const settings = createExecuteQuerySettings();
|
|
198
|
+
const rs = await s.executeQuery(yql, params, undefined, settings);
|
|
197
199
|
const rowset = rs.resultSets?.[0];
|
|
198
200
|
const rows = (rowset?.rows ?? []);
|
|
199
201
|
return rows.map((row) => {
|
|
@@ -334,7 +336,8 @@ async function searchPointsOneTableApproximate(tableName, queryVector, top, with
|
|
|
334
336
|
},
|
|
335
337
|
}, "one_table search (approximate): executing YQL");
|
|
336
338
|
}
|
|
337
|
-
const
|
|
339
|
+
const settings = createExecuteQuerySettings();
|
|
340
|
+
const rs = await s.executeQuery(yql, params, undefined, settings);
|
|
338
341
|
const rowset = rs.resultSets?.[0];
|
|
339
342
|
const rows = (rowset?.rows ?? []);
|
|
340
343
|
return rows.map((row) => {
|
|
@@ -371,6 +374,7 @@ export async function searchPointsOneTable(tableName, queryVector, top, withPayl
|
|
|
371
374
|
export async function deletePointsOneTable(tableName, ids, uid) {
|
|
372
375
|
let deleted = 0;
|
|
373
376
|
await withSession(async (s) => {
|
|
377
|
+
const settings = createExecuteQuerySettings();
|
|
374
378
|
for (const id of ids) {
|
|
375
379
|
const yql = `
|
|
376
380
|
DECLARE $uid AS Utf8;
|
|
@@ -381,7 +385,7 @@ export async function deletePointsOneTable(tableName, ids, uid) {
|
|
|
381
385
|
$uid: TypedValues.utf8(uid),
|
|
382
386
|
$id: TypedValues.utf8(String(id)),
|
|
383
387
|
};
|
|
384
|
-
await s.executeQuery(yql, params);
|
|
388
|
+
await s.executeQuery(yql, params, undefined, settings);
|
|
385
389
|
deleted += 1;
|
|
386
390
|
}
|
|
387
391
|
});
|
package/dist/utils/retry.js
CHANGED
|
@@ -3,7 +3,19 @@ const DEFAULT_MAX_RETRIES = 6;
|
|
|
3
3
|
const DEFAULT_BASE_DELAY_MS = 250;
|
|
4
4
|
export function isTransientYdbError(error) {
|
|
5
5
|
const msg = error instanceof Error ? error.message : String(error);
|
|
6
|
-
|
|
6
|
+
if (/Aborted|schema version mismatch|Table metadata loading|Failed to load metadata|overloaded|is in process of split|wrong shard state|Rejecting data TxId/i.test(msg)) {
|
|
7
|
+
return true;
|
|
8
|
+
}
|
|
9
|
+
if (typeof error === "object" && error !== null) {
|
|
10
|
+
const issues = error.issues;
|
|
11
|
+
if (issues !== undefined) {
|
|
12
|
+
const issuesText = typeof issues === "string" ? issues : JSON.stringify(issues);
|
|
13
|
+
if (/overloaded|is in process of split|wrong shard state|Rejecting data TxId/i.test(issuesText)) {
|
|
14
|
+
return true;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return false;
|
|
7
19
|
}
|
|
8
20
|
export async function withRetry(fn, options = {}) {
|
|
9
21
|
const maxRetries = options.maxRetries ?? DEFAULT_MAX_RETRIES;
|
package/dist/ydb/client.d.ts
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
|
-
import type { Session, IAuthService } from "ydb-sdk";
|
|
2
|
-
declare const Types: typeof import("ydb-sdk").Types, TypedValues: typeof import("ydb-sdk").TypedValues, TableDescription: typeof import("ydb-sdk").TableDescription, Column: typeof import("ydb-sdk").Column;
|
|
3
|
-
export { Types, TypedValues, TableDescription, Column };
|
|
1
|
+
import type { Session, IAuthService, ExecuteQuerySettings as YdbExecuteQuerySettings } from "ydb-sdk";
|
|
2
|
+
declare const Types: typeof import("ydb-sdk").Types, TypedValues: typeof import("ydb-sdk").TypedValues, TableDescription: typeof import("ydb-sdk").TableDescription, Column: typeof import("ydb-sdk").Column, ExecuteQuerySettings: typeof YdbExecuteQuerySettings;
|
|
3
|
+
export { Types, TypedValues, TableDescription, Column, ExecuteQuerySettings };
|
|
4
|
+
export declare function createExecuteQuerySettings(options?: {
|
|
5
|
+
keepInCache?: boolean;
|
|
6
|
+
idempotent?: boolean;
|
|
7
|
+
}): YdbExecuteQuerySettings;
|
|
4
8
|
type DriverConfig = {
|
|
5
9
|
endpoint?: string;
|
|
6
10
|
database?: string;
|
package/dist/ydb/client.js
CHANGED
|
@@ -2,8 +2,19 @@ import { createRequire } from "module";
|
|
|
2
2
|
import { YDB_DATABASE, YDB_ENDPOINT, SESSION_POOL_MIN_SIZE, SESSION_POOL_MAX_SIZE, SESSION_KEEPALIVE_PERIOD_MS, } from "../config/env.js";
|
|
3
3
|
import { logger } from "../logging/logger.js";
|
|
4
4
|
const require = createRequire(import.meta.url);
|
|
5
|
-
const { Driver, getCredentialsFromEnv, Types, TypedValues, TableDescription, Column, } = require("ydb-sdk");
|
|
6
|
-
export { Types, TypedValues, TableDescription, Column };
|
|
5
|
+
const { Driver, getCredentialsFromEnv, Types, TypedValues, TableDescription, Column, ExecuteQuerySettings, } = require("ydb-sdk");
|
|
6
|
+
export { Types, TypedValues, TableDescription, Column, ExecuteQuerySettings };
|
|
7
|
+
export function createExecuteQuerySettings(options) {
|
|
8
|
+
const { keepInCache = true, idempotent = true } = options ?? {};
|
|
9
|
+
const settings = new ExecuteQuerySettings();
|
|
10
|
+
if (keepInCache) {
|
|
11
|
+
settings.withKeepInCache(true);
|
|
12
|
+
}
|
|
13
|
+
if (idempotent) {
|
|
14
|
+
settings.withIdempotent(true);
|
|
15
|
+
}
|
|
16
|
+
return settings;
|
|
17
|
+
}
|
|
7
18
|
const DRIVER_READY_TIMEOUT_MS = 15000;
|
|
8
19
|
const TABLE_SESSION_TIMEOUT_MS = 20000;
|
|
9
20
|
const YDB_HEALTHCHECK_READY_TIMEOUT_MS = 5000;
|