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.
Files changed (87) hide show
  1. package/README.md +20 -18
  2. package/dist/SmokeTest.js +2 -2
  3. package/dist/compute/ComputePool.d.ts +5 -0
  4. package/dist/compute/ComputePool.js +64 -0
  5. package/dist/compute/ComputeWorker.d.ts +36 -0
  6. package/dist/compute/ComputeWorker.js +84 -0
  7. package/dist/config/env.d.ts +24 -7
  8. package/dist/config/env.js +65 -35
  9. package/dist/index.d.ts +2 -0
  10. package/dist/index.js +92 -2
  11. package/dist/logging/DeployLogFormatter.d.ts +2 -0
  12. package/dist/logging/DeployLogFormatter.js +131 -0
  13. package/dist/logging/logger.js +13 -1
  14. package/dist/logging/requestContext.d.ts +17 -0
  15. package/dist/logging/requestContext.js +43 -0
  16. package/dist/middleware/requestLogger.js +134 -6
  17. package/dist/middleware/upsertBodyPhase.d.ts +6 -0
  18. package/dist/middleware/upsertBodyPhase.js +184 -0
  19. package/dist/middleware/upsertRequestTimeout.d.ts +16 -0
  20. package/dist/middleware/upsertRequestTimeout.js +158 -0
  21. package/dist/package/api.d.ts +20 -12
  22. package/dist/package/api.js +57 -28
  23. package/dist/qdrant/QdrantRestTypes.d.ts +4 -0
  24. package/dist/qdrant/Requests.d.ts +97 -0
  25. package/dist/qdrant/Requests.js +72 -0
  26. package/dist/repositories/collectionsRepo.d.ts +18 -2
  27. package/dist/repositories/collectionsRepo.js +103 -7
  28. package/dist/repositories/collectionsRepo.one-table.d.ts +4 -3
  29. package/dist/repositories/collectionsRepo.one-table.js +99 -36
  30. package/dist/repositories/collectionsRepo.shared.d.ts +2 -2
  31. package/dist/repositories/collectionsRepo.shared.js +9 -4
  32. package/dist/repositories/pointsRepo.d.ts +6 -4
  33. package/dist/repositories/pointsRepo.js +8 -7
  34. package/dist/repositories/pointsRepo.one-table/Delete.d.ts +2 -2
  35. package/dist/repositories/pointsRepo.one-table/Delete.js +157 -60
  36. package/dist/repositories/pointsRepo.one-table/PathSegmentsFilter.d.ts +7 -5
  37. package/dist/repositories/pointsRepo.one-table/PathSegmentsFilter.js +44 -13
  38. package/dist/repositories/pointsRepo.one-table/Retrieve.d.ts +6 -0
  39. package/dist/repositories/pointsRepo.one-table/Retrieve.js +69 -0
  40. package/dist/repositories/pointsRepo.one-table/Search.d.ts +2 -3
  41. package/dist/repositories/pointsRepo.one-table/Search.js +102 -124
  42. package/dist/repositories/pointsRepo.one-table/Upsert.d.ts +2 -2
  43. package/dist/repositories/pointsRepo.one-table/Upsert.js +244 -48
  44. package/dist/repositories/pointsRepo.one-table.d.ts +1 -0
  45. package/dist/repositories/pointsRepo.one-table.js +1 -0
  46. package/dist/routes/collections.js +45 -36
  47. package/dist/routes/points.js +144 -59
  48. package/dist/server.js +42 -6
  49. package/dist/services/CollectionService.d.ts +7 -5
  50. package/dist/services/CollectionService.js +12 -9
  51. package/dist/services/CollectionService.one-table.js +1 -2
  52. package/dist/services/CollectionService.shared.d.ts +6 -5
  53. package/dist/services/CollectionService.shared.js +28 -12
  54. package/dist/services/PointsService.d.ts +8 -0
  55. package/dist/services/PointsService.js +132 -15
  56. package/dist/types.d.ts +4 -94
  57. package/dist/types.js +1 -54
  58. package/dist/utils/EnvParsers.d.ts +5 -0
  59. package/dist/utils/EnvParsers.js +30 -0
  60. package/dist/utils/PayloadSign.d.ts +4 -0
  61. package/dist/utils/PayloadSign.js +18 -0
  62. package/dist/utils/distance.d.ts +1 -12
  63. package/dist/utils/distance.js +0 -21
  64. package/dist/utils/pathPrefix.d.ts +3 -0
  65. package/dist/utils/pathPrefix.js +47 -0
  66. package/dist/utils/prefixExpansion.d.ts +1 -0
  67. package/dist/utils/prefixExpansion.js +11 -0
  68. package/dist/utils/qdrantResponse.d.ts +13 -0
  69. package/dist/utils/qdrantResponse.js +12 -0
  70. package/dist/utils/requestIdentity.d.ts +8 -0
  71. package/dist/utils/requestIdentity.js +52 -0
  72. package/dist/utils/retry.d.ts +2 -0
  73. package/dist/utils/retry.js +55 -11
  74. package/dist/utils/tenant.d.ts +12 -6
  75. package/dist/utils/tenant.js +41 -32
  76. package/dist/utils/vectorBinary.d.ts +0 -1
  77. package/dist/utils/vectorBinary.js +0 -98
  78. package/dist/utils/ydbErrors.d.ts +1 -0
  79. package/dist/utils/ydbErrors.js +14 -0
  80. package/dist/ydb/bootstrapMetaTable.js +14 -2
  81. package/dist/ydb/client.d.ts +10 -2
  82. package/dist/ydb/client.js +83 -24
  83. package/dist/ydb/helpers.d.ts +0 -1
  84. package/dist/ydb/helpers.js +1 -2
  85. package/dist/ydb/schema.d.ts +2 -0
  86. package/dist/ydb/schema.js +84 -7
  87. package/package.json +10 -5
@@ -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("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))
93
- .withPrimaryKeys("uid", "point_id");
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, require the new embedding_quantized column.
121
+ // Table exists: validate required columns for the current schema.
115
122
  const columns = tableDescription.columns ?? [];
116
- const hasEmbeddingQuantized = columns.some((col) => col.name === "embedding_quantized");
117
- if (!hasEmbeddingQuantized) {
118
- throwMigrationRequired(`Global points table ${GLOBAL_POINTS_TABLE} is missing required column embedding_quantized; please recreate the table or apply a manual schema migration before starting the service`);
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": "8.1.1",
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 && YDB_QDRANT_SEARCH_MODE=approximate vitest run test/integration/YdbRealIntegration.one-table.test.ts && YDB_QDRANT_SEARCH_MODE=exact vitest run test/integration/YdbRealIntegration.one-table.test.ts",
20
- "test:recall": "YDB_QDRANT_SEARCH_MODE=approximate vitest run test/integration/YdbRealIntegration.one-table.test.ts && YDB_QDRANT_SEARCH_MODE=exact vitest run test/integration/YdbRealIntegration.one-table.test.ts",
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
- "approximate-nearest-neighbor",
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 and approximate KNN search over serialized vectors.",
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
  },