bulltrackers-module 1.0.105 → 1.0.106
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 +222 -222
- package/functions/appscript-api/helpers/errors.js +19 -19
- package/functions/appscript-api/index.js +58 -58
- package/functions/computation-system/helpers/orchestration_helpers.js +647 -113
- package/functions/computation-system/utils/data_loader.js +191 -191
- package/functions/computation-system/utils/utils.js +149 -254
- package/functions/core/utils/firestore_utils.js +433 -433
- package/functions/core/utils/pubsub_utils.js +53 -53
- package/functions/dispatcher/helpers/dispatch_helpers.js +47 -47
- package/functions/dispatcher/index.js +52 -52
- package/functions/etoro-price-fetcher/helpers/handler_helpers.js +124 -124
- package/functions/fetch-insights/helpers/handler_helpers.js +91 -91
- package/functions/generic-api/helpers/api_helpers.js +379 -379
- package/functions/generic-api/index.js +150 -150
- package/functions/invalid-speculator-handler/helpers/handler_helpers.js +75 -75
- package/functions/orchestrator/helpers/discovery_helpers.js +226 -226
- package/functions/orchestrator/helpers/update_helpers.js +92 -92
- package/functions/orchestrator/index.js +147 -147
- package/functions/price-backfill/helpers/handler_helpers.js +116 -123
- package/functions/social-orchestrator/helpers/orchestrator_helpers.js +61 -61
- package/functions/social-task-handler/helpers/handler_helpers.js +288 -288
- package/functions/task-engine/handler_creator.js +78 -78
- package/functions/task-engine/helpers/discover_helpers.js +125 -125
- package/functions/task-engine/helpers/update_helpers.js +118 -118
- package/functions/task-engine/helpers/verify_helpers.js +162 -162
- package/functions/task-engine/utils/firestore_batch_manager.js +258 -258
- package/index.js +105 -113
- package/package.json +45 -45
- package/functions/computation-system/computation_dependencies.json +0 -120
- package/functions/computation-system/helpers/worker_helpers.js +0 -340
- package/functions/computation-system/utils/computation_state_manager.js +0 -178
- package/functions/computation-system/utils/dependency_graph.js +0 -191
- package/functions/speculator-cleanup-orchestrator/helpers/cleanup_helpers.js +0 -160
|
@@ -1,259 +1,259 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Utility class to manage stateful Firestore write batches.
|
|
3
|
-
* REFACTORED: Renamed 'firestore' to 'db' for consistency.
|
|
4
|
-
* OPTIMIZED: Added logic to handle speculator timestamp fixes within the batch.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
const { FieldValue } = require('@google-cloud/firestore');
|
|
8
|
-
// No longer requires logger from sharedsetup, it's passed in.
|
|
9
|
-
|
|
10
|
-
class FirestoreBatchManager {
|
|
11
|
-
/**
|
|
12
|
-
* @param {Firestore} db - A Firestore instance.
|
|
13
|
-
* @param {IntelligentHeaderManager} headerManager - An IntelligentHeaderManager instance.
|
|
14
|
-
* @param {object} logger - A logger instance.
|
|
15
|
-
* @param {object} config - Configuration object.
|
|
16
|
-
*/
|
|
17
|
-
constructor(db, headerManager, logger, config) {
|
|
18
|
-
this.db = db; // Renamed from firestore
|
|
19
|
-
this.headerManager = headerManager;
|
|
20
|
-
this.logger = logger; // Added logger
|
|
21
|
-
this.config = config;
|
|
22
|
-
|
|
23
|
-
this.portfolioBatch = {};
|
|
24
|
-
this.timestampBatch = {};
|
|
25
|
-
this.processedSpeculatorCids = new Set();
|
|
26
|
-
this.speculatorTimestampFixBatch = {}; // <-- OPTIMIZATION: ADD THIS
|
|
27
|
-
this.batchTimeout = null;
|
|
28
|
-
|
|
29
|
-
this.logger.log('INFO', 'FirestoreBatchManager initialized.');
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Schedules a flush if one isn't already scheduled.
|
|
34
|
-
*/
|
|
35
|
-
_scheduleFlush() {
|
|
36
|
-
if (!this.batchTimeout) {
|
|
37
|
-
this.batchTimeout = setTimeout(
|
|
38
|
-
() => this.flushBatches(),
|
|
39
|
-
this.config.TASK_ENGINE_FLUSH_INTERVAL_MS
|
|
40
|
-
);
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Adds a portfolio to the batch.
|
|
46
|
-
* @param {string} userId
|
|
47
|
-
* @param {string} blockId
|
|
48
|
-
* @param {string} date
|
|
49
|
-
* @param {object} portfolioData
|
|
50
|
-
* @param {string} userType
|
|
51
|
-
* @param {string|null} instrumentId
|
|
52
|
-
*/
|
|
53
|
-
async addToPortfolioBatch(userId, blockId, date, portfolioData, userType, instrumentId = null) {
|
|
54
|
-
const collection = userType === 'speculator'
|
|
55
|
-
? this.config.FIRESTORE_COLLECTION_SPECULATOR_PORTFOLIOS
|
|
56
|
-
: this.config.FIRESTORE_COLLECTION_NORMAL_PORTFOLIOS;
|
|
57
|
-
const basePath = `${collection}/${blockId}/snapshots/${date}`;
|
|
58
|
-
|
|
59
|
-
if (!this.portfolioBatch[basePath]) {
|
|
60
|
-
this.portfolioBatch[basePath] = {};
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
this.portfolioBatch[basePath][userId] = portfolioData;
|
|
64
|
-
|
|
65
|
-
const totalUsersInBatch = Object.values(this.portfolioBatch).reduce((sum, users) => sum + Object.keys(users).length, 0);
|
|
66
|
-
|
|
67
|
-
if (totalUsersInBatch >= this.config.TASK_ENGINE_MAX_BATCH_SIZE) {
|
|
68
|
-
await this.flushBatches();
|
|
69
|
-
} else {
|
|
70
|
-
this._scheduleFlush();
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Adds a user timestamp update to the batch.
|
|
76
|
-
* @param {string} userId
|
|
77
|
-
* @param {string} userType
|
|
78
|
-
* @param {string|null} instrumentId
|
|
79
|
-
*/
|
|
80
|
-
async updateUserTimestamp(userId, userType, instrumentId = null) {
|
|
81
|
-
const collection = userType === 'speculator'
|
|
82
|
-
? this.config.FIRESTORE_COLLECTION_SPECULATOR_PORTFOLIOS
|
|
83
|
-
: this.config.FIRESTORE_COLLECTION_NORMAL_PORTFOLIOS;
|
|
84
|
-
const docId = userType === 'speculator' ? 'speculators' : 'normal';
|
|
85
|
-
const docPath = `${collection}/${docId}`;
|
|
86
|
-
|
|
87
|
-
if (!this.timestampBatch[docPath]) {
|
|
88
|
-
this.timestampBatch[docPath] = {};
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
const timestampKey = userType === 'speculator' ? `${userId}_${instrumentId}` : userId;
|
|
92
|
-
this.timestampBatch[docPath][timestampKey] = new Date();
|
|
93
|
-
|
|
94
|
-
if (Object.keys(this.timestampBatch[docPath]).length >= this.config.TASK_ENGINE_MAX_BATCH_SIZE) {
|
|
95
|
-
await this.flushBatches();
|
|
96
|
-
} else {
|
|
97
|
-
this._scheduleFlush();
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* Removes a user timestamp from the batch (e.g., if user is private).
|
|
103
|
-
* @param {string} userId
|
|
104
|
-
* @param {string} userType
|
|
105
|
-
* @param {string|null} instrumentId
|
|
106
|
-
*/
|
|
107
|
-
deleteFromTimestampBatch(userId, userType, instrumentId) {
|
|
108
|
-
const collection = userType === 'speculator'
|
|
109
|
-
? this.config.FIRESTORE_COLLECTION_SPECULATOR_PORTFOLIOS
|
|
110
|
-
: this.config.FIRESTORE_COLLECTION_NORMAL_PORTFOLIOS;
|
|
111
|
-
const docId = userType === 'speculator' ? 'speculators' : 'normal';
|
|
112
|
-
const docPath = `${collection}/${docId}`;
|
|
113
|
-
|
|
114
|
-
if (this.timestampBatch[docPath]) {
|
|
115
|
-
const timestampKey = userType === 'speculator' ? `${userId}_${instrumentId}` : userId;
|
|
116
|
-
delete this.timestampBatch[docPath][timestampKey];
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
/**
|
|
121
|
-
* Adds discovered speculator CIDs to the in-memory set for later deletion.
|
|
122
|
-
* @param {Array<string>} cids
|
|
123
|
-
*/
|
|
124
|
-
addProcessedSpeculatorCids(cids) {
|
|
125
|
-
cids.forEach(cid => this.processedSpeculatorCids.add(cid));
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
/**
|
|
129
|
-
* --- OPTIMIZATION: ADD THIS NEW METHOD ---
|
|
130
|
-
* Adds a speculator timestamp fix to the batch.
|
|
131
|
-
* @param {string} userId
|
|
132
|
-
* @param {string} orchestratorBlockId (e.g., "1000000")
|
|
133
|
-
*/
|
|
134
|
-
async addSpeculatorTimestampFix(userId, orchestratorBlockId) {
|
|
135
|
-
const docPath = `${this.config.FIRESTORE_COLLECTION_SPECULATOR_BLOCKS}/${orchestratorBlockId}`;
|
|
136
|
-
|
|
137
|
-
if (!this.speculatorTimestampFixBatch[docPath]) {
|
|
138
|
-
this.speculatorTimestampFixBatch[docPath] = {};
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
// These are the two fields the orchestrator reads
|
|
142
|
-
this.speculatorTimestampFixBatch[docPath][`users.${userId}.lastVerified`] = new Date();
|
|
143
|
-
this.speculatorTimestampFixBatch[docPath][`users.${userId}.lastHeldSpeculatorAsset`] = new Date();
|
|
144
|
-
|
|
145
|
-
this.logger.log('TRACE', `[BATCH] Queued speculator timestamp fix for user ${userId} in block ${orchestratorBlockId}`);
|
|
146
|
-
this._scheduleFlush(); // Schedule a flush
|
|
147
|
-
}
|
|
148
|
-
// --- END NEW METHOD ---
|
|
149
|
-
|
|
150
|
-
/**
|
|
151
|
-
* Flushes all pending writes to Firestore and updates header performance.
|
|
152
|
-
*/
|
|
153
|
-
async flushBatches() {
|
|
154
|
-
if (this.batchTimeout) {
|
|
155
|
-
clearTimeout(this.batchTimeout);
|
|
156
|
-
this.batchTimeout = null;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
const promises = [];
|
|
160
|
-
const firestoreBatch = this.db.batch(); // Use this.db
|
|
161
|
-
let batchOperationCount = 0;
|
|
162
|
-
|
|
163
|
-
for (const basePath in this.portfolioBatch) {
|
|
164
|
-
const userPortfolios = this.portfolioBatch[basePath];
|
|
165
|
-
const userIds = Object.keys(userPortfolios);
|
|
166
|
-
|
|
167
|
-
if (userIds.length > 0) {
|
|
168
|
-
for (let i = 0; i < userIds.length; i += this.config.TASK_ENGINE_MAX_USERS_PER_SHARD) {
|
|
169
|
-
const chunkUserIds = userIds.slice(i, i + this.config.TASK_ENGINE_MAX_USERS_PER_SHARD);
|
|
170
|
-
const shardIndex = Math.floor(i / this.config.TASK_ENGINE_MAX_USERS_PER_SHARD);
|
|
171
|
-
|
|
172
|
-
const chunkData = {};
|
|
173
|
-
chunkUserIds.forEach(userId => {
|
|
174
|
-
chunkData[userId] = userPortfolios[userId];
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
const docRef = this.db.collection(`${basePath}/parts`).doc(); // Now uses a reandomised ID from firestore to solve an overwrite issue with contention
|
|
178
|
-
firestoreBatch.set(docRef, chunkData); // No merge: true needed, it's a new doc
|
|
179
|
-
batchOperationCount++;
|
|
180
|
-
}
|
|
181
|
-
// (Optional) Update the log message to reflect the path used for the data write
|
|
182
|
-
this.logger.log('INFO', `[BATCH] Staged ${userIds.length} users into ${Math.ceil(userIds.length / this.config.TASK_ENGINE_MAX_USERS_PER_SHARD)} unique shard documents for ${basePath}.`);
|
|
183
|
-
}
|
|
184
|
-
delete this.portfolioBatch[basePath];
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
for (const docPath in this.timestampBatch) {
|
|
188
|
-
if (Object.keys(this.timestampBatch[docPath]).length > 0) {
|
|
189
|
-
const [collection] = docPath.split('/');
|
|
190
|
-
const docRef = this.db.collection(collection).doc('timestamps').collection('users').doc('normal'); // Use this.db
|
|
191
|
-
|
|
192
|
-
if (batchOperationCount < 450) {
|
|
193
|
-
firestoreBatch.set(docRef, { users: this.timestampBatch[docPath] }, { merge: true });
|
|
194
|
-
batchOperationCount++;
|
|
195
|
-
} else {
|
|
196
|
-
promises.push(firestoreBatch.commit());
|
|
197
|
-
const newBatch = this.db.batch(); // Use this.db
|
|
198
|
-
newBatch.set(docRef, { users: this.timestampBatch[docPath] }, { merge: true });
|
|
199
|
-
promises.push(newBatch.commit());
|
|
200
|
-
batchOperationCount = 1;
|
|
201
|
-
}
|
|
202
|
-
delete this.timestampBatch[docPath];
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
if (this.processedSpeculatorCids.size > 0) {
|
|
207
|
-
this.logger.log('INFO', `[BATCH] Flushing ${this.processedSpeculatorCids.size} processed speculator CIDs from pending list documents.`);
|
|
208
|
-
const cidsToDelete = Array.from(this.processedSpeculatorCids);
|
|
209
|
-
this.processedSpeculatorCids.clear();
|
|
210
|
-
|
|
211
|
-
const pendingDocsSnapshot = await this.db.collection(this.config.PENDING_SPECULATORS_COLLECTION).get(); // Use this.db
|
|
212
|
-
|
|
213
|
-
if (!pendingDocsSnapshot.empty) {
|
|
214
|
-
const deletePromises = [];
|
|
215
|
-
pendingDocsSnapshot.forEach(doc => {
|
|
216
|
-
const docData = doc.data().users || {};
|
|
217
|
-
const cidsInThisDoc = cidsToDelete.filter(cid => docData.hasOwnProperty(cid));
|
|
218
|
-
|
|
219
|
-
if (cidsInThisDoc.length > 0) {
|
|
220
|
-
const deleteBatch = this.db.batch(); // Use this.db
|
|
221
|
-
const updates = {};
|
|
222
|
-
cidsInThisDoc.forEach(cid => {
|
|
223
|
-
updates[`users.${cid}`] = FieldValue.delete();
|
|
224
|
-
});
|
|
225
|
-
deleteBatch.update(doc.ref, updates);
|
|
226
|
-
deletePromises.push(deleteBatch.commit());
|
|
227
|
-
this.logger.log('INFO', `[BATCH] Staged deletion of ${cidsInThisDoc.length} CIDs from document: ${doc.id}`);
|
|
228
|
-
}
|
|
229
|
-
});
|
|
230
|
-
promises.push(...deletePromises);
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
// --- OPTIMIZATION: ADD THIS LOOP ---
|
|
235
|
-
for (const docPath in this.speculatorTimestampFixBatch) {
|
|
236
|
-
const updates = this.speculatorTimestampFixBatch[docPath];
|
|
237
|
-
if (Object.keys(updates).length > 0) {
|
|
238
|
-
const docRef = this.db.doc(docPath);
|
|
239
|
-
|
|
240
|
-
// Add to the existing batch
|
|
241
|
-
firestoreBatch.set(docRef, updates, { merge: true });
|
|
242
|
-
batchOperationCount++;
|
|
243
|
-
}
|
|
244
|
-
delete this.speculatorTimestampFixBatch[docPath];
|
|
245
|
-
}
|
|
246
|
-
// --- END OPTIMIZATION ---
|
|
247
|
-
|
|
248
|
-
if (batchOperationCount > 0) {
|
|
249
|
-
promises.push(firestoreBatch.commit());
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
promises.push(this.headerManager.flushPerformanceUpdates());
|
|
253
|
-
|
|
254
|
-
await Promise.all(promises);
|
|
255
|
-
this.logger.log('INFO', '[BATCH] All batches flushed successfully.');
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Utility class to manage stateful Firestore write batches.
|
|
3
|
+
* REFACTORED: Renamed 'firestore' to 'db' for consistency.
|
|
4
|
+
* OPTIMIZED: Added logic to handle speculator timestamp fixes within the batch.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const { FieldValue } = require('@google-cloud/firestore');
|
|
8
|
+
// No longer requires logger from sharedsetup, it's passed in.
|
|
9
|
+
|
|
10
|
+
class FirestoreBatchManager {
|
|
11
|
+
/**
|
|
12
|
+
* @param {Firestore} db - A Firestore instance.
|
|
13
|
+
* @param {IntelligentHeaderManager} headerManager - An IntelligentHeaderManager instance.
|
|
14
|
+
* @param {object} logger - A logger instance.
|
|
15
|
+
* @param {object} config - Configuration object.
|
|
16
|
+
*/
|
|
17
|
+
constructor(db, headerManager, logger, config) {
|
|
18
|
+
this.db = db; // Renamed from firestore
|
|
19
|
+
this.headerManager = headerManager;
|
|
20
|
+
this.logger = logger; // Added logger
|
|
21
|
+
this.config = config;
|
|
22
|
+
|
|
23
|
+
this.portfolioBatch = {};
|
|
24
|
+
this.timestampBatch = {};
|
|
25
|
+
this.processedSpeculatorCids = new Set();
|
|
26
|
+
this.speculatorTimestampFixBatch = {}; // <-- OPTIMIZATION: ADD THIS
|
|
27
|
+
this.batchTimeout = null;
|
|
28
|
+
|
|
29
|
+
this.logger.log('INFO', 'FirestoreBatchManager initialized.');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Schedules a flush if one isn't already scheduled.
|
|
34
|
+
*/
|
|
35
|
+
_scheduleFlush() {
|
|
36
|
+
if (!this.batchTimeout) {
|
|
37
|
+
this.batchTimeout = setTimeout(
|
|
38
|
+
() => this.flushBatches(),
|
|
39
|
+
this.config.TASK_ENGINE_FLUSH_INTERVAL_MS
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Adds a portfolio to the batch.
|
|
46
|
+
* @param {string} userId
|
|
47
|
+
* @param {string} blockId
|
|
48
|
+
* @param {string} date
|
|
49
|
+
* @param {object} portfolioData
|
|
50
|
+
* @param {string} userType
|
|
51
|
+
* @param {string|null} instrumentId
|
|
52
|
+
*/
|
|
53
|
+
async addToPortfolioBatch(userId, blockId, date, portfolioData, userType, instrumentId = null) {
|
|
54
|
+
const collection = userType === 'speculator'
|
|
55
|
+
? this.config.FIRESTORE_COLLECTION_SPECULATOR_PORTFOLIOS
|
|
56
|
+
: this.config.FIRESTORE_COLLECTION_NORMAL_PORTFOLIOS;
|
|
57
|
+
const basePath = `${collection}/${blockId}/snapshots/${date}`;
|
|
58
|
+
|
|
59
|
+
if (!this.portfolioBatch[basePath]) {
|
|
60
|
+
this.portfolioBatch[basePath] = {};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
this.portfolioBatch[basePath][userId] = portfolioData;
|
|
64
|
+
|
|
65
|
+
const totalUsersInBatch = Object.values(this.portfolioBatch).reduce((sum, users) => sum + Object.keys(users).length, 0);
|
|
66
|
+
|
|
67
|
+
if (totalUsersInBatch >= this.config.TASK_ENGINE_MAX_BATCH_SIZE) {
|
|
68
|
+
await this.flushBatches();
|
|
69
|
+
} else {
|
|
70
|
+
this._scheduleFlush();
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Adds a user timestamp update to the batch.
|
|
76
|
+
* @param {string} userId
|
|
77
|
+
* @param {string} userType
|
|
78
|
+
* @param {string|null} instrumentId
|
|
79
|
+
*/
|
|
80
|
+
async updateUserTimestamp(userId, userType, instrumentId = null) {
|
|
81
|
+
const collection = userType === 'speculator'
|
|
82
|
+
? this.config.FIRESTORE_COLLECTION_SPECULATOR_PORTFOLIOS
|
|
83
|
+
: this.config.FIRESTORE_COLLECTION_NORMAL_PORTFOLIOS;
|
|
84
|
+
const docId = userType === 'speculator' ? 'speculators' : 'normal';
|
|
85
|
+
const docPath = `${collection}/${docId}`;
|
|
86
|
+
|
|
87
|
+
if (!this.timestampBatch[docPath]) {
|
|
88
|
+
this.timestampBatch[docPath] = {};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const timestampKey = userType === 'speculator' ? `${userId}_${instrumentId}` : userId;
|
|
92
|
+
this.timestampBatch[docPath][timestampKey] = new Date();
|
|
93
|
+
|
|
94
|
+
if (Object.keys(this.timestampBatch[docPath]).length >= this.config.TASK_ENGINE_MAX_BATCH_SIZE) {
|
|
95
|
+
await this.flushBatches();
|
|
96
|
+
} else {
|
|
97
|
+
this._scheduleFlush();
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Removes a user timestamp from the batch (e.g., if user is private).
|
|
103
|
+
* @param {string} userId
|
|
104
|
+
* @param {string} userType
|
|
105
|
+
* @param {string|null} instrumentId
|
|
106
|
+
*/
|
|
107
|
+
deleteFromTimestampBatch(userId, userType, instrumentId) {
|
|
108
|
+
const collection = userType === 'speculator'
|
|
109
|
+
? this.config.FIRESTORE_COLLECTION_SPECULATOR_PORTFOLIOS
|
|
110
|
+
: this.config.FIRESTORE_COLLECTION_NORMAL_PORTFOLIOS;
|
|
111
|
+
const docId = userType === 'speculator' ? 'speculators' : 'normal';
|
|
112
|
+
const docPath = `${collection}/${docId}`;
|
|
113
|
+
|
|
114
|
+
if (this.timestampBatch[docPath]) {
|
|
115
|
+
const timestampKey = userType === 'speculator' ? `${userId}_${instrumentId}` : userId;
|
|
116
|
+
delete this.timestampBatch[docPath][timestampKey];
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Adds discovered speculator CIDs to the in-memory set for later deletion.
|
|
122
|
+
* @param {Array<string>} cids
|
|
123
|
+
*/
|
|
124
|
+
addProcessedSpeculatorCids(cids) {
|
|
125
|
+
cids.forEach(cid => this.processedSpeculatorCids.add(cid));
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* --- OPTIMIZATION: ADD THIS NEW METHOD ---
|
|
130
|
+
* Adds a speculator timestamp fix to the batch.
|
|
131
|
+
* @param {string} userId
|
|
132
|
+
* @param {string} orchestratorBlockId (e.g., "1000000")
|
|
133
|
+
*/
|
|
134
|
+
async addSpeculatorTimestampFix(userId, orchestratorBlockId) {
|
|
135
|
+
const docPath = `${this.config.FIRESTORE_COLLECTION_SPECULATOR_BLOCKS}/${orchestratorBlockId}`;
|
|
136
|
+
|
|
137
|
+
if (!this.speculatorTimestampFixBatch[docPath]) {
|
|
138
|
+
this.speculatorTimestampFixBatch[docPath] = {};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// These are the two fields the orchestrator reads
|
|
142
|
+
this.speculatorTimestampFixBatch[docPath][`users.${userId}.lastVerified`] = new Date();
|
|
143
|
+
this.speculatorTimestampFixBatch[docPath][`users.${userId}.lastHeldSpeculatorAsset`] = new Date();
|
|
144
|
+
|
|
145
|
+
this.logger.log('TRACE', `[BATCH] Queued speculator timestamp fix for user ${userId} in block ${orchestratorBlockId}`);
|
|
146
|
+
this._scheduleFlush(); // Schedule a flush
|
|
147
|
+
}
|
|
148
|
+
// --- END NEW METHOD ---
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Flushes all pending writes to Firestore and updates header performance.
|
|
152
|
+
*/
|
|
153
|
+
async flushBatches() {
|
|
154
|
+
if (this.batchTimeout) {
|
|
155
|
+
clearTimeout(this.batchTimeout);
|
|
156
|
+
this.batchTimeout = null;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const promises = [];
|
|
160
|
+
const firestoreBatch = this.db.batch(); // Use this.db
|
|
161
|
+
let batchOperationCount = 0;
|
|
162
|
+
|
|
163
|
+
for (const basePath in this.portfolioBatch) {
|
|
164
|
+
const userPortfolios = this.portfolioBatch[basePath];
|
|
165
|
+
const userIds = Object.keys(userPortfolios);
|
|
166
|
+
|
|
167
|
+
if (userIds.length > 0) {
|
|
168
|
+
for (let i = 0; i < userIds.length; i += this.config.TASK_ENGINE_MAX_USERS_PER_SHARD) {
|
|
169
|
+
const chunkUserIds = userIds.slice(i, i + this.config.TASK_ENGINE_MAX_USERS_PER_SHARD);
|
|
170
|
+
const shardIndex = Math.floor(i / this.config.TASK_ENGINE_MAX_USERS_PER_SHARD);
|
|
171
|
+
|
|
172
|
+
const chunkData = {};
|
|
173
|
+
chunkUserIds.forEach(userId => {
|
|
174
|
+
chunkData[userId] = userPortfolios[userId];
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
const docRef = this.db.collection(`${basePath}/parts`).doc(); // Now uses a reandomised ID from firestore to solve an overwrite issue with contention
|
|
178
|
+
firestoreBatch.set(docRef, chunkData); // No merge: true needed, it's a new doc
|
|
179
|
+
batchOperationCount++;
|
|
180
|
+
}
|
|
181
|
+
// (Optional) Update the log message to reflect the path used for the data write
|
|
182
|
+
this.logger.log('INFO', `[BATCH] Staged ${userIds.length} users into ${Math.ceil(userIds.length / this.config.TASK_ENGINE_MAX_USERS_PER_SHARD)} unique shard documents for ${basePath}.`);
|
|
183
|
+
}
|
|
184
|
+
delete this.portfolioBatch[basePath];
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
for (const docPath in this.timestampBatch) {
|
|
188
|
+
if (Object.keys(this.timestampBatch[docPath]).length > 0) {
|
|
189
|
+
const [collection] = docPath.split('/');
|
|
190
|
+
const docRef = this.db.collection(collection).doc('timestamps').collection('users').doc('normal'); // Use this.db
|
|
191
|
+
|
|
192
|
+
if (batchOperationCount < 450) {
|
|
193
|
+
firestoreBatch.set(docRef, { users: this.timestampBatch[docPath] }, { merge: true });
|
|
194
|
+
batchOperationCount++;
|
|
195
|
+
} else {
|
|
196
|
+
promises.push(firestoreBatch.commit());
|
|
197
|
+
const newBatch = this.db.batch(); // Use this.db
|
|
198
|
+
newBatch.set(docRef, { users: this.timestampBatch[docPath] }, { merge: true });
|
|
199
|
+
promises.push(newBatch.commit());
|
|
200
|
+
batchOperationCount = 1;
|
|
201
|
+
}
|
|
202
|
+
delete this.timestampBatch[docPath];
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (this.processedSpeculatorCids.size > 0) {
|
|
207
|
+
this.logger.log('INFO', `[BATCH] Flushing ${this.processedSpeculatorCids.size} processed speculator CIDs from pending list documents.`);
|
|
208
|
+
const cidsToDelete = Array.from(this.processedSpeculatorCids);
|
|
209
|
+
this.processedSpeculatorCids.clear();
|
|
210
|
+
|
|
211
|
+
const pendingDocsSnapshot = await this.db.collection(this.config.PENDING_SPECULATORS_COLLECTION).get(); // Use this.db
|
|
212
|
+
|
|
213
|
+
if (!pendingDocsSnapshot.empty) {
|
|
214
|
+
const deletePromises = [];
|
|
215
|
+
pendingDocsSnapshot.forEach(doc => {
|
|
216
|
+
const docData = doc.data().users || {};
|
|
217
|
+
const cidsInThisDoc = cidsToDelete.filter(cid => docData.hasOwnProperty(cid));
|
|
218
|
+
|
|
219
|
+
if (cidsInThisDoc.length > 0) {
|
|
220
|
+
const deleteBatch = this.db.batch(); // Use this.db
|
|
221
|
+
const updates = {};
|
|
222
|
+
cidsInThisDoc.forEach(cid => {
|
|
223
|
+
updates[`users.${cid}`] = FieldValue.delete();
|
|
224
|
+
});
|
|
225
|
+
deleteBatch.update(doc.ref, updates);
|
|
226
|
+
deletePromises.push(deleteBatch.commit());
|
|
227
|
+
this.logger.log('INFO', `[BATCH] Staged deletion of ${cidsInThisDoc.length} CIDs from document: ${doc.id}`);
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
promises.push(...deletePromises);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// --- OPTIMIZATION: ADD THIS LOOP ---
|
|
235
|
+
for (const docPath in this.speculatorTimestampFixBatch) {
|
|
236
|
+
const updates = this.speculatorTimestampFixBatch[docPath];
|
|
237
|
+
if (Object.keys(updates).length > 0) {
|
|
238
|
+
const docRef = this.db.doc(docPath);
|
|
239
|
+
|
|
240
|
+
// Add to the existing batch
|
|
241
|
+
firestoreBatch.set(docRef, updates, { merge: true });
|
|
242
|
+
batchOperationCount++;
|
|
243
|
+
}
|
|
244
|
+
delete this.speculatorTimestampFixBatch[docPath];
|
|
245
|
+
}
|
|
246
|
+
// --- END OPTIMIZATION ---
|
|
247
|
+
|
|
248
|
+
if (batchOperationCount > 0) {
|
|
249
|
+
promises.push(firestoreBatch.commit());
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
promises.push(this.headerManager.flushPerformanceUpdates());
|
|
253
|
+
|
|
254
|
+
await Promise.all(promises);
|
|
255
|
+
this.logger.log('INFO', '[BATCH] All batches flushed successfully.');
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
259
|
module.exports = { FirestoreBatchManager };
|