@milaboratories/pl-middle-layer 1.38.0 → 1.39.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.
@@ -38,7 +38,10 @@ import {
38
38
  extractAllColumns,
39
39
  mapDataInfo,
40
40
  isDataInfo,
41
+ ensureError,
41
42
  } from '@platforma-sdk/model';
43
+ import { LRUCache } from 'lru-cache';
44
+ import type { UnrefFn } from './ref_count_pool';
42
45
  import { RefCountResourcePool } from './ref_count_pool';
43
46
  import { allBlobs, makeDataInfoFromPColumnValues, mapBlobs, parseDataInfoResource } from './data';
44
47
  import { createHash } from 'node:crypto';
@@ -50,6 +53,8 @@ import * as fsp from 'node:fs/promises';
50
53
  import * as path from 'node:path';
51
54
  import { getDebugFlags } from '../debug';
52
55
 
56
+ export type PColumnDataUniversal = PlTreeNodeAccessor | DataInfo<PlTreeNodeAccessor> | PColumnValues;
57
+
53
58
  function blobKey(res: ResourceInfo): string {
54
59
  return String(res.id);
55
60
  }
@@ -81,18 +86,22 @@ function migrateFilters(filters: PTableRecordFilter[]): PTableRecordFilter[] {
81
86
  return filtersV2;
82
87
  }
83
88
 
84
- function migratePTableDef(
85
- def: PTableDef<PColumn<PlTreeNodeAccessor | PColumnValues | DataInfo<PlTreeNodeAccessor>>>,
86
- ): PTableDef<PColumn<PlTreeNodeAccessor | PColumnValues | DataInfo<PlTreeNodeAccessor>>> {
87
- if (!('partitionFilters' in (def as Omit<typeof def, 'partitionFilters'>))) {
88
- // For old blocks make all filters partition filters
89
+ function migratePTableFilters<T>(
90
+ def: Omit<PTableDef<T>, 'partitionFilters'> | PTableDef<T>,
91
+ ): PTableDef<T> {
92
+ if (!('partitionFilters' in def)) {
93
+ // For old blocks assume all axes filters to be partition filters
89
94
  return {
90
95
  ...def,
91
- partitionFilters: def.filters,
92
- filters: [],
96
+ partitionFilters: migrateFilters(def.filters.filter((f) => f.column.type === 'axis')),
97
+ filters: migrateFilters(def.filters.filter((f) => f.column.type === 'column')),
93
98
  };
94
99
  }
95
- return def;
100
+ return {
101
+ ...def,
102
+ partitionFilters: migrateFilters(def.partitionFilters),
103
+ filters: migrateFilters(def.filters),
104
+ };
96
105
  }
97
106
 
98
107
  const bigintReplacer = (_: string, v: unknown) => (typeof v === 'bigint' ? v.toString() : v);
@@ -100,6 +109,7 @@ const bigintReplacer = (_: string, v: unknown) => (typeof v === 'bigint' ? v.toS
100
109
  class PFrameHolder implements PFrameInternal.PFrameDataSource, Disposable {
101
110
  public readonly pFrame: PFrameInternal.PFrameV7;
102
111
  private readonly abortController = new AbortController();
112
+ private readonly pTableCache: LRUCache<PTableHandle, UnrefFn>;
103
113
  private readonly blobIdToResource = new Map<string, ResourceInfo>();
104
114
  private readonly blobHandleComputables = new Map<
105
115
  string,
@@ -141,6 +151,11 @@ class PFrameHolder implements PFrameInternal.PFrameDataSource, Disposable {
141
151
  `Rust PFrame creation failed, columns: ${JSON.stringify(distinctСolumns)}, error: ${err as Error}`,
142
152
  );
143
153
  }
154
+
155
+ this.pTableCache = new LRUCache<PTableHandle, UnrefFn>({
156
+ max: 5, // TODO: calculate size on disk, not number of PTables
157
+ dispose: (unref) => unref(),
158
+ });
144
159
  }
145
160
 
146
161
  private getOrCreateComputableForBlob(blobId: string) {
@@ -173,26 +188,38 @@ class PFrameHolder implements PFrameInternal.PFrameDataSource, Disposable {
173
188
  return this.abortController.signal;
174
189
  }
175
190
 
191
+ public cache(handle: PTableHandle, unref: UnrefFn): void {
192
+ this.pTableCache.set(handle, unref);
193
+ }
194
+
176
195
  [Symbol.dispose](): void {
177
196
  this.abortController.abort();
197
+ this.pTableCache.clear();
178
198
  for (const computable of this.blobHandleComputables.values()) computable.resetState();
179
199
  this.pFrame.dispose();
180
200
  }
181
201
  }
182
202
 
183
203
  class PTableHolder implements Disposable {
204
+ private readonly abortController = new AbortController();
205
+ private readonly combinedDisposeSignal: AbortSignal;
206
+
184
207
  constructor(
208
+ pFrameDisposeSignal: AbortSignal,
185
209
  public readonly pTable: PFrameInternal.PTableV5,
186
- private readonly abortController: AbortController,
187
- ) {}
210
+ private readonly unrefPredecessor?: UnrefFn,
211
+ ) {
212
+ this.combinedDisposeSignal = AbortSignal.any([pFrameDisposeSignal, this.abortController.signal]);
213
+ }
188
214
 
189
215
  public get disposeSignal(): AbortSignal {
190
- return this.abortController.signal;
216
+ return this.combinedDisposeSignal;
191
217
  }
192
218
 
193
219
  [Symbol.dispose](): void {
194
220
  this.abortController.abort();
195
221
  this.pTable.dispose();
222
+ this.unrefPredecessor?.();
196
223
  }
197
224
  }
198
225
 
@@ -217,13 +244,13 @@ export interface InternalPFrameDriver extends SdkPFrameDriver {
217
244
 
218
245
  /** Create a new PFrame */
219
246
  createPFrame(
220
- def: PFrameDef<PlTreeNodeAccessor | PColumnValues | DataInfo<PlTreeNodeAccessor>>,
247
+ def: PFrameDef<PColumnDataUniversal>,
221
248
  ctx: ComputableCtx,
222
249
  ): PFrameHandle;
223
250
 
224
251
  /** Create a new PTable */
225
252
  createPTable(
226
- def: PTableDef<PColumn<PlTreeNodeAccessor | PColumnValues | DataInfo<PlTreeNodeAccessor>>>,
253
+ def: PTableDef<PColumn<PColumnDataUniversal>>,
227
254
  ctx: ComputableCtx,
228
255
  ): PTableHandle;
229
256
 
@@ -326,31 +353,47 @@ export class PFrameDriver implements InternalPFrameDriver {
326
353
  }
327
354
 
328
355
  protected createNewResource(params: FullPTableDef): PTableHolder {
329
- const handle: PFrameHandle = params.pFrameHandle;
330
356
  if (getDebugFlags().logPFrameRequests) {
331
357
  logger.info(
332
358
  `PTable creation (pTableHandle = ${this.calculateParamsKey(params)}): ${JSON.stringify(params, bigintReplacer)}`,
333
359
  );
334
360
  }
335
361
 
336
- const pFrameHolder = this.pFrames.getByKey(handle);
337
- const abortController = new AbortController();
362
+ const handle = params.pFrameHandle;
363
+ const { pFrame, disposeSignal } = this.pFrames.getByKey(handle);
338
364
 
339
- const table = pFrameHolder.pFrame.createTable({
340
- src: joinEntryToInternal(params.def.src),
341
- filters: migrateFilters([...params.def.partitionFilters, ...params.def.filters]),
342
- });
343
-
344
- let sortedTable = table;
365
+ // 3. Sort
345
366
  if (params.def.sorting.length > 0) {
346
- try {
347
- sortedTable = table.sort(params.def.sorting);
348
- } finally {
349
- table.dispose();
350
- }
367
+ const { resource: { pTable }, unref } = this.acquire({
368
+ ...params,
369
+ def: {
370
+ ...params.def,
371
+ sorting: [],
372
+ },
373
+ });
374
+ const sortedTable = pTable.sort(params.def.sorting);
375
+ return new PTableHolder(disposeSignal, sortedTable, unref);
351
376
  }
352
377
 
353
- return new PTableHolder(sortedTable, abortController);
378
+ // 2. Filter
379
+ if (params.def.filters.length > 0) {
380
+ const { resource: { pTable }, unref } = this.acquire({
381
+ ...params,
382
+ def: {
383
+ ...params.def,
384
+ filters: [],
385
+ },
386
+ });
387
+ const filteredTable = pTable.filter(params.def.filters);
388
+ return new PTableHolder(disposeSignal, filteredTable, unref);
389
+ }
390
+
391
+ // 1. Join
392
+ const table = pFrame.createTable({
393
+ src: joinEntryToInternal(params.def.src),
394
+ filters: params.def.partitionFilters,
395
+ });
396
+ return new PTableHolder(disposeSignal, table);
354
397
  }
355
398
 
356
399
  protected calculateParamsKey(params: FullPTableDef): string {
@@ -364,7 +407,7 @@ export class PFrameDriver implements InternalPFrameDriver {
364
407
  //
365
408
 
366
409
  public createPFrame(
367
- def: PFrameDef<PlTreeNodeAccessor | PColumnValues | DataInfo<PlTreeNodeAccessor>>,
410
+ def: PFrameDef<PColumnDataUniversal>,
368
411
  ctx: ComputableCtx,
369
412
  ): PFrameHandle {
370
413
  const internalData = def
@@ -384,10 +427,10 @@ export class PFrameDriver implements InternalPFrameDriver {
384
427
  }
385
428
 
386
429
  public createPTable(
387
- rawDef: PTableDef<PColumn<PlTreeNodeAccessor | PColumnValues | DataInfo<PlTreeNodeAccessor>>>,
430
+ rawDef: PTableDef<PColumn<PColumnDataUniversal>>,
388
431
  ctx: ComputableCtx,
389
432
  ): PTableHandle {
390
- const def = migratePTableDef(rawDef);
433
+ const def = migratePTableFilters(rawDef);
391
434
  const pFrameHandle = this.createPFrame(extractAllColumns(def.src), ctx);
392
435
  const defIds = mapPTableDef(def, (c) => c.id);
393
436
  const res = this.pTables.acquire({ def: defIds, pFrameHandle });
@@ -424,7 +467,8 @@ export class PFrameDriver implements InternalPFrameDriver {
424
467
  }]
425
468
  : [],
426
469
  };
427
- const responce = await this.pFrames.getByKey(handle).pFrame.findColumns(iRequest);
470
+ const { pFrame } = this.pFrames.getByKey(handle);
471
+ const responce = await pFrame.findColumns(iRequest);
428
472
  return {
429
473
  hits: responce.hits
430
474
  .filter((h) => // only exactly matching columns
@@ -437,11 +481,13 @@ export class PFrameDriver implements InternalPFrameDriver {
437
481
  }
438
482
 
439
483
  public async getColumnSpec(handle: PFrameHandle, columnId: PObjectId): Promise<PColumnSpec> {
440
- return await this.pFrames.getByKey(handle).pFrame.getColumnSpec(columnId);
484
+ const { pFrame } = this.pFrames.getByKey(handle);
485
+ return await pFrame.getColumnSpec(columnId);
441
486
  }
442
487
 
443
488
  public async listColumns(handle: PFrameHandle): Promise<PColumnIdAndSpec[]> {
444
- return await this.pFrames.getByKey(handle).pFrame.listColumns();
489
+ const { pFrame } = this.pFrames.getByKey(handle);
490
+ return await pFrame.listColumns();
445
491
  }
446
492
 
447
493
  public async calculateTableData(
@@ -455,36 +501,24 @@ export class PFrameDriver implements InternalPFrameDriver {
455
501
  `Call calculateTableData, handle = ${handle}, request = ${JSON.stringify(request, bigintReplacer)}`,
456
502
  );
457
503
  }
458
- return await this.frameConcurrencyLimiter.run(async () => {
459
- const pFrameHolder = this.pFrames.getByKey(handle);
460
- const combinedSignal = AbortSignal.any([signal, pFrameHolder.disposeSignal].filter((s) => !!s));
461
- const table = pFrameHolder.pFrame.createTable({
462
- src: joinEntryToInternal(request.src),
463
- filters: migrateFilters(request.filters),
464
- });
465
504
 
466
- let sortedTable = table;
467
- if (request.sorting.length > 0) {
468
- try {
469
- sortedTable = table.sort(request.sorting);
470
- } finally {
471
- table.dispose();
472
- }
473
- }
505
+ const { key: pTableHandle, resource: { pTable, disposeSignal }, unref } = this.pTables.acquire({
506
+ pFrameHandle: handle,
507
+ def: migratePTableFilters(request),
508
+ });
509
+ const combinedSignal = AbortSignal.any([signal, disposeSignal].filter((s) => !!s));
474
510
 
475
- try {
476
- const spec = sortedTable.getSpec();
477
- const data = await sortedTable.getData([...spec.keys()], {
478
- range,
479
- signal: combinedSignal,
480
- });
481
- return spec.map((spec, i) => ({
482
- spec: spec,
483
- data: data[i],
484
- }));
485
- } finally {
486
- sortedTable.dispose();
487
- }
511
+ return await this.frameConcurrencyLimiter.run(async () => {
512
+ this.pFrames.getByKey(handle).cache(pTableHandle as PTableHandle, unref);
513
+ const spec = pTable.getSpec();
514
+ const data = await pTable.getData([...spec.keys()], {
515
+ range,
516
+ signal: combinedSignal,
517
+ });
518
+ return spec.map((spec, i) => ({
519
+ spec: spec,
520
+ data: data[i],
521
+ }));
488
522
  });
489
523
  }
490
524
 
@@ -498,10 +532,12 @@ export class PFrameDriver implements InternalPFrameDriver {
498
532
  `Call getUniqueValues, handle = ${handle}, request = ${JSON.stringify(request, bigintReplacer)}`,
499
533
  );
500
534
  }
535
+
536
+ const { pFrame, disposeSignal } = this.pFrames.getByKey(handle);
537
+ const combinedSignal = AbortSignal.any([signal, disposeSignal].filter((s) => !!s));
538
+
501
539
  return await this.frameConcurrencyLimiter.run(async () => {
502
- const pFrameHolder = this.pFrames.getByKey(handle);
503
- const combinedSignal = AbortSignal.any([signal, pFrameHolder.disposeSignal].filter((s) => !!s));
504
- return await pFrameHolder.pFrame.getUniqueValues({
540
+ return await pFrame.getUniqueValues({
505
541
  ...request,
506
542
  filters: migrateFilters(request.filters),
507
543
  }, {
@@ -515,15 +551,21 @@ export class PFrameDriver implements InternalPFrameDriver {
515
551
  //
516
552
 
517
553
  public getSpec(handle: PTableHandle): Promise<PTableColumnSpec[]> {
518
- const pTable = this.pTables.getByKey(handle).pTable;
519
- return Promise.resolve(pTable.getSpec());
554
+ const { pTable } = this.pTables.getByKey(handle);
555
+ // TODO: use Promise.try after Electron update
556
+ try {
557
+ return Promise.resolve(pTable.getSpec());
558
+ } catch (err: unknown) {
559
+ return Promise.reject(ensureError(err));
560
+ }
520
561
  }
521
562
 
522
563
  public async getShape(handle: PTableHandle, signal?: AbortSignal): Promise<PTableShape> {
564
+ const { pTable, disposeSignal } = this.pTables.getByKey(handle);
565
+ const combinedSignal = AbortSignal.any([signal, disposeSignal].filter((s) => !!s));
566
+
523
567
  return await this.tableConcurrencyLimiter.run(async () => {
524
- const pTableHolder = this.pTables.getByKey(handle);
525
- const combinedSignal = AbortSignal.any([signal, pTableHolder.disposeSignal].filter((s) => !!s));
526
- return await pTableHolder.pTable.getShape({
568
+ return await pTable.getShape({
527
569
  signal: combinedSignal,
528
570
  });
529
571
  });
@@ -535,10 +577,11 @@ export class PFrameDriver implements InternalPFrameDriver {
535
577
  range: TableRange | undefined,
536
578
  signal?: AbortSignal,
537
579
  ): Promise<PTableVector[]> {
580
+ const { pTable, disposeSignal } = this.pTables.getByKey(handle);
581
+ const combinedSignal = AbortSignal.any([signal, disposeSignal].filter((s) => !!s));
582
+
538
583
  return await this.tableConcurrencyLimiter.run(async () => {
539
- const pTableHolder = this.pTables.getByKey(handle);
540
- const combinedSignal = AbortSignal.any([signal, pTableHolder.disposeSignal].filter((s) => !!s));
541
- return await pTableHolder.pTable.getData(columnIndices, {
584
+ return await pTable.getData(columnIndices, {
542
585
  range,
543
586
  signal: combinedSignal,
544
587
  });