@naturalcycles/datastore-lib 3.14.1 → 3.16.2
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/CHANGELOG.md +28 -0
- package/dist/DatastoreStreamReadable.d.ts +21 -0
- package/dist/DatastoreStreamReadable.js +90 -0
- package/dist/datastore.db.d.ts +4 -4
- package/dist/datastore.db.js +12 -9
- package/dist/datastore.model.d.ts +40 -1
- package/dist/index.d.ts +2 -2
- package/package.json +1 -1
- package/src/DatastoreStreamReadable.ts +120 -0
- package/src/datastore.db.ts +27 -17
- package/src/datastore.model.ts +46 -1
- package/src/datastoreKeyValueDB.ts +1 -1
- package/src/index.ts +6 -0
- package/src/query.util.ts +9 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,31 @@
|
|
|
1
|
+
## [3.16.2](https://github.com/NaturalCycles/datastore-lib/compare/v3.16.1...v3.16.2) (2021-10-17)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* adapt to latest db-lib with typed queries ([21d31ea](https://github.com/NaturalCycles/datastore-lib/commit/21d31ea7343ca0b2590950c067f6ce472dc82501))
|
|
7
|
+
|
|
8
|
+
## [3.16.1](https://github.com/NaturalCycles/datastore-lib/compare/v3.16.0...v3.16.1) (2021-10-07)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Bug Fixes
|
|
12
|
+
|
|
13
|
+
* logging ([961b485](https://github.com/NaturalCycles/datastore-lib/commit/961b485a0ff1c0268bfc491e9f7a608a1b2b77da))
|
|
14
|
+
|
|
15
|
+
# [3.16.0](https://github.com/NaturalCycles/datastore-lib/compare/v3.15.0...v3.16.0) (2021-10-07)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
### Features
|
|
19
|
+
|
|
20
|
+
* allow to set streamOptions in DatastoreCfg ([c9fb5f0](https://github.com/NaturalCycles/datastore-lib/commit/c9fb5f08760f113d3b563ce80579df189d06b7b1))
|
|
21
|
+
|
|
22
|
+
# [3.15.0](https://github.com/NaturalCycles/datastore-lib/compare/v3.14.1...v3.15.0) (2021-10-07)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
### Features
|
|
26
|
+
|
|
27
|
+
* experimentalCursorStream mode to fix backpressure ([419f8e1](https://github.com/NaturalCycles/datastore-lib/commit/419f8e16ccb9722771a01b90001ae19f2a7a8595))
|
|
28
|
+
|
|
1
29
|
## [3.14.1](https://github.com/NaturalCycles/datastore-lib/compare/v3.14.0...v3.14.1) (2021-10-04)
|
|
2
30
|
|
|
3
31
|
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
import { Readable } from 'stream';
|
|
3
|
+
import { Query } from '@google-cloud/datastore';
|
|
4
|
+
import type { ReadableTyped } from '@naturalcycles/nodejs-lib';
|
|
5
|
+
import type { DatastoreDBStreamOptions } from './datastore.model';
|
|
6
|
+
export declare class DatastoreStreamReadable<T = any> extends Readable implements ReadableTyped<T> {
|
|
7
|
+
private q;
|
|
8
|
+
private originalLimit;
|
|
9
|
+
private rowsRetrieved;
|
|
10
|
+
private endCursor?;
|
|
11
|
+
private running;
|
|
12
|
+
private done;
|
|
13
|
+
private lastQueryDone?;
|
|
14
|
+
private totalWait;
|
|
15
|
+
private log;
|
|
16
|
+
private opt;
|
|
17
|
+
constructor(q: Query, opt: DatastoreDBStreamOptions);
|
|
18
|
+
private runNextQuery;
|
|
19
|
+
count: number;
|
|
20
|
+
_read(): void;
|
|
21
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DatastoreStreamReadable = void 0;
|
|
4
|
+
const stream_1 = require("stream");
|
|
5
|
+
const js_lib_1 = require("@naturalcycles/js-lib");
|
|
6
|
+
class DatastoreStreamReadable extends stream_1.Readable {
|
|
7
|
+
constructor(q, opt) {
|
|
8
|
+
super({ objectMode: true });
|
|
9
|
+
this.q = q;
|
|
10
|
+
this.rowsRetrieved = 0;
|
|
11
|
+
this.running = false;
|
|
12
|
+
this.done = false;
|
|
13
|
+
this.totalWait = 0;
|
|
14
|
+
// private log = (...args: any[]): void => console.log(...args)
|
|
15
|
+
this.log = console.log.bind(console);
|
|
16
|
+
this.count = 0; // use for debugging
|
|
17
|
+
this.opt = {
|
|
18
|
+
rssLimitMB: 1000,
|
|
19
|
+
batchSize: 1000,
|
|
20
|
+
...opt,
|
|
21
|
+
};
|
|
22
|
+
if (!opt.debug)
|
|
23
|
+
this.log = () => { };
|
|
24
|
+
this.originalLimit = q.limitVal;
|
|
25
|
+
console.log(`!! using experimentalCursorStream !! batchSize: ${opt.batchSize}`);
|
|
26
|
+
}
|
|
27
|
+
async runNextQuery() {
|
|
28
|
+
if (this.done)
|
|
29
|
+
return;
|
|
30
|
+
if (this.lastQueryDone) {
|
|
31
|
+
const now = Date.now();
|
|
32
|
+
this.totalWait += now - this.lastQueryDone;
|
|
33
|
+
}
|
|
34
|
+
this.running = true;
|
|
35
|
+
// console.log('running query...')
|
|
36
|
+
let limit = this.opt.batchSize;
|
|
37
|
+
if (this.originalLimit) {
|
|
38
|
+
limit = Math.min(this.opt.batchSize, this.originalLimit - this.rowsRetrieved);
|
|
39
|
+
}
|
|
40
|
+
// console.log(`limit: ${limit}`)
|
|
41
|
+
let q = this.q.limit(limit);
|
|
42
|
+
if (this.endCursor) {
|
|
43
|
+
q = q.start(this.endCursor);
|
|
44
|
+
}
|
|
45
|
+
try {
|
|
46
|
+
const [rows, info] = await q.run();
|
|
47
|
+
this.rowsRetrieved += rows.length;
|
|
48
|
+
this.log(`got ${rows.length} rows, ${this.rowsRetrieved} rowsRetrieved, totalWait: ${(0, js_lib_1._ms)(this.totalWait)}`, info.moreResults);
|
|
49
|
+
this.endCursor = info.endCursor;
|
|
50
|
+
this.running = false; // ready to take more _reads
|
|
51
|
+
this.lastQueryDone = Date.now();
|
|
52
|
+
rows.forEach(row => this.push(row));
|
|
53
|
+
if (!info.endCursor ||
|
|
54
|
+
info.moreResults === 'NO_MORE_RESULTS' ||
|
|
55
|
+
(this.originalLimit && this.rowsRetrieved >= this.originalLimit)) {
|
|
56
|
+
this.log(`!!!! DONE! ${this.rowsRetrieved} rowsRetrieved, totalWait: ${(0, js_lib_1._ms)(this.totalWait)}`);
|
|
57
|
+
this.push(null);
|
|
58
|
+
this.done = true;
|
|
59
|
+
}
|
|
60
|
+
else if (this.opt.rssLimitMB) {
|
|
61
|
+
const rssMB = Math.round(process.memoryUsage().rss / 1024 / 1024);
|
|
62
|
+
if (rssMB <= this.opt.rssLimitMB) {
|
|
63
|
+
void this.runNextQuery();
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
this.log(`rssLimitMB reached ${rssMB} > ${this.opt.rssLimitMB}, pausing stream`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
catch (err) {
|
|
71
|
+
console.error('DatastoreStreamReadable error!\n', err);
|
|
72
|
+
this.emit('error', err);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
_read() {
|
|
76
|
+
// console.log(`_read called ${++this.count}, wasRunning: ${this.running}`) // debugging
|
|
77
|
+
this.count++;
|
|
78
|
+
if (this.running) {
|
|
79
|
+
this.log(`_read ${this.count}, wasRunning: true`);
|
|
80
|
+
}
|
|
81
|
+
if (this.done) {
|
|
82
|
+
console.warn(`!!! _read was called, but done==true`);
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
if (!this.running) {
|
|
86
|
+
void this.runNextQuery();
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
exports.DatastoreStreamReadable = DatastoreStreamReadable;
|
package/dist/datastore.db.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import type { Datastore, Key, Query } from '@google-cloud/datastore';
|
|
|
2
2
|
import { BaseCommonDB, CommonDB, DBQuery, DBTransaction, ObjectWithId, RunQueryResult } from '@naturalcycles/db-lib';
|
|
3
3
|
import { JsonSchemaRootObject } from '@naturalcycles/js-lib';
|
|
4
4
|
import { ReadableTyped } from '@naturalcycles/nodejs-lib';
|
|
5
|
-
import { DatastoreDBCfg, DatastoreDBOptions, DatastoreDBSaveOptions, DatastorePayload, DatastorePropertyStats, DatastoreStats } from './datastore.model';
|
|
5
|
+
import { DatastoreDBCfg, DatastoreDBOptions, DatastoreDBSaveOptions, DatastoreDBStreamOptions, DatastorePayload, DatastorePropertyStats, DatastoreStats } from './datastore.model';
|
|
6
6
|
/**
|
|
7
7
|
* Datastore API:
|
|
8
8
|
* https://googlecloudplatform.github.io/google-cloud-node/#/docs/datastore/1.0.3/datastore
|
|
@@ -23,12 +23,12 @@ export declare class DatastoreDB extends BaseCommonDB implements CommonDB {
|
|
|
23
23
|
runQuery<ROW extends ObjectWithId>(dbQuery: DBQuery<ROW>, _opt?: DatastoreDBOptions): Promise<RunQueryResult<ROW>>;
|
|
24
24
|
runQueryCount<ROW extends ObjectWithId>(dbQuery: DBQuery<ROW>, _opt?: DatastoreDBOptions): Promise<number>;
|
|
25
25
|
runDatastoreQuery<ROW extends ObjectWithId>(q: Query): Promise<RunQueryResult<ROW>>;
|
|
26
|
-
runQueryStream
|
|
27
|
-
streamQuery<ROW extends ObjectWithId>(dbQuery: DBQuery<ROW>,
|
|
26
|
+
private runQueryStream;
|
|
27
|
+
streamQuery<ROW extends ObjectWithId>(dbQuery: DBQuery<ROW>, opt?: DatastoreDBStreamOptions): ReadableTyped<ROW>;
|
|
28
28
|
/**
|
|
29
29
|
* Returns saved entities with generated id/updated/created (non-mutating!)
|
|
30
30
|
*/
|
|
31
|
-
saveBatch<ROW extends ObjectWithId>(table: string, rows: ROW[], opt?: DatastoreDBSaveOptions): Promise<void>;
|
|
31
|
+
saveBatch<ROW extends ObjectWithId>(table: string, rows: ROW[], opt?: DatastoreDBSaveOptions<ROW>): Promise<void>;
|
|
32
32
|
deleteByQuery<ROW extends ObjectWithId>(q: DBQuery<ROW>, opt?: DatastoreDBOptions): Promise<number>;
|
|
33
33
|
/**
|
|
34
34
|
* Limitation: Datastore's delete returns void, so we always return all ids here as "deleted"
|
package/dist/datastore.db.js
CHANGED
|
@@ -6,6 +6,7 @@ const db_lib_1 = require("@naturalcycles/db-lib");
|
|
|
6
6
|
const js_lib_1 = require("@naturalcycles/js-lib");
|
|
7
7
|
const colors_1 = require("@naturalcycles/nodejs-lib/dist/colors");
|
|
8
8
|
const datastore_model_1 = require("./datastore.model");
|
|
9
|
+
const DatastoreStreamReadable_1 = require("./DatastoreStreamReadable");
|
|
9
10
|
const query_util_1 = require("./query.util");
|
|
10
11
|
// Datastore (also Firestore and other Google APIs) supports max 500 of items when saving/deleting, etc.
|
|
11
12
|
const MAX_ITEMS = 500;
|
|
@@ -88,21 +89,23 @@ class DatastoreDB extends db_lib_1.BaseCommonDB {
|
|
|
88
89
|
rows,
|
|
89
90
|
};
|
|
90
91
|
}
|
|
91
|
-
runQueryStream(q) {
|
|
92
|
-
|
|
93
|
-
.
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
92
|
+
runQueryStream(q, _opt) {
|
|
93
|
+
const opt = {
|
|
94
|
+
...this.cfg.streamOptions,
|
|
95
|
+
..._opt,
|
|
96
|
+
};
|
|
97
|
+
return (opt.experimentalCursorStream
|
|
98
|
+
? new DatastoreStreamReadable_1.DatastoreStreamReadable(q, opt)
|
|
99
|
+
: this.ds().runQueryStream(q)).pipe(new stream_1.Transform({
|
|
97
100
|
objectMode: true,
|
|
98
101
|
transform: (chunk, _enc, cb) => {
|
|
99
102
|
cb(null, this.mapId(chunk));
|
|
100
103
|
},
|
|
101
|
-
}))
|
|
104
|
+
}));
|
|
102
105
|
}
|
|
103
|
-
streamQuery(dbQuery,
|
|
106
|
+
streamQuery(dbQuery, opt) {
|
|
104
107
|
const q = (0, query_util_1.dbQueryToDatastoreQuery)(dbQuery, this.ds().createQuery(dbQuery.table));
|
|
105
|
-
return this.runQueryStream(q);
|
|
108
|
+
return this.runQueryStream(q, opt);
|
|
106
109
|
}
|
|
107
110
|
// https://github.com/GoogleCloudPlatform/nodejs-getting-started/blob/master/2-structured-data/books/model-datastore.js
|
|
108
111
|
/**
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { DatastoreOptions, Key, Transaction } from '@google-cloud/datastore';
|
|
2
2
|
import { CommonDBOptions, CommonDBSaveOptions } from '@naturalcycles/db-lib';
|
|
3
|
+
import { AnyObjectWithId, ObjectWithId } from '@naturalcycles/db-lib/src/db.model';
|
|
3
4
|
export interface DatastorePayload<T = any> {
|
|
4
5
|
key: Key;
|
|
5
6
|
data: T;
|
|
@@ -24,6 +25,11 @@ export interface DatastoreDBCfg extends DatastoreOptions {
|
|
|
24
25
|
* Allows to set the old native library here, e.g `grpc: require('grpc')`
|
|
25
26
|
*/
|
|
26
27
|
grpc?: any;
|
|
28
|
+
/**
|
|
29
|
+
* Use it to set default options to stream operations,
|
|
30
|
+
* e.g you can globally enable `experimentalCursorStream` here, set the batchSize, etc.
|
|
31
|
+
*/
|
|
32
|
+
streamOptions?: DatastoreDBStreamOptions;
|
|
27
33
|
}
|
|
28
34
|
export interface DatastoreCredentials {
|
|
29
35
|
type?: string;
|
|
@@ -35,10 +41,43 @@ export interface DatastoreCredentials {
|
|
|
35
41
|
client_secret?: string;
|
|
36
42
|
refresh_token?: string;
|
|
37
43
|
}
|
|
44
|
+
export interface DatastoreDBStreamOptions extends DatastoreDBOptions {
|
|
45
|
+
/**
|
|
46
|
+
* Set to `true` to stream via experimental "cursor-query based stream".
|
|
47
|
+
*
|
|
48
|
+
* @default false
|
|
49
|
+
*/
|
|
50
|
+
experimentalCursorStream?: boolean;
|
|
51
|
+
/**
|
|
52
|
+
* Applicable to `experimentalCursorStream`
|
|
53
|
+
*
|
|
54
|
+
* @default 1000
|
|
55
|
+
*/
|
|
56
|
+
batchSize?: number;
|
|
57
|
+
/**
|
|
58
|
+
* Applicable to `experimentalCursorStream`
|
|
59
|
+
*
|
|
60
|
+
* Set to a value (number of Megabytes) to control the peak RSS size.
|
|
61
|
+
* If limit is reached - streaming will pause until the stream keeps up, and then
|
|
62
|
+
* resumes.
|
|
63
|
+
*
|
|
64
|
+
* Set to 0/undefined to disable. Stream will get "slow" then, cause it'll only run the query
|
|
65
|
+
* when _read is called.
|
|
66
|
+
*
|
|
67
|
+
* @default 1000
|
|
68
|
+
*/
|
|
69
|
+
rssLimitMB?: number;
|
|
70
|
+
/**
|
|
71
|
+
* Set to `true` to log additional debug info, when using experimentalCursorStream.
|
|
72
|
+
*
|
|
73
|
+
* @default false
|
|
74
|
+
*/
|
|
75
|
+
debug?: boolean;
|
|
76
|
+
}
|
|
38
77
|
export interface DatastoreDBOptions extends CommonDBOptions {
|
|
39
78
|
tx?: Transaction;
|
|
40
79
|
}
|
|
41
|
-
export interface DatastoreDBSaveOptions extends CommonDBSaveOptions {
|
|
80
|
+
export interface DatastoreDBSaveOptions<ROW extends ObjectWithId = AnyObjectWithId> extends CommonDBSaveOptions<ROW> {
|
|
42
81
|
tx?: Transaction;
|
|
43
82
|
}
|
|
44
83
|
export interface DatastoreStats {
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { DatastoreDB } from './datastore.db';
|
|
2
|
-
import { DatastoreCredentials, DatastoreDBCfg, DatastorePayload, DatastorePropertyStats, DatastoreStats, DatastoreType } from './datastore.model';
|
|
2
|
+
import { DatastoreCredentials, DatastoreDBCfg, DatastoreDBOptions, DatastoreDBSaveOptions, DatastoreDBStreamOptions, DatastorePayload, DatastorePropertyStats, DatastoreStats, DatastoreType } from './datastore.model';
|
|
3
3
|
import { DatastoreKeyValueDB, DatastoreKeyValueDBCfg } from './datastoreKeyValueDB';
|
|
4
4
|
import { getDBAdapter } from './dbAdapter';
|
|
5
|
-
export type { DatastorePayload, DatastoreDBCfg, DatastoreKeyValueDBCfg, DatastoreStats, DatastoreCredentials, DatastorePropertyStats, };
|
|
5
|
+
export type { DatastorePayload, DatastoreDBCfg, DatastoreKeyValueDBCfg, DatastoreStats, DatastoreCredentials, DatastorePropertyStats, DatastoreDBStreamOptions, DatastoreDBOptions, DatastoreDBSaveOptions, };
|
|
6
6
|
export { DatastoreDB, DatastoreType, getDBAdapter, DatastoreKeyValueDB };
|
package/package.json
CHANGED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { Readable } from 'stream'
|
|
2
|
+
import { Query } from '@google-cloud/datastore'
|
|
3
|
+
import { _ms } from '@naturalcycles/js-lib'
|
|
4
|
+
import type { ReadableTyped } from '@naturalcycles/nodejs-lib'
|
|
5
|
+
import type { DatastoreDBStreamOptions } from './datastore.model'
|
|
6
|
+
|
|
7
|
+
export class DatastoreStreamReadable<T = any> extends Readable implements ReadableTyped<T> {
|
|
8
|
+
private originalLimit: number
|
|
9
|
+
private rowsRetrieved = 0
|
|
10
|
+
private endCursor?: string
|
|
11
|
+
private running = false
|
|
12
|
+
private done = false
|
|
13
|
+
private lastQueryDone?: number
|
|
14
|
+
private totalWait = 0
|
|
15
|
+
|
|
16
|
+
// private log = (...args: any[]): void => console.log(...args)
|
|
17
|
+
private log = console.log.bind(console)
|
|
18
|
+
|
|
19
|
+
private opt: DatastoreDBStreamOptions & { batchSize: number }
|
|
20
|
+
|
|
21
|
+
constructor(private q: Query, opt: DatastoreDBStreamOptions) {
|
|
22
|
+
super({ objectMode: true })
|
|
23
|
+
|
|
24
|
+
this.opt = {
|
|
25
|
+
rssLimitMB: 1000,
|
|
26
|
+
batchSize: 1000,
|
|
27
|
+
...opt,
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (!opt.debug) this.log = () => {}
|
|
31
|
+
|
|
32
|
+
this.originalLimit = q.limitVal
|
|
33
|
+
|
|
34
|
+
console.log(`!! using experimentalCursorStream !! batchSize: ${opt.batchSize}`)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
private async runNextQuery(): Promise<void> {
|
|
38
|
+
if (this.done) return
|
|
39
|
+
|
|
40
|
+
if (this.lastQueryDone) {
|
|
41
|
+
const now = Date.now()
|
|
42
|
+
this.totalWait += now - this.lastQueryDone
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
this.running = true
|
|
46
|
+
// console.log('running query...')
|
|
47
|
+
|
|
48
|
+
let limit = this.opt.batchSize
|
|
49
|
+
|
|
50
|
+
if (this.originalLimit) {
|
|
51
|
+
limit = Math.min(this.opt.batchSize, this.originalLimit - this.rowsRetrieved)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// console.log(`limit: ${limit}`)
|
|
55
|
+
let q = this.q.limit(limit)
|
|
56
|
+
if (this.endCursor) {
|
|
57
|
+
q = q.start(this.endCursor)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
const [rows, info] = await q.run()
|
|
62
|
+
|
|
63
|
+
this.rowsRetrieved += rows.length
|
|
64
|
+
this.log(
|
|
65
|
+
`got ${rows.length} rows, ${this.rowsRetrieved} rowsRetrieved, totalWait: ${_ms(
|
|
66
|
+
this.totalWait,
|
|
67
|
+
)}`,
|
|
68
|
+
info.moreResults,
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
this.endCursor = info.endCursor
|
|
72
|
+
this.running = false // ready to take more _reads
|
|
73
|
+
this.lastQueryDone = Date.now()
|
|
74
|
+
|
|
75
|
+
rows.forEach(row => this.push(row))
|
|
76
|
+
|
|
77
|
+
if (
|
|
78
|
+
!info.endCursor ||
|
|
79
|
+
info.moreResults === 'NO_MORE_RESULTS' ||
|
|
80
|
+
(this.originalLimit && this.rowsRetrieved >= this.originalLimit)
|
|
81
|
+
) {
|
|
82
|
+
this.log(
|
|
83
|
+
`!!!! DONE! ${this.rowsRetrieved} rowsRetrieved, totalWait: ${_ms(this.totalWait)}`,
|
|
84
|
+
)
|
|
85
|
+
this.push(null)
|
|
86
|
+
this.done = true
|
|
87
|
+
} else if (this.opt.rssLimitMB) {
|
|
88
|
+
const rssMB = Math.round(process.memoryUsage().rss / 1024 / 1024)
|
|
89
|
+
|
|
90
|
+
if (rssMB <= this.opt.rssLimitMB) {
|
|
91
|
+
void this.runNextQuery()
|
|
92
|
+
} else {
|
|
93
|
+
this.log(`rssLimitMB reached ${rssMB} > ${this.opt.rssLimitMB}, pausing stream`)
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
} catch (err) {
|
|
97
|
+
console.error('DatastoreStreamReadable error!\n', err)
|
|
98
|
+
this.emit('error', err)
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
count = 0 // use for debugging
|
|
103
|
+
|
|
104
|
+
override _read(): void {
|
|
105
|
+
// console.log(`_read called ${++this.count}, wasRunning: ${this.running}`) // debugging
|
|
106
|
+
this.count++
|
|
107
|
+
if (this.running) {
|
|
108
|
+
this.log(`_read ${this.count}, wasRunning: true`)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (this.done) {
|
|
112
|
+
console.warn(`!!! _read was called, but done==true`)
|
|
113
|
+
return
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (!this.running) {
|
|
117
|
+
void this.runNextQuery()
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
package/src/datastore.db.ts
CHANGED
|
@@ -29,11 +29,13 @@ import {
|
|
|
29
29
|
DatastoreDBCfg,
|
|
30
30
|
DatastoreDBOptions,
|
|
31
31
|
DatastoreDBSaveOptions,
|
|
32
|
+
DatastoreDBStreamOptions,
|
|
32
33
|
DatastorePayload,
|
|
33
34
|
DatastorePropertyStats,
|
|
34
35
|
DatastoreStats,
|
|
35
36
|
DatastoreType,
|
|
36
37
|
} from './datastore.model'
|
|
38
|
+
import { DatastoreStreamReadable } from './DatastoreStreamReadable'
|
|
37
39
|
import { dbQueryToDatastoreQuery } from './query.util'
|
|
38
40
|
|
|
39
41
|
// Datastore (also Firestore and other Google APIs) supports max 500 of items when saving/deleting, etc.
|
|
@@ -154,29 +156,35 @@ export class DatastoreDB extends BaseCommonDB implements CommonDB {
|
|
|
154
156
|
}
|
|
155
157
|
}
|
|
156
158
|
|
|
157
|
-
runQueryStream<ROW extends ObjectWithId>(
|
|
159
|
+
private runQueryStream<ROW extends ObjectWithId>(
|
|
160
|
+
q: Query,
|
|
161
|
+
_opt?: DatastoreDBStreamOptions,
|
|
162
|
+
): ReadableTyped<ROW> {
|
|
163
|
+
const opt = {
|
|
164
|
+
...this.cfg.streamOptions,
|
|
165
|
+
..._opt,
|
|
166
|
+
}
|
|
167
|
+
|
|
158
168
|
return (
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
}),
|
|
170
|
-
)
|
|
169
|
+
opt.experimentalCursorStream
|
|
170
|
+
? new DatastoreStreamReadable(q, opt)
|
|
171
|
+
: this.ds().runQueryStream(q)
|
|
172
|
+
).pipe(
|
|
173
|
+
new Transform({
|
|
174
|
+
objectMode: true,
|
|
175
|
+
transform: (chunk, _enc, cb) => {
|
|
176
|
+
cb(null, this.mapId(chunk))
|
|
177
|
+
},
|
|
178
|
+
}),
|
|
171
179
|
)
|
|
172
180
|
}
|
|
173
181
|
|
|
174
182
|
override streamQuery<ROW extends ObjectWithId>(
|
|
175
183
|
dbQuery: DBQuery<ROW>,
|
|
176
|
-
|
|
184
|
+
opt?: DatastoreDBStreamOptions,
|
|
177
185
|
): ReadableTyped<ROW> {
|
|
178
186
|
const q = dbQueryToDatastoreQuery(dbQuery, this.ds().createQuery(dbQuery.table))
|
|
179
|
-
return this.runQueryStream(q)
|
|
187
|
+
return this.runQueryStream(q, opt)
|
|
180
188
|
}
|
|
181
189
|
|
|
182
190
|
// https://github.com/GoogleCloudPlatform/nodejs-getting-started/blob/master/2-structured-data/books/model-datastore.js
|
|
@@ -187,9 +195,11 @@ export class DatastoreDB extends BaseCommonDB implements CommonDB {
|
|
|
187
195
|
override async saveBatch<ROW extends ObjectWithId>(
|
|
188
196
|
table: string,
|
|
189
197
|
rows: ROW[],
|
|
190
|
-
opt: DatastoreDBSaveOptions = {},
|
|
198
|
+
opt: DatastoreDBSaveOptions<ROW> = {},
|
|
191
199
|
): Promise<void> {
|
|
192
|
-
const entities = rows.map(obj =>
|
|
200
|
+
const entities = rows.map(obj =>
|
|
201
|
+
this.toDatastoreEntity(table, obj, opt.excludeFromIndexes as string[]),
|
|
202
|
+
)
|
|
193
203
|
|
|
194
204
|
const save = pRetry(
|
|
195
205
|
async (batch: DatastorePayload<ROW>[]) => {
|
package/src/datastore.model.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { DatastoreOptions, Key, Transaction } from '@google-cloud/datastore'
|
|
2
2
|
import { CommonDBOptions, CommonDBSaveOptions } from '@naturalcycles/db-lib'
|
|
3
|
+
import { AnyObjectWithId, ObjectWithId } from '@naturalcycles/db-lib/src/db.model'
|
|
3
4
|
|
|
4
5
|
export interface DatastorePayload<T = any> {
|
|
5
6
|
key: Key
|
|
@@ -29,6 +30,12 @@ export interface DatastoreDBCfg extends DatastoreOptions {
|
|
|
29
30
|
* Allows to set the old native library here, e.g `grpc: require('grpc')`
|
|
30
31
|
*/
|
|
31
32
|
grpc?: any
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Use it to set default options to stream operations,
|
|
36
|
+
* e.g you can globally enable `experimentalCursorStream` here, set the batchSize, etc.
|
|
37
|
+
*/
|
|
38
|
+
streamOptions?: DatastoreDBStreamOptions
|
|
32
39
|
}
|
|
33
40
|
|
|
34
41
|
export interface DatastoreCredentials {
|
|
@@ -42,10 +49,48 @@ export interface DatastoreCredentials {
|
|
|
42
49
|
refresh_token?: string
|
|
43
50
|
}
|
|
44
51
|
|
|
52
|
+
export interface DatastoreDBStreamOptions extends DatastoreDBOptions {
|
|
53
|
+
/**
|
|
54
|
+
* Set to `true` to stream via experimental "cursor-query based stream".
|
|
55
|
+
*
|
|
56
|
+
* @default false
|
|
57
|
+
*/
|
|
58
|
+
experimentalCursorStream?: boolean
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Applicable to `experimentalCursorStream`
|
|
62
|
+
*
|
|
63
|
+
* @default 1000
|
|
64
|
+
*/
|
|
65
|
+
batchSize?: number
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Applicable to `experimentalCursorStream`
|
|
69
|
+
*
|
|
70
|
+
* Set to a value (number of Megabytes) to control the peak RSS size.
|
|
71
|
+
* If limit is reached - streaming will pause until the stream keeps up, and then
|
|
72
|
+
* resumes.
|
|
73
|
+
*
|
|
74
|
+
* Set to 0/undefined to disable. Stream will get "slow" then, cause it'll only run the query
|
|
75
|
+
* when _read is called.
|
|
76
|
+
*
|
|
77
|
+
* @default 1000
|
|
78
|
+
*/
|
|
79
|
+
rssLimitMB?: number
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Set to `true` to log additional debug info, when using experimentalCursorStream.
|
|
83
|
+
*
|
|
84
|
+
* @default false
|
|
85
|
+
*/
|
|
86
|
+
debug?: boolean
|
|
87
|
+
}
|
|
88
|
+
|
|
45
89
|
export interface DatastoreDBOptions extends CommonDBOptions {
|
|
46
90
|
tx?: Transaction
|
|
47
91
|
}
|
|
48
|
-
export interface DatastoreDBSaveOptions extends
|
|
92
|
+
export interface DatastoreDBSaveOptions<ROW extends ObjectWithId = AnyObjectWithId>
|
|
93
|
+
extends CommonDBSaveOptions<ROW> {
|
|
49
94
|
tx?: Transaction
|
|
50
95
|
}
|
|
51
96
|
|
package/src/index.ts
CHANGED
|
@@ -2,6 +2,9 @@ import { DatastoreDB } from './datastore.db'
|
|
|
2
2
|
import {
|
|
3
3
|
DatastoreCredentials,
|
|
4
4
|
DatastoreDBCfg,
|
|
5
|
+
DatastoreDBOptions,
|
|
6
|
+
DatastoreDBSaveOptions,
|
|
7
|
+
DatastoreDBStreamOptions,
|
|
5
8
|
DatastorePayload,
|
|
6
9
|
DatastorePropertyStats,
|
|
7
10
|
DatastoreStats,
|
|
@@ -17,6 +20,9 @@ export type {
|
|
|
17
20
|
DatastoreStats,
|
|
18
21
|
DatastoreCredentials,
|
|
19
22
|
DatastorePropertyStats,
|
|
23
|
+
DatastoreDBStreamOptions,
|
|
24
|
+
DatastoreDBOptions,
|
|
25
|
+
DatastoreDBSaveOptions,
|
|
20
26
|
}
|
|
21
27
|
|
|
22
28
|
export { DatastoreDB, DatastoreType, getDBAdapter, DatastoreKeyValueDB }
|
package/src/query.util.ts
CHANGED
|
@@ -19,18 +19,24 @@ export function dbQueryToDatastoreQuery<ROW extends ObjectWithId>(
|
|
|
19
19
|
|
|
20
20
|
// filter
|
|
21
21
|
// eslint-disable-next-line unicorn/no-array-reduce
|
|
22
|
-
q = dbQuery._filters.reduce(
|
|
22
|
+
q = dbQuery._filters.reduce(
|
|
23
|
+
(q, f) => q.filter(f.name as string, OP_MAP[f.op] || (f.op as any), f.val),
|
|
24
|
+
q,
|
|
25
|
+
)
|
|
23
26
|
|
|
24
27
|
// limit
|
|
25
28
|
q = q.limit(dbQuery._limitValue || 0)
|
|
26
29
|
|
|
27
30
|
// order
|
|
28
31
|
// eslint-disable-next-line unicorn/no-array-reduce
|
|
29
|
-
q = dbQuery._orders.reduce(
|
|
32
|
+
q = dbQuery._orders.reduce(
|
|
33
|
+
(q, ord) => q.order(ord.name as string, { descending: ord.descending }),
|
|
34
|
+
q,
|
|
35
|
+
)
|
|
30
36
|
|
|
31
37
|
// select
|
|
32
38
|
if (dbQuery._selectedFieldNames) {
|
|
33
|
-
const fields = dbQuery._selectedFieldNames.map(f => FNAME_MAP[f] || f)
|
|
39
|
+
const fields = (dbQuery._selectedFieldNames as string[]).map(f => FNAME_MAP[f] || f)
|
|
34
40
|
|
|
35
41
|
// Datastore requires you to specify at least one column, so if empty array is passed - it'll include __key__ at least
|
|
36
42
|
if (!fields.length) {
|