ydb-qdrant 8.1.1 → 9.0.3
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/README.md +20 -18
- package/dist/SmokeTest.js +2 -2
- package/dist/compute/ComputePool.d.ts +5 -0
- package/dist/compute/ComputePool.js +64 -0
- package/dist/compute/ComputeWorker.d.ts +36 -0
- package/dist/compute/ComputeWorker.js +84 -0
- package/dist/config/env.d.ts +24 -7
- package/dist/config/env.js +65 -35
- package/dist/index.d.ts +2 -0
- package/dist/index.js +92 -2
- package/dist/logging/DeployLogFormatter.d.ts +2 -0
- package/dist/logging/DeployLogFormatter.js +131 -0
- package/dist/logging/logger.js +13 -1
- package/dist/logging/requestContext.d.ts +17 -0
- package/dist/logging/requestContext.js +43 -0
- package/dist/middleware/requestLogger.js +134 -6
- package/dist/middleware/upsertBodyPhase.d.ts +6 -0
- package/dist/middleware/upsertBodyPhase.js +184 -0
- package/dist/middleware/upsertRequestTimeout.d.ts +16 -0
- package/dist/middleware/upsertRequestTimeout.js +158 -0
- package/dist/package/api.d.ts +20 -12
- package/dist/package/api.js +57 -28
- package/dist/qdrant/QdrantRestTypes.d.ts +4 -0
- package/dist/qdrant/Requests.d.ts +97 -0
- package/dist/qdrant/Requests.js +72 -0
- package/dist/repositories/collectionsRepo.d.ts +18 -2
- package/dist/repositories/collectionsRepo.js +103 -7
- package/dist/repositories/collectionsRepo.one-table.d.ts +4 -3
- package/dist/repositories/collectionsRepo.one-table.js +99 -36
- package/dist/repositories/collectionsRepo.shared.d.ts +2 -2
- package/dist/repositories/collectionsRepo.shared.js +9 -4
- package/dist/repositories/pointsRepo.d.ts +6 -4
- package/dist/repositories/pointsRepo.js +8 -7
- package/dist/repositories/pointsRepo.one-table/Delete.d.ts +2 -2
- package/dist/repositories/pointsRepo.one-table/Delete.js +157 -60
- package/dist/repositories/pointsRepo.one-table/PathSegmentsFilter.d.ts +7 -5
- package/dist/repositories/pointsRepo.one-table/PathSegmentsFilter.js +44 -13
- package/dist/repositories/pointsRepo.one-table/Retrieve.d.ts +6 -0
- package/dist/repositories/pointsRepo.one-table/Retrieve.js +69 -0
- package/dist/repositories/pointsRepo.one-table/Search.d.ts +2 -3
- package/dist/repositories/pointsRepo.one-table/Search.js +102 -124
- package/dist/repositories/pointsRepo.one-table/Upsert.d.ts +2 -2
- package/dist/repositories/pointsRepo.one-table/Upsert.js +244 -48
- package/dist/repositories/pointsRepo.one-table.d.ts +1 -0
- package/dist/repositories/pointsRepo.one-table.js +1 -0
- package/dist/routes/collections.js +45 -36
- package/dist/routes/points.js +144 -59
- package/dist/server.js +42 -6
- package/dist/services/CollectionService.d.ts +7 -5
- package/dist/services/CollectionService.js +12 -9
- package/dist/services/CollectionService.one-table.js +1 -2
- package/dist/services/CollectionService.shared.d.ts +6 -5
- package/dist/services/CollectionService.shared.js +28 -12
- package/dist/services/PointsService.d.ts +8 -0
- package/dist/services/PointsService.js +132 -15
- package/dist/types.d.ts +4 -94
- package/dist/types.js +1 -54
- package/dist/utils/EnvParsers.d.ts +5 -0
- package/dist/utils/EnvParsers.js +30 -0
- package/dist/utils/PayloadSign.d.ts +4 -0
- package/dist/utils/PayloadSign.js +18 -0
- package/dist/utils/distance.d.ts +1 -12
- package/dist/utils/distance.js +0 -21
- package/dist/utils/pathPrefix.d.ts +3 -0
- package/dist/utils/pathPrefix.js +47 -0
- package/dist/utils/prefixExpansion.d.ts +1 -0
- package/dist/utils/prefixExpansion.js +11 -0
- package/dist/utils/qdrantResponse.d.ts +13 -0
- package/dist/utils/qdrantResponse.js +12 -0
- package/dist/utils/requestIdentity.d.ts +8 -0
- package/dist/utils/requestIdentity.js +52 -0
- package/dist/utils/retry.d.ts +2 -0
- package/dist/utils/retry.js +55 -11
- package/dist/utils/tenant.d.ts +12 -6
- package/dist/utils/tenant.js +41 -32
- package/dist/utils/vectorBinary.d.ts +0 -1
- package/dist/utils/vectorBinary.js +0 -98
- package/dist/utils/ydbErrors.d.ts +1 -0
- package/dist/utils/ydbErrors.js +14 -0
- package/dist/ydb/bootstrapMetaTable.js +14 -2
- package/dist/ydb/client.d.ts +10 -2
- package/dist/ydb/client.js +83 -24
- package/dist/ydb/helpers.d.ts +0 -1
- package/dist/ydb/helpers.js +1 -2
- package/dist/ydb/schema.d.ts +2 -0
- package/dist/ydb/schema.js +84 -7
- package/package.json +10 -5
package/dist/ydb/schema.js
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
|
-
import { withSession, TableDescription, Column, Types, Ydb } from "./client.js";
|
|
1
|
+
import { withSession, TableDescription, Column, Types, Ydb, } from "./client.js";
|
|
2
2
|
import { logger } from "../logging/logger.js";
|
|
3
3
|
export const GLOBAL_POINTS_TABLE = "qdrant_all_points";
|
|
4
|
+
export const POINTS_BY_FILE_LOOKUP_TABLE = "qdrant_points_by_file";
|
|
4
5
|
// Shared YDB-related constants for repositories.
|
|
5
6
|
export { UPSERT_BATCH_SIZE } from "../config/env.js";
|
|
6
7
|
let metaTableReady = false;
|
|
7
8
|
let metaTableReadyInFlight = null;
|
|
8
9
|
let globalPointsTableReady = false;
|
|
9
10
|
let globalPointsTableReadyInFlight = null;
|
|
11
|
+
let pointsByFileTableReady = false;
|
|
12
|
+
let pointsByFileTableReadyInFlight = null;
|
|
10
13
|
function throwMigrationRequired(message) {
|
|
11
14
|
logger.error(message);
|
|
12
15
|
throw new Error(message);
|
|
@@ -57,6 +60,10 @@ async function ensureMetaTableOnce() {
|
|
|
57
60
|
if (!hasLastAccessedAt) {
|
|
58
61
|
throwMigrationRequired("Metadata table qdr__collections is missing required column last_accessed_at; please recreate the table or apply a manual schema migration before starting the service");
|
|
59
62
|
}
|
|
63
|
+
const hasUserUid = columns.some((col) => col.name === "user_uid");
|
|
64
|
+
if (!hasUserUid) {
|
|
65
|
+
throwMigrationRequired("Metadata table qdr__collections is missing required column user_uid; please recreate the table or apply a manual schema migration before starting the service");
|
|
66
|
+
}
|
|
60
67
|
});
|
|
61
68
|
metaTableReady = true;
|
|
62
69
|
}
|
|
@@ -89,8 +96,8 @@ async function ensureGlobalPointsTableOnce() {
|
|
|
89
96
|
// Table doesn't exist, create it with all columns using the new schema and
|
|
90
97
|
// auto-partitioning enabled.
|
|
91
98
|
const desc = new TableDescription()
|
|
92
|
-
.withColumns(new Column("
|
|
93
|
-
.withPrimaryKeys("
|
|
99
|
+
.withColumns(new Column("collection", Types.UTF8), new Column("point_id", Types.UTF8), new Column("embedding", Types.BYTES), new Column("payload", Types.JSON_DOCUMENT), new Column("payload_sign", Types.UTF8), new Column("path_prefix", Types.optional(Types.UTF8)))
|
|
100
|
+
.withPrimaryKeys("collection", "point_id");
|
|
94
101
|
desc.withPartitioningSettings({
|
|
95
102
|
partitioningByLoad: Ydb.FeatureFlag.Status.ENABLED,
|
|
96
103
|
partitioningBySize: Ydb.FeatureFlag.Status.ENABLED,
|
|
@@ -111,11 +118,19 @@ async function ensureGlobalPointsTableOnce() {
|
|
|
111
118
|
// schema validation.
|
|
112
119
|
tableDescription = await s.describeTable(GLOBAL_POINTS_TABLE);
|
|
113
120
|
}
|
|
114
|
-
// Table exists
|
|
121
|
+
// Table exists: validate required columns for the current schema.
|
|
115
122
|
const columns = tableDescription.columns ?? [];
|
|
116
|
-
const
|
|
117
|
-
|
|
118
|
-
|
|
123
|
+
const hasCollection = columns.some((col) => col.name === "collection");
|
|
124
|
+
const hasPayloadSign = columns.some((col) => col.name === "payload_sign");
|
|
125
|
+
if (!hasCollection) {
|
|
126
|
+
throwMigrationRequired(`Global points table ${GLOBAL_POINTS_TABLE} is missing required column collection; please recreate the table before starting the service`);
|
|
127
|
+
}
|
|
128
|
+
if (!hasPayloadSign) {
|
|
129
|
+
throwMigrationRequired(`Global points table ${GLOBAL_POINTS_TABLE} is missing required column payload_sign; please apply a manual schema migration before starting the service (example: ALTER TABLE ${GLOBAL_POINTS_TABLE} ADD COLUMN payload_sign Utf8)`);
|
|
130
|
+
}
|
|
131
|
+
const hasPathPrefix = columns.some((col) => col.name === "path_prefix");
|
|
132
|
+
if (!hasPathPrefix) {
|
|
133
|
+
throwMigrationRequired(`Global points table ${GLOBAL_POINTS_TABLE} is missing required column path_prefix; please apply a manual schema migration before starting the service (example: ALTER TABLE ${GLOBAL_POINTS_TABLE} ADD COLUMN path_prefix Optional<Utf8>)`);
|
|
119
134
|
}
|
|
120
135
|
});
|
|
121
136
|
}
|
|
@@ -136,3 +151,65 @@ export async function ensureGlobalPointsTable() {
|
|
|
136
151
|
globalPointsTableReadyInFlight = null;
|
|
137
152
|
}
|
|
138
153
|
}
|
|
154
|
+
async function ensurePointsByFileTableOnce() {
|
|
155
|
+
await withSession(async (s) => {
|
|
156
|
+
let tableDescription = null;
|
|
157
|
+
try {
|
|
158
|
+
tableDescription = await s.describeTable(POINTS_BY_FILE_LOOKUP_TABLE);
|
|
159
|
+
}
|
|
160
|
+
catch (err) {
|
|
161
|
+
if (!isTableNotFoundError(err)) {
|
|
162
|
+
throw err;
|
|
163
|
+
}
|
|
164
|
+
const desc = new TableDescription()
|
|
165
|
+
.withColumns(new Column("collection", Types.UTF8), new Column("file_path", Types.UTF8), new Column("point_id", Types.UTF8))
|
|
166
|
+
.withPrimaryKeys("collection", "file_path", "point_id");
|
|
167
|
+
desc.withPartitioningSettings({
|
|
168
|
+
partitioningByLoad: Ydb.FeatureFlag.Status.ENABLED,
|
|
169
|
+
partitioningBySize: Ydb.FeatureFlag.Status.ENABLED,
|
|
170
|
+
partitionSizeMb: 100,
|
|
171
|
+
});
|
|
172
|
+
try {
|
|
173
|
+
await s.createTable(POINTS_BY_FILE_LOOKUP_TABLE, desc);
|
|
174
|
+
logger.info(`created points-by-file lookup table ${POINTS_BY_FILE_LOOKUP_TABLE}`);
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
catch (createErr) {
|
|
178
|
+
if (!isAlreadyExistsError(createErr)) {
|
|
179
|
+
throw createErr;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
tableDescription = await s.describeTable(POINTS_BY_FILE_LOOKUP_TABLE);
|
|
183
|
+
}
|
|
184
|
+
const columns = tableDescription.columns ?? [];
|
|
185
|
+
const hasCollection = columns.some((col) => col.name === "collection");
|
|
186
|
+
const hasFilePath = columns.some((col) => col.name === "file_path");
|
|
187
|
+
const hasPointId = columns.some((col) => col.name === "point_id");
|
|
188
|
+
if (!hasCollection) {
|
|
189
|
+
throwMigrationRequired(`Points-by-file lookup table ${POINTS_BY_FILE_LOOKUP_TABLE} is missing required column collection; please recreate the table before starting the service`);
|
|
190
|
+
}
|
|
191
|
+
if (!hasFilePath) {
|
|
192
|
+
throwMigrationRequired(`Points-by-file lookup table ${POINTS_BY_FILE_LOOKUP_TABLE} is missing required column file_path; please recreate the table before starting the service`);
|
|
193
|
+
}
|
|
194
|
+
if (!hasPointId) {
|
|
195
|
+
throwMigrationRequired(`Points-by-file lookup table ${POINTS_BY_FILE_LOOKUP_TABLE} is missing required column point_id; please recreate the table before starting the service`);
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
pointsByFileTableReady = true;
|
|
199
|
+
}
|
|
200
|
+
export async function ensurePointsByFileTable() {
|
|
201
|
+
if (pointsByFileTableReady) {
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
if (pointsByFileTableReadyInFlight) {
|
|
205
|
+
await pointsByFileTableReadyInFlight;
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
pointsByFileTableReadyInFlight = ensurePointsByFileTableOnce();
|
|
209
|
+
try {
|
|
210
|
+
await pointsByFileTableReadyInFlight;
|
|
211
|
+
}
|
|
212
|
+
finally {
|
|
213
|
+
pointsByFileTableReadyInFlight = null;
|
|
214
|
+
}
|
|
215
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ydb-qdrant",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "9.0.3",
|
|
4
4
|
"main": "dist/package/api.js",
|
|
5
5
|
"types": "dist/package/api.d.ts",
|
|
6
6
|
"exports": {
|
|
@@ -16,13 +16,16 @@
|
|
|
16
16
|
"scripts": {
|
|
17
17
|
"test": "vitest run --exclude \"test/integration/**\"",
|
|
18
18
|
"test:coverage": "vitest run --coverage --exclude \"test/integration/**\"",
|
|
19
|
-
"test:integration": "vitest run test/integration/YdbRealIntegration.test.ts &&
|
|
20
|
-
"test:
|
|
19
|
+
"test:integration": "vitest run test/integration/YdbRealIntegration.test.ts && vitest run test/integration/YdbRealIntegration.one-table.test.ts",
|
|
20
|
+
"test:e2e": "vitest run --config=vitest.config.e2e.mts",
|
|
21
|
+
"test:recall": "vitest run test/integration/YdbRealIntegration.one-table.test.ts",
|
|
21
22
|
"load:soak": "k6 run loadtest/soak-test.js",
|
|
22
23
|
"load:stress": "k6 run loadtest/stress-test.js",
|
|
24
|
+
"load:stress:pathsegments": "k6 run loadtest/pathsegments-stress.js",
|
|
23
25
|
"build": "tsc -p tsconfig.json",
|
|
24
26
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
25
27
|
"dev": "tsx watch src/index.ts",
|
|
28
|
+
"start:e2e:app": "tsx src/index.ts",
|
|
26
29
|
"start": "node --experimental-specifier-resolution=node --enable-source-maps dist/index.js",
|
|
27
30
|
"smoke": "npm run build && node --experimental-specifier-resolution=node --enable-source-maps dist/SmokeTest.js",
|
|
28
31
|
"lint": "eslint \"src/**/*.ts\" \"test/**/*.ts\"",
|
|
@@ -32,7 +35,7 @@
|
|
|
32
35
|
"keywords": [
|
|
33
36
|
"ydb",
|
|
34
37
|
"vector-search",
|
|
35
|
-
"
|
|
38
|
+
"nearest-neighbor",
|
|
36
39
|
"qdrant-compatible",
|
|
37
40
|
"embeddings",
|
|
38
41
|
"nodejs",
|
|
@@ -49,7 +52,7 @@
|
|
|
49
52
|
],
|
|
50
53
|
"author": "",
|
|
51
54
|
"license": "Apache-2.0",
|
|
52
|
-
"description": "Qdrant-compatible Node.js/TypeScript API that stores/searches embeddings in YDB using a global one-table layout with exact
|
|
55
|
+
"description": "Qdrant-compatible Node.js/TypeScript API that stores/searches embeddings in YDB using a global one-table layout with exact KNN search over serialized vectors.",
|
|
53
56
|
"repository": {
|
|
54
57
|
"type": "git",
|
|
55
58
|
"url": "git+https://github.com/astandrik/ydb-qdrant.git"
|
|
@@ -69,8 +72,10 @@
|
|
|
69
72
|
"@yandex-cloud/nodejs-sdk": "^2.9.0",
|
|
70
73
|
"dotenv": "^17.2.3",
|
|
71
74
|
"express": "^5.1.0",
|
|
75
|
+
"fast-json-stable-stringify": "^2.1.0",
|
|
72
76
|
"nice-grpc": "^2.1.13",
|
|
73
77
|
"pino": "^10.1.0",
|
|
78
|
+
"piscina": "^5.1.4",
|
|
74
79
|
"ydb-sdk": "^5.11.1",
|
|
75
80
|
"zod": "^4.1.12"
|
|
76
81
|
},
|