ydb-qdrant 5.0.0 → 5.1.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 +2 -0
- package/dist/config/env.js +2 -0
- package/dist/repositories/collectionsRepo.js +16 -8
- package/dist/repositories/pointsRepo.one-table.js +17 -5
- package/dist/routes/points.js +30 -4
- package/dist/server.js +18 -1
- package/dist/utils/exit.d.ts +2 -0
- package/dist/utils/exit.js +12 -0
- package/dist/ydb/client.d.ts +2 -2
- package/dist/ydb/client.js +2 -2
- package/dist/ydb/schema.js +8 -2
- package/package.json +1 -1
package/dist/config/env.d.ts
CHANGED
|
@@ -16,3 +16,5 @@ export declare const UPSERT_BATCH_SIZE: number;
|
|
|
16
16
|
export declare const SESSION_POOL_MIN_SIZE: number;
|
|
17
17
|
export declare const SESSION_POOL_MAX_SIZE: number;
|
|
18
18
|
export declare const SESSION_KEEPALIVE_PERIOD_MS: number;
|
|
19
|
+
export declare const UPSERT_OPERATION_TIMEOUT_MS: number;
|
|
20
|
+
export declare const SEARCH_OPERATION_TIMEOUT_MS: number;
|
package/dist/config/env.js
CHANGED
|
@@ -67,3 +67,5 @@ const NORMALIZED_SESSION_POOL_MIN_SIZE = RAW_SESSION_POOL_MIN_SIZE > RAW_SESSION
|
|
|
67
67
|
export const SESSION_POOL_MIN_SIZE = NORMALIZED_SESSION_POOL_MIN_SIZE;
|
|
68
68
|
export const SESSION_POOL_MAX_SIZE = RAW_SESSION_POOL_MAX_SIZE;
|
|
69
69
|
export const SESSION_KEEPALIVE_PERIOD_MS = parseIntegerEnv(process.env.YDB_SESSION_KEEPALIVE_PERIOD_MS, 5000, { min: 1000, max: 60000 });
|
|
70
|
+
export const UPSERT_OPERATION_TIMEOUT_MS = parseIntegerEnv(process.env.YDB_QDRANT_UPSERT_TIMEOUT_MS, 5000, { min: 1000 });
|
|
71
|
+
export const SEARCH_OPERATION_TIMEOUT_MS = parseIntegerEnv(process.env.YDB_QDRANT_SEARCH_TIMEOUT_MS, 10000, { min: 1000 });
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { TypedValues, withSession, createExecuteQuerySettings, withStartupProbeSession, createExecuteQuerySettingsWithTimeout, } from "../ydb/client.js";
|
|
2
2
|
import { uidFor } from "../utils/tenant.js";
|
|
3
3
|
import { createCollectionOneTable, deleteCollectionOneTable, } from "./collectionsRepo.one-table.js";
|
|
4
|
+
import { withRetry, isTransientYdbError } from "../utils/retry.js";
|
|
4
5
|
export async function createCollection(metaKey, dim, distance, vectorType) {
|
|
5
6
|
await createCollectionOneTable(metaKey, dim, distance, vectorType);
|
|
6
7
|
}
|
|
@@ -35,15 +36,22 @@ export async function verifyCollectionsQueryCompilationForStartup() {
|
|
|
35
36
|
FROM qdr__collections
|
|
36
37
|
WHERE collection = $collection;
|
|
37
38
|
`;
|
|
38
|
-
await
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
39
|
+
await withRetry(async () => {
|
|
40
|
+
await withStartupProbeSession(async (s) => {
|
|
41
|
+
const settings = createExecuteQuerySettingsWithTimeout({
|
|
42
|
+
keepInCache: true,
|
|
43
|
+
idempotent: true,
|
|
44
|
+
timeoutMs: 3000,
|
|
45
|
+
});
|
|
46
|
+
await s.executeQuery(qry, {
|
|
47
|
+
$collection: TypedValues.utf8(probeKey),
|
|
48
|
+
}, undefined, settings);
|
|
43
49
|
});
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
50
|
+
}, {
|
|
51
|
+
isTransient: isTransientYdbError,
|
|
52
|
+
maxRetries: 2,
|
|
53
|
+
baseDelayMs: 200,
|
|
54
|
+
context: { probe: "collections_startup_compilation" },
|
|
47
55
|
});
|
|
48
56
|
}
|
|
49
57
|
export async function deleteCollection(metaKey, uid) {
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { TypedValues, Types, withSession, createExecuteQuerySettings, } from "../ydb/client.js";
|
|
1
|
+
import { TypedValues, Types, withSession, createExecuteQuerySettings, createExecuteQuerySettingsWithTimeout, } from "../ydb/client.js";
|
|
2
2
|
import { buildVectorParam, buildVectorBinaryParams } from "../ydb/helpers.js";
|
|
3
3
|
import { mapDistanceToKnnFn, mapDistanceToBitKnnFn, } from "../utils/distance.js";
|
|
4
4
|
import { withRetry, isTransientYdbError } from "../utils/retry.js";
|
|
5
5
|
import { UPSERT_BATCH_SIZE } from "../ydb/schema.js";
|
|
6
|
-
import { CLIENT_SIDE_SERIALIZATION_ENABLED, SearchMode, } from "../config/env.js";
|
|
6
|
+
import { CLIENT_SIDE_SERIALIZATION_ENABLED, SearchMode, UPSERT_OPERATION_TIMEOUT_MS, SEARCH_OPERATION_TIMEOUT_MS, } from "../config/env.js";
|
|
7
7
|
import { logger } from "../logging/logger.js";
|
|
8
8
|
export async function upsertPointsOneTable(tableName, points, dimension, uid) {
|
|
9
9
|
for (const p of points) {
|
|
@@ -24,7 +24,11 @@ export async function upsertPointsOneTable(tableName, points, dimension, uid) {
|
|
|
24
24
|
}
|
|
25
25
|
let upserted = 0;
|
|
26
26
|
await withSession(async (s) => {
|
|
27
|
-
const settings =
|
|
27
|
+
const settings = createExecuteQuerySettingsWithTimeout({
|
|
28
|
+
keepInCache: true,
|
|
29
|
+
idempotent: true,
|
|
30
|
+
timeoutMs: UPSERT_OPERATION_TIMEOUT_MS,
|
|
31
|
+
});
|
|
28
32
|
for (let i = 0; i < points.length; i += UPSERT_BATCH_SIZE) {
|
|
29
33
|
const batch = points.slice(i, i + UPSERT_BATCH_SIZE);
|
|
30
34
|
let ddl;
|
|
@@ -192,7 +196,11 @@ async function searchPointsOneTableExact(tableName, queryVector, top, withPayloa
|
|
|
192
196
|
vectorPreview: queryVector.slice(0, 3),
|
|
193
197
|
},
|
|
194
198
|
}, "one_table search (exact): executing YQL");
|
|
195
|
-
const settings =
|
|
199
|
+
const settings = createExecuteQuerySettingsWithTimeout({
|
|
200
|
+
keepInCache: true,
|
|
201
|
+
idempotent: true,
|
|
202
|
+
timeoutMs: SEARCH_OPERATION_TIMEOUT_MS,
|
|
203
|
+
});
|
|
196
204
|
const rs = await s.executeQuery(yql, params, undefined, settings);
|
|
197
205
|
const rowset = rs.resultSets?.[0];
|
|
198
206
|
const rows = (rowset?.rows ?? []);
|
|
@@ -334,7 +342,11 @@ async function searchPointsOneTableApproximate(tableName, queryVector, top, with
|
|
|
334
342
|
},
|
|
335
343
|
}, "one_table search (approximate): executing YQL");
|
|
336
344
|
}
|
|
337
|
-
const settings =
|
|
345
|
+
const settings = createExecuteQuerySettingsWithTimeout({
|
|
346
|
+
keepInCache: true,
|
|
347
|
+
idempotent: true,
|
|
348
|
+
timeoutMs: SEARCH_OPERATION_TIMEOUT_MS,
|
|
349
|
+
});
|
|
338
350
|
const rs = await s.executeQuery(yql, params, undefined, settings);
|
|
339
351
|
const rowset = rs.resultSets?.[0];
|
|
340
352
|
const rows = (rowset?.rows ?? []);
|
package/dist/routes/points.js
CHANGED
|
@@ -2,6 +2,8 @@ import { Router } from "express";
|
|
|
2
2
|
import { upsertPoints, searchPoints, queryPoints, deletePoints, } from "../services/PointsService.js";
|
|
3
3
|
import { QdrantServiceError } from "../services/errors.js";
|
|
4
4
|
import { logger } from "../logging/logger.js";
|
|
5
|
+
import { isCompilationTimeoutError } from "../ydb/client.js";
|
|
6
|
+
import { scheduleExit } from "../utils/exit.js";
|
|
5
7
|
export const pointsRouter = Router();
|
|
6
8
|
// Qdrant-compatible: PUT /collections/:collection/points (upsert)
|
|
7
9
|
pointsRouter.put("/:collection/points", async (req, res) => {
|
|
@@ -18,8 +20,14 @@ pointsRouter.put("/:collection/points", async (req, res) => {
|
|
|
18
20
|
if (err instanceof QdrantServiceError) {
|
|
19
21
|
return res.status(err.statusCode).json(err.payload);
|
|
20
22
|
}
|
|
21
|
-
logger.error({ err }, "upsert points (PUT) failed");
|
|
22
23
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
24
|
+
if (isCompilationTimeoutError(err)) {
|
|
25
|
+
logger.error({ err }, "YDB compilation error during upsert points (PUT); scheduling process exit");
|
|
26
|
+
res.status(500).json({ status: "error", error: errorMessage });
|
|
27
|
+
scheduleExit(1);
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
logger.error({ err }, "upsert points (PUT) failed");
|
|
23
31
|
res.status(500).json({ status: "error", error: errorMessage });
|
|
24
32
|
}
|
|
25
33
|
});
|
|
@@ -37,8 +45,14 @@ pointsRouter.post("/:collection/points/upsert", async (req, res) => {
|
|
|
37
45
|
if (err instanceof QdrantServiceError) {
|
|
38
46
|
return res.status(err.statusCode).json(err.payload);
|
|
39
47
|
}
|
|
40
|
-
logger.error({ err }, "upsert points failed");
|
|
41
48
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
49
|
+
if (isCompilationTimeoutError(err)) {
|
|
50
|
+
logger.error({ err }, "YDB compilation error during upsert points; scheduling process exit");
|
|
51
|
+
res.status(500).json({ status: "error", error: errorMessage });
|
|
52
|
+
scheduleExit(1);
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
logger.error({ err }, "upsert points failed");
|
|
42
56
|
res.status(500).json({ status: "error", error: errorMessage });
|
|
43
57
|
}
|
|
44
58
|
});
|
|
@@ -56,8 +70,14 @@ pointsRouter.post("/:collection/points/search", async (req, res) => {
|
|
|
56
70
|
if (err instanceof QdrantServiceError) {
|
|
57
71
|
return res.status(err.statusCode).json(err.payload);
|
|
58
72
|
}
|
|
59
|
-
logger.error({ err }, "search points failed");
|
|
60
73
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
74
|
+
if (isCompilationTimeoutError(err)) {
|
|
75
|
+
logger.error({ err }, "YDB compilation error during search points; scheduling process exit");
|
|
76
|
+
res.status(500).json({ status: "error", error: errorMessage });
|
|
77
|
+
scheduleExit(1);
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
logger.error({ err }, "search points failed");
|
|
61
81
|
res.status(500).json({ status: "error", error: errorMessage });
|
|
62
82
|
}
|
|
63
83
|
});
|
|
@@ -76,8 +96,14 @@ pointsRouter.post("/:collection/points/query", async (req, res) => {
|
|
|
76
96
|
if (err instanceof QdrantServiceError) {
|
|
77
97
|
return res.status(err.statusCode).json(err.payload);
|
|
78
98
|
}
|
|
79
|
-
logger.error({ err }, "search points (query) failed");
|
|
80
99
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
100
|
+
if (isCompilationTimeoutError(err)) {
|
|
101
|
+
logger.error({ err }, "YDB compilation error during search points (query); scheduling process exit");
|
|
102
|
+
res.status(500).json({ status: "error", error: errorMessage });
|
|
103
|
+
scheduleExit(1);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
logger.error({ err }, "search points (query) failed");
|
|
81
107
|
res.status(500).json({ status: "error", error: errorMessage });
|
|
82
108
|
}
|
|
83
109
|
});
|
package/dist/server.js
CHANGED
|
@@ -2,11 +2,28 @@ import express from "express";
|
|
|
2
2
|
import { collectionsRouter } from "./routes/collections.js";
|
|
3
3
|
import { pointsRouter } from "./routes/points.js";
|
|
4
4
|
import { requestLogger } from "./middleware/requestLogger.js";
|
|
5
|
-
import { isYdbAvailable } from "./ydb/client.js";
|
|
5
|
+
import { isYdbAvailable, isCompilationTimeoutError } from "./ydb/client.js";
|
|
6
|
+
import { verifyCollectionsQueryCompilationForStartup } from "./repositories/collectionsRepo.js";
|
|
7
|
+
import { logger } from "./logging/logger.js";
|
|
8
|
+
import { scheduleExit } from "./utils/exit.js";
|
|
6
9
|
export async function healthHandler(_req, res) {
|
|
7
10
|
const ok = await isYdbAvailable();
|
|
8
11
|
if (!ok) {
|
|
12
|
+
logger.error("YDB unavailable during health check; scheduling process exit");
|
|
9
13
|
res.status(503).json({ status: "error", error: "YDB unavailable" });
|
|
14
|
+
scheduleExit(1);
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
try {
|
|
18
|
+
await verifyCollectionsQueryCompilationForStartup();
|
|
19
|
+
}
|
|
20
|
+
catch (err) {
|
|
21
|
+
const isTimeout = isCompilationTimeoutError(err);
|
|
22
|
+
logger.error({ err }, isTimeout
|
|
23
|
+
? "YDB compilation timeout during health probe; scheduling process exit"
|
|
24
|
+
: "YDB health probe failed; scheduling process exit");
|
|
25
|
+
res.status(503).json({ status: "error", error: "YDB health probe failed" });
|
|
26
|
+
scheduleExit(1);
|
|
10
27
|
return;
|
|
11
28
|
}
|
|
12
29
|
res.json({ status: "ok" });
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
let exitFn = (code) => {
|
|
2
|
+
// Use process.exit in production; this will be overridden in tests.
|
|
3
|
+
process.exit(code);
|
|
4
|
+
};
|
|
5
|
+
export function scheduleExit(code) {
|
|
6
|
+
// Schedule exit on the next tick so HTTP responses can be flushed first.
|
|
7
|
+
setImmediate(() => exitFn(code));
|
|
8
|
+
}
|
|
9
|
+
// Test-only: allow overriding the underlying exit behavior.
|
|
10
|
+
export function __setExitFnForTests(fn) {
|
|
11
|
+
exitFn = fn;
|
|
12
|
+
}
|
package/dist/ydb/client.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
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 };
|
|
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, Ydb: typeof import("ydb-sdk-proto").Ydb;
|
|
3
|
+
export { Types, TypedValues, TableDescription, Column, ExecuteQuerySettings, Ydb, };
|
|
4
4
|
export declare function createExecuteQuerySettings(options?: {
|
|
5
5
|
keepInCache?: boolean;
|
|
6
6
|
idempotent?: boolean;
|
package/dist/ydb/client.js
CHANGED
|
@@ -2,8 +2,8 @@ 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, ExecuteQuerySettings, OperationParams, } = require("ydb-sdk");
|
|
6
|
-
export { Types, TypedValues, TableDescription, Column, ExecuteQuerySettings };
|
|
5
|
+
const { Driver, getCredentialsFromEnv, Types, TypedValues, TableDescription, Column, ExecuteQuerySettings, OperationParams, Ydb, } = require("ydb-sdk");
|
|
6
|
+
export { Types, TypedValues, TableDescription, Column, ExecuteQuerySettings, Ydb, };
|
|
7
7
|
export function createExecuteQuerySettings(options) {
|
|
8
8
|
const { keepInCache = true, idempotent = true } = options ?? {};
|
|
9
9
|
const settings = new ExecuteQuerySettings();
|
package/dist/ydb/schema.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { withSession, TableDescription, Column, Types } from "./client.js";
|
|
1
|
+
import { withSession, TableDescription, Column, Types, Ydb } from "./client.js";
|
|
2
2
|
import { logger } from "../logging/logger.js";
|
|
3
3
|
import { GLOBAL_POINTS_AUTOMIGRATE_ENABLED } from "../config/env.js";
|
|
4
4
|
export const GLOBAL_POINTS_TABLE = "qdrant_all_points";
|
|
@@ -41,10 +41,16 @@ export async function ensureGlobalPointsTable() {
|
|
|
41
41
|
tableDescription = await s.describeTable(GLOBAL_POINTS_TABLE);
|
|
42
42
|
}
|
|
43
43
|
catch {
|
|
44
|
-
// Table doesn't exist, create it with all columns using the new schema
|
|
44
|
+
// Table doesn't exist, create it with all columns using the new schema and
|
|
45
|
+
// auto-partitioning enabled.
|
|
45
46
|
const desc = new TableDescription()
|
|
46
47
|
.withColumns(new Column("uid", Types.UTF8), new Column("point_id", Types.UTF8), new Column("embedding", Types.BYTES), new Column("embedding_quantized", Types.BYTES), new Column("payload", Types.JSON_DOCUMENT))
|
|
47
48
|
.withPrimaryKeys("uid", "point_id");
|
|
49
|
+
desc.withPartitioningSettings({
|
|
50
|
+
partitioningByLoad: Ydb.FeatureFlag.Status.ENABLED,
|
|
51
|
+
partitioningBySize: Ydb.FeatureFlag.Status.ENABLED,
|
|
52
|
+
partitionSizeMb: 100,
|
|
53
|
+
});
|
|
48
54
|
await s.createTable(GLOBAL_POINTS_TABLE, desc);
|
|
49
55
|
globalPointsTableReady = true;
|
|
50
56
|
logger.info(`created global points table ${GLOBAL_POINTS_TABLE}`);
|