ydb-qdrant 5.1.0 → 5.2.0
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/config/env.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ export declare const YDB_DATABASE: string;
|
|
|
4
4
|
export declare const PORT: number;
|
|
5
5
|
export declare const LOG_LEVEL: string;
|
|
6
6
|
export declare const GLOBAL_POINTS_AUTOMIGRATE_ENABLED: boolean;
|
|
7
|
+
export declare const USE_BATCH_DELETE_FOR_COLLECTIONS: boolean;
|
|
7
8
|
export declare enum SearchMode {
|
|
8
9
|
Exact = "exact",
|
|
9
10
|
Approximate = "approximate"
|
|
@@ -16,5 +17,6 @@ export declare const UPSERT_BATCH_SIZE: number;
|
|
|
16
17
|
export declare const SESSION_POOL_MIN_SIZE: number;
|
|
17
18
|
export declare const SESSION_POOL_MAX_SIZE: number;
|
|
18
19
|
export declare const SESSION_KEEPALIVE_PERIOD_MS: number;
|
|
20
|
+
export declare const STARTUP_PROBE_SESSION_TIMEOUT_MS: number;
|
|
19
21
|
export declare const UPSERT_OPERATION_TIMEOUT_MS: number;
|
|
20
22
|
export declare const SEARCH_OPERATION_TIMEOUT_MS: number;
|
package/dist/config/env.js
CHANGED
|
@@ -35,6 +35,7 @@ function parseBooleanEnv(value, defaultValue) {
|
|
|
35
35
|
return true;
|
|
36
36
|
}
|
|
37
37
|
export const GLOBAL_POINTS_AUTOMIGRATE_ENABLED = parseBooleanEnv(process.env.YDB_QDRANT_GLOBAL_POINTS_AUTOMIGRATE, false);
|
|
38
|
+
export const USE_BATCH_DELETE_FOR_COLLECTIONS = parseBooleanEnv(process.env.YDB_QDRANT_USE_BATCH_DELETE, true);
|
|
38
39
|
export var SearchMode;
|
|
39
40
|
(function (SearchMode) {
|
|
40
41
|
SearchMode["Exact"] = "exact";
|
|
@@ -67,5 +68,6 @@ const NORMALIZED_SESSION_POOL_MIN_SIZE = RAW_SESSION_POOL_MIN_SIZE > RAW_SESSION
|
|
|
67
68
|
export const SESSION_POOL_MIN_SIZE = NORMALIZED_SESSION_POOL_MIN_SIZE;
|
|
68
69
|
export const SESSION_POOL_MAX_SIZE = RAW_SESSION_POOL_MAX_SIZE;
|
|
69
70
|
export const SESSION_KEEPALIVE_PERIOD_MS = parseIntegerEnv(process.env.YDB_SESSION_KEEPALIVE_PERIOD_MS, 5000, { min: 1000, max: 60000 });
|
|
71
|
+
export const STARTUP_PROBE_SESSION_TIMEOUT_MS = parseIntegerEnv(process.env.YDB_QDRANT_STARTUP_PROBE_SESSION_TIMEOUT_MS, 5000, { min: 1000 });
|
|
70
72
|
export const UPSERT_OPERATION_TIMEOUT_MS = parseIntegerEnv(process.env.YDB_QDRANT_UPSERT_TIMEOUT_MS, 5000, { min: 1000 });
|
|
71
73
|
export const SEARCH_OPERATION_TIMEOUT_MS = parseIntegerEnv(process.env.YDB_QDRANT_SEARCH_TIMEOUT_MS, 10000, { min: 1000 });
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { TypedValues, withSession, createExecuteQuerySettings, withStartupProbeSession, createExecuteQuerySettingsWithTimeout, } from "../ydb/client.js";
|
|
2
|
+
import { STARTUP_PROBE_SESSION_TIMEOUT_MS } from "../config/env.js";
|
|
2
3
|
import { uidFor } from "../utils/tenant.js";
|
|
3
4
|
import { createCollectionOneTable, deleteCollectionOneTable, } from "./collectionsRepo.one-table.js";
|
|
4
5
|
import { withRetry, isTransientYdbError } from "../utils/retry.js";
|
|
@@ -41,7 +42,7 @@ export async function verifyCollectionsQueryCompilationForStartup() {
|
|
|
41
42
|
const settings = createExecuteQuerySettingsWithTimeout({
|
|
42
43
|
keepInCache: true,
|
|
43
44
|
idempotent: true,
|
|
44
|
-
timeoutMs:
|
|
45
|
+
timeoutMs: STARTUP_PROBE_SESSION_TIMEOUT_MS,
|
|
45
46
|
});
|
|
46
47
|
await s.executeQuery(qry, {
|
|
47
48
|
$collection: TypedValues.utf8(probeKey),
|
|
@@ -2,6 +2,7 @@ import { TypedValues, Types, withSession, createExecuteQuerySettings, } from "..
|
|
|
2
2
|
import { GLOBAL_POINTS_TABLE, ensureGlobalPointsTable } from "../ydb/schema.js";
|
|
3
3
|
import { upsertCollectionMeta } from "./collectionsRepo.shared.js";
|
|
4
4
|
import { withRetry, isTransientYdbError } from "../utils/retry.js";
|
|
5
|
+
import { USE_BATCH_DELETE_FOR_COLLECTIONS } from "../config/env.js";
|
|
5
6
|
const DELETE_COLLECTION_BATCH_SIZE = 10000;
|
|
6
7
|
function isOutOfBufferMemoryYdbError(error) {
|
|
7
8
|
const msg = error instanceof Error ? error.message : String(error);
|
|
@@ -63,32 +64,68 @@ export async function createCollectionOneTable(metaKey, dim, distance, vectorTyp
|
|
|
63
64
|
}
|
|
64
65
|
export async function deleteCollectionOneTable(metaKey, uid) {
|
|
65
66
|
await ensureGlobalPointsTable();
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
if (!isOutOfBufferMemoryYdbError(err)) {
|
|
79
|
-
throw err;
|
|
67
|
+
if (USE_BATCH_DELETE_FOR_COLLECTIONS) {
|
|
68
|
+
const batchDeletePointsYql = `
|
|
69
|
+
DECLARE $uid AS Utf8;
|
|
70
|
+
BATCH DELETE FROM ${GLOBAL_POINTS_TABLE}
|
|
71
|
+
WHERE uid = $uid;
|
|
72
|
+
`;
|
|
73
|
+
await withRetry(() => withSession(async (s) => {
|
|
74
|
+
const settings = createExecuteQuerySettings();
|
|
75
|
+
try {
|
|
76
|
+
await s.executeQuery(batchDeletePointsYql, {
|
|
77
|
+
$uid: TypedValues.utf8(uid),
|
|
78
|
+
}, undefined, settings);
|
|
80
79
|
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
},
|
|
91
|
-
|
|
80
|
+
catch (err) {
|
|
81
|
+
if (!isOutOfBufferMemoryYdbError(err)) {
|
|
82
|
+
throw err;
|
|
83
|
+
}
|
|
84
|
+
// BATCH DELETE already deletes in chunks per partition, but if YDB
|
|
85
|
+
// still reports an out-of-buffer-memory condition, fall back to
|
|
86
|
+
// the same per-uid chunked deletion strategy as the legacy path.
|
|
87
|
+
await deletePointsForUidInChunks(s, uid);
|
|
88
|
+
}
|
|
89
|
+
}), {
|
|
90
|
+
isTransient: isTransientYdbError,
|
|
91
|
+
context: {
|
|
92
|
+
operation: "deleteCollectionOneTable",
|
|
93
|
+
tableName: GLOBAL_POINTS_TABLE,
|
|
94
|
+
metaKey,
|
|
95
|
+
uid,
|
|
96
|
+
mode: "batch_delete",
|
|
97
|
+
},
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
const deletePointsYql = `
|
|
102
|
+
DECLARE $uid AS Utf8;
|
|
103
|
+
DELETE FROM ${GLOBAL_POINTS_TABLE} WHERE uid = $uid;
|
|
104
|
+
`;
|
|
105
|
+
await withRetry(() => withSession(async (s) => {
|
|
106
|
+
const settings = createExecuteQuerySettings();
|
|
107
|
+
try {
|
|
108
|
+
await s.executeQuery(deletePointsYql, {
|
|
109
|
+
$uid: TypedValues.utf8(uid),
|
|
110
|
+
}, undefined, settings);
|
|
111
|
+
}
|
|
112
|
+
catch (err) {
|
|
113
|
+
if (!isOutOfBufferMemoryYdbError(err)) {
|
|
114
|
+
throw err;
|
|
115
|
+
}
|
|
116
|
+
await deletePointsForUidInChunks(s, uid);
|
|
117
|
+
}
|
|
118
|
+
}), {
|
|
119
|
+
isTransient: isTransientYdbError,
|
|
120
|
+
context: {
|
|
121
|
+
operation: "deleteCollectionOneTable",
|
|
122
|
+
tableName: GLOBAL_POINTS_TABLE,
|
|
123
|
+
metaKey,
|
|
124
|
+
uid,
|
|
125
|
+
mode: "legacy_chunked",
|
|
126
|
+
},
|
|
127
|
+
});
|
|
128
|
+
}
|
|
92
129
|
const delMeta = `
|
|
93
130
|
DECLARE $collection AS Utf8;
|
|
94
131
|
DELETE FROM qdr__collections WHERE collection = $collection;
|
package/dist/utils/retry.js
CHANGED
|
@@ -32,7 +32,14 @@ export async function withRetry(fn, options = {}) {
|
|
|
32
32
|
throw e;
|
|
33
33
|
}
|
|
34
34
|
const backoffMs = Math.floor(baseDelayMs * Math.pow(2, attempt) + Math.random() * 100);
|
|
35
|
-
logger.warn({
|
|
35
|
+
logger.warn({
|
|
36
|
+
...context,
|
|
37
|
+
attempt,
|
|
38
|
+
backoffMs,
|
|
39
|
+
err: e instanceof Error
|
|
40
|
+
? e
|
|
41
|
+
: new Error(typeof e === "string" ? e : JSON.stringify(e)),
|
|
42
|
+
}, "operation aborted due to transient error; retrying");
|
|
36
43
|
await new Promise((r) => setTimeout(r, backoffMs));
|
|
37
44
|
attempt += 1;
|
|
38
45
|
}
|
package/dist/ydb/client.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createRequire } from "module";
|
|
2
|
-
import { YDB_DATABASE, YDB_ENDPOINT, SESSION_POOL_MIN_SIZE, SESSION_POOL_MAX_SIZE, SESSION_KEEPALIVE_PERIOD_MS, } from "../config/env.js";
|
|
2
|
+
import { YDB_DATABASE, YDB_ENDPOINT, SESSION_POOL_MIN_SIZE, SESSION_POOL_MAX_SIZE, SESSION_KEEPALIVE_PERIOD_MS, STARTUP_PROBE_SESSION_TIMEOUT_MS, } from "../config/env.js";
|
|
3
3
|
import { logger } from "../logging/logger.js";
|
|
4
4
|
const require = createRequire(import.meta.url);
|
|
5
5
|
const { Driver, getCredentialsFromEnv, Types, TypedValues, TableDescription, Column, ExecuteQuerySettings, OperationParams, Ydb, } = require("ydb-sdk");
|
|
@@ -30,7 +30,6 @@ const DRIVER_READY_TIMEOUT_MS = 15000;
|
|
|
30
30
|
const TABLE_SESSION_TIMEOUT_MS = 20000;
|
|
31
31
|
const YDB_HEALTHCHECK_READY_TIMEOUT_MS = 5000;
|
|
32
32
|
const DRIVER_REFRESH_COOLDOWN_MS = 30000;
|
|
33
|
-
const STARTUP_PROBE_SESSION_TIMEOUT_MS = 3000;
|
|
34
33
|
let overrideConfig;
|
|
35
34
|
let driver;
|
|
36
35
|
let lastDriverRefreshAt = 0;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ydb-qdrant",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.2.0",
|
|
4
4
|
"main": "dist/package/api.js",
|
|
5
5
|
"types": "dist/package/api.d.ts",
|
|
6
6
|
"exports": {
|
|
@@ -50,6 +50,14 @@
|
|
|
50
50
|
"author": "",
|
|
51
51
|
"license": "Apache-2.0",
|
|
52
52
|
"description": "Qdrant-compatible Node.js/TypeScript API that stores/searches embeddings in YDB using a global one-table layout with exact and approximate KNN search over serialized vectors.",
|
|
53
|
+
"repository": {
|
|
54
|
+
"type": "git",
|
|
55
|
+
"url": "git+https://github.com/astandrik/ydb-qdrant.git"
|
|
56
|
+
},
|
|
57
|
+
"homepage": "https://github.com/astandrik/ydb-qdrant#readme",
|
|
58
|
+
"bugs": {
|
|
59
|
+
"url": "https://github.com/astandrik/ydb-qdrant/issues"
|
|
60
|
+
},
|
|
53
61
|
"type": "module",
|
|
54
62
|
"publishConfig": {
|
|
55
63
|
"access": "public"
|
|
@@ -83,4 +91,4 @@
|
|
|
83
91
|
"typescript-eslint": "^8.47.0",
|
|
84
92
|
"vitest": "^4.0.12"
|
|
85
93
|
}
|
|
86
|
-
}
|
|
94
|
+
}
|