@cloudbitmaps/core 0.2.0 → 0.3.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.
- package/dist/index.cjs +102 -18
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +62 -1
- package/dist/index.d.ts +62 -1
- package/dist/index.js +100 -19
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -171,6 +171,18 @@ function checkBudget(budget, projectedRequests, op) {
|
|
|
171
171
|
);
|
|
172
172
|
}
|
|
173
173
|
}
|
|
174
|
+
async function collectWithinBudget(source, budget, op) {
|
|
175
|
+
const out = [];
|
|
176
|
+
for await (const item of source) {
|
|
177
|
+
out.push(item);
|
|
178
|
+
if (budget !== null && out.length > budget.maxRequests) {
|
|
179
|
+
throw new BudgetExceededError(
|
|
180
|
+
`${op} would fan out to more than ${budget.maxRequests} units, over the per-op budget \u2014 the scan was abandoned at that point rather than completed, so no exact total is available. Raise \`budget.maxRequests\`, override it per-op, or set \`budget: false\``
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return out;
|
|
185
|
+
}
|
|
174
186
|
|
|
175
187
|
// src/core/concurrency.ts
|
|
176
188
|
async function mapWithConcurrency(items, limit, fn) {
|
|
@@ -397,6 +409,7 @@ var ZeroRng = class {
|
|
|
397
409
|
return 0;
|
|
398
410
|
}
|
|
399
411
|
};
|
|
412
|
+
var DEFAULT_MAX_WARM_SCAN_BYTES = 64 * 1024 * 1024;
|
|
400
413
|
var SegmentEngine = class {
|
|
401
414
|
warm;
|
|
402
415
|
cold;
|
|
@@ -412,6 +425,7 @@ var SegmentEngine = class {
|
|
|
412
425
|
/** Consistency for READ-path Warm fetches (precomputed; OCC RMW passes strong explicitly, see `warmGet`). */
|
|
413
426
|
readOpts;
|
|
414
427
|
writeConcurrency;
|
|
428
|
+
maxWarmScanBytes;
|
|
415
429
|
/** Resolved per-op budget (null = disabled); undefined deps ⇒ the generous default. See {@link checkBudget}. */
|
|
416
430
|
budget;
|
|
417
431
|
constructor(deps) {
|
|
@@ -426,6 +440,12 @@ var SegmentEngine = class {
|
|
|
426
440
|
this.occBackoff = deps.occBackoff ?? DEFAULT_OCC_BACKOFF;
|
|
427
441
|
this.readOpts = { consistent: (deps.warmReadConsistency ?? "strong") !== "eventual" };
|
|
428
442
|
this.writeConcurrency = deps.writeConcurrency ?? 1;
|
|
443
|
+
this.maxWarmScanBytes = deps.maxWarmScanBytes ?? DEFAULT_MAX_WARM_SCAN_BYTES;
|
|
444
|
+
if (!Number.isFinite(this.maxWarmScanBytes) || this.maxWarmScanBytes < 1) {
|
|
445
|
+
throw new ValidationError(
|
|
446
|
+
`maxWarmScanBytes must be a finite number >= 1 (got ${String(deps.maxWarmScanBytes)})`
|
|
447
|
+
);
|
|
448
|
+
}
|
|
429
449
|
this.budget = deps.budget === void 0 ? DEFAULT_BUDGET : deps.budget;
|
|
430
450
|
this.metrics = safeMetrics(deps.metrics ?? NOOP_METRICS);
|
|
431
451
|
this.metricsOn = this.metrics !== NOOP_METRICS;
|
|
@@ -457,7 +477,7 @@ var SegmentEngine = class {
|
|
|
457
477
|
return cold ? cold.has(remainder) : false;
|
|
458
478
|
}
|
|
459
479
|
async count(seg) {
|
|
460
|
-
const warmRows = await this.collectWarm(seg);
|
|
480
|
+
const warmRows = await this.collectWarm(seg, "count", this.budget?.maxRequests ?? null);
|
|
461
481
|
const cardinalities = this.cold.cardinalities ? await this.cold.cardinalities(seg) : null;
|
|
462
482
|
if (!cardinalities) {
|
|
463
483
|
const chunkKeys = await this.chunkKeys(seg, warmRows);
|
|
@@ -489,7 +509,7 @@ var SegmentEngine = class {
|
|
|
489
509
|
return this.cold.sizeOf ? this.cold.sizeOf(seg) : Promise.resolve(null);
|
|
490
510
|
}
|
|
491
511
|
async *iterate(seg) {
|
|
492
|
-
const warmRows = await this.collectWarm(seg);
|
|
512
|
+
const warmRows = await this.collectWarm(seg, "iterate", this.budget?.maxRequests ?? null);
|
|
493
513
|
const chunkKeys = await this.chunkKeys(seg, warmRows);
|
|
494
514
|
checkBudget(this.budget, chunkKeys.length, "iterate");
|
|
495
515
|
const gen = await this.currentGen(seg);
|
|
@@ -536,7 +556,7 @@ var SegmentEngine = class {
|
|
|
536
556
|
const budget = resolvePerOpBudget(options?.budget, this.budget);
|
|
537
557
|
const operands = await Promise.all(
|
|
538
558
|
segs.map(async (seg) => {
|
|
539
|
-
const warmRows = await this.collectWarm(seg);
|
|
559
|
+
const warmRows = await this.collectWarm(seg, "intersect", null);
|
|
540
560
|
const keys = await this.chunkKeys(seg, warmRows);
|
|
541
561
|
const gen = await this.currentGen(seg);
|
|
542
562
|
return { seg, warmRows, keys: new Set(keys), gen };
|
|
@@ -677,12 +697,22 @@ var SegmentEngine = class {
|
|
|
677
697
|
}
|
|
678
698
|
return row;
|
|
679
699
|
}
|
|
680
|
-
async collectWarm(seg) {
|
|
700
|
+
async collectWarm(seg, op, rowCeiling) {
|
|
681
701
|
const rows = /* @__PURE__ */ new Map();
|
|
682
702
|
let bytes = 0;
|
|
683
703
|
for await (const row of this.warm.listChunks(seg, this.readOpts)) {
|
|
684
704
|
rows.set(row.chunkKey, row.bytes);
|
|
685
705
|
bytes += row.bytes.length;
|
|
706
|
+
if (bytes > this.maxWarmScanBytes) {
|
|
707
|
+
throw new BudgetExceededError(
|
|
708
|
+
`${op} would hold more than ${this.maxWarmScanBytes} bytes of warm deltas resident for one segment \u2014 the scan was abandoned there rather than completed. Raise \`maxWarmScanBytes\`, narrow the segment, or compact it so fewer chunks carry warm deltas. (This ceiling is independent of \`budget\` and stays in force when \`budget: false\`.)`
|
|
709
|
+
);
|
|
710
|
+
}
|
|
711
|
+
if (rowCeiling !== null && rows.size > rowCeiling) {
|
|
712
|
+
throw new BudgetExceededError(
|
|
713
|
+
`${op} would read more than ${rowCeiling} warm chunk rows, over the per-op budget \u2014 the scan was abandoned at that point rather than completed, so no exact total is available. Raise \`budget.maxRequests\`, override it per-op, or set \`budget: false\``
|
|
714
|
+
);
|
|
715
|
+
}
|
|
686
716
|
}
|
|
687
717
|
if (this.metricsOn) {
|
|
688
718
|
this.metrics.onEvent({
|
|
@@ -1193,22 +1223,44 @@ function validateChunkRef(ref) {
|
|
|
1193
1223
|
}
|
|
1194
1224
|
|
|
1195
1225
|
// src/core/crbm/crc32c.ts
|
|
1196
|
-
var
|
|
1226
|
+
var TABLES = (() => {
|
|
1197
1227
|
const POLY = 2197175160;
|
|
1198
|
-
const
|
|
1228
|
+
const t0 = new Uint32Array(256);
|
|
1199
1229
|
for (let n = 0; n < 256; n++) {
|
|
1200
1230
|
let c = n;
|
|
1201
|
-
for (let k = 0; k < 8; k++)
|
|
1202
|
-
|
|
1231
|
+
for (let k = 0; k < 8; k++) c = c & 1 ? POLY ^ c >>> 1 : c >>> 1;
|
|
1232
|
+
t0[n] = c >>> 0;
|
|
1233
|
+
}
|
|
1234
|
+
const tables = [t0];
|
|
1235
|
+
for (let k = 1; k < 8; k++) {
|
|
1236
|
+
const prev = tables[k - 1];
|
|
1237
|
+
const next = new Uint32Array(256);
|
|
1238
|
+
for (let n = 0; n < 256; n++) {
|
|
1239
|
+
const v = prev[n];
|
|
1240
|
+
next[n] = (v >>> 8 ^ t0[v & 255]) >>> 0;
|
|
1203
1241
|
}
|
|
1204
|
-
|
|
1242
|
+
tables.push(next);
|
|
1205
1243
|
}
|
|
1206
|
-
return
|
|
1244
|
+
return tables;
|
|
1207
1245
|
})();
|
|
1246
|
+
var T0 = TABLES[0];
|
|
1247
|
+
var T1 = TABLES[1];
|
|
1248
|
+
var T2 = TABLES[2];
|
|
1249
|
+
var T3 = TABLES[3];
|
|
1250
|
+
var T4 = TABLES[4];
|
|
1251
|
+
var T5 = TABLES[5];
|
|
1252
|
+
var T6 = TABLES[6];
|
|
1253
|
+
var T7 = TABLES[7];
|
|
1208
1254
|
function crc32c(bytes, seed = 0) {
|
|
1209
1255
|
let crc = (seed ^ 4294967295) >>> 0;
|
|
1210
|
-
|
|
1211
|
-
|
|
1256
|
+
let i = 0;
|
|
1257
|
+
const n8 = bytes.length - bytes.length % 8;
|
|
1258
|
+
for (; i < n8; i += 8) {
|
|
1259
|
+
crc ^= bytes[i] | bytes[i + 1] << 8 | bytes[i + 2] << 16 | bytes[i + 3] << 24;
|
|
1260
|
+
crc = (T7[crc & 255] ^ T6[crc >>> 8 & 255] ^ T5[crc >>> 16 & 255] ^ T4[crc >>> 24 & 255] ^ T3[bytes[i + 4]] ^ T2[bytes[i + 5]] ^ T1[bytes[i + 6]] ^ T0[bytes[i + 7]]) >>> 0;
|
|
1261
|
+
}
|
|
1262
|
+
for (; i < bytes.length; i++) {
|
|
1263
|
+
crc = (T0[(crc ^ bytes[i]) & 255] ^ crc >>> 8) >>> 0;
|
|
1212
1264
|
}
|
|
1213
1265
|
return (crc ^ 4294967295) >>> 0;
|
|
1214
1266
|
}
|
|
@@ -1719,6 +1771,7 @@ function coldBlobReader(driver, key) {
|
|
|
1719
1771
|
};
|
|
1720
1772
|
}
|
|
1721
1773
|
var DEFAULT_CURRENT_GEN_TTL_MS = 2e3;
|
|
1774
|
+
var BULK_FLUSH_IDS = 1 << 20;
|
|
1722
1775
|
var DEFAULT_MAX_OPEN_SEGMENTS = 1024;
|
|
1723
1776
|
var DEFAULT_MAX_OPEN_INDEX_BYTES = 64 * 1024 * 1024;
|
|
1724
1777
|
var CrbmColdChunkSource = class {
|
|
@@ -1996,15 +2049,29 @@ async function bulkLoadCrbmGeneration(driver, key, ids, options = {}) {
|
|
|
1996
2049
|
}
|
|
1997
2050
|
const codec = requireCodec(options.codec, "bulkLoadCrbmGeneration");
|
|
1998
2051
|
const byChunk = /* @__PURE__ */ new Map();
|
|
2052
|
+
const pendingByChunk = /* @__PURE__ */ new Map();
|
|
2053
|
+
let pending = 0;
|
|
2054
|
+
const flushPending = () => {
|
|
2055
|
+
for (const [chunkKey, rems] of pendingByChunk) {
|
|
2056
|
+
if (rems.length === 0) continue;
|
|
2057
|
+
const existingBitmap = byChunk.get(chunkKey);
|
|
2058
|
+
if (existingBitmap === void 0) byChunk.set(chunkKey, codec.fromValues(rems));
|
|
2059
|
+
else existingBitmap.addMany(rems);
|
|
2060
|
+
rems.length = 0;
|
|
2061
|
+
}
|
|
2062
|
+
pending = 0;
|
|
2063
|
+
};
|
|
1999
2064
|
for await (const id of toAsyncIterable(ids)) {
|
|
2000
2065
|
const { chunkKey, remainder } = splitId(id);
|
|
2001
|
-
let
|
|
2002
|
-
if (
|
|
2003
|
-
|
|
2004
|
-
|
|
2066
|
+
let bucket = pendingByChunk.get(chunkKey);
|
|
2067
|
+
if (bucket === void 0) {
|
|
2068
|
+
bucket = [];
|
|
2069
|
+
pendingByChunk.set(chunkKey, bucket);
|
|
2005
2070
|
}
|
|
2006
|
-
|
|
2071
|
+
bucket.push(remainder);
|
|
2072
|
+
if (++pending >= BULK_FLUSH_IDS) flushPending();
|
|
2007
2073
|
}
|
|
2074
|
+
flushPending();
|
|
2008
2075
|
let cardinality = 0;
|
|
2009
2076
|
const chunks = [];
|
|
2010
2077
|
for (const [chunkKey, bitmap] of byChunk) {
|
|
@@ -3788,6 +3855,7 @@ async function eraseWarm(warm, ref) {
|
|
|
3788
3855
|
}
|
|
3789
3856
|
|
|
3790
3857
|
// src/core/consistency.ts
|
|
3858
|
+
var DEFAULT_MAX_SCAN_SEGMENTS = 25e4;
|
|
3791
3859
|
var DEFAULT_CHECK_CONCURRENCY = 8;
|
|
3792
3860
|
async function listGenerations(cold, ref) {
|
|
3793
3861
|
const present = /* @__PURE__ */ new Set();
|
|
@@ -3799,8 +3867,21 @@ async function runConsistencyCheck(deps, options = {}) {
|
|
|
3799
3867
|
if (!Number.isInteger(concurrency) || concurrency < 1) {
|
|
3800
3868
|
throw new ValidationError(`concurrency must be a positive integer; got ${concurrency}`);
|
|
3801
3869
|
}
|
|
3870
|
+
const maxScanSegments = options.maxScanSegments ?? DEFAULT_MAX_SCAN_SEGMENTS;
|
|
3871
|
+
if (!Number.isFinite(maxScanSegments) || maxScanSegments < 1) {
|
|
3872
|
+
throw new ValidationError(
|
|
3873
|
+
`maxScanSegments must be a finite number >= 1; got ${String(options.maxScanSegments)}`
|
|
3874
|
+
);
|
|
3875
|
+
}
|
|
3802
3876
|
const recs = [];
|
|
3803
|
-
for await (const rec of deps.registry.list(options.namespace))
|
|
3877
|
+
for await (const rec of deps.registry.list(options.namespace)) {
|
|
3878
|
+
recs.push(rec);
|
|
3879
|
+
if (recs.length > maxScanSegments) {
|
|
3880
|
+
throw new BudgetExceededError(
|
|
3881
|
+
`checkConsistency would enumerate more than ${maxScanSegments} segments \u2014 the scan was abandoned there rather than completed. Narrow it with \`namespace\`, or raise \`maxScanSegments\` if the fleet really is that large and the memory is available.`
|
|
3882
|
+
);
|
|
3883
|
+
}
|
|
3884
|
+
}
|
|
3804
3885
|
const results = await mapWithConcurrency(recs, concurrency, async (rec) => {
|
|
3805
3886
|
if (rec.status === "destroyed") return { kind: "ok" };
|
|
3806
3887
|
const ref = { segment: rec.segment, namespace: rec.namespace };
|
|
@@ -4004,6 +4085,8 @@ exports.CrbmColdChunkSource = CrbmColdChunkSource;
|
|
|
4004
4085
|
exports.CrbmReader = CrbmReader;
|
|
4005
4086
|
exports.CrbmWriter = CrbmWriter;
|
|
4006
4087
|
exports.DEFAULT_BUDGET = DEFAULT_BUDGET;
|
|
4088
|
+
exports.DEFAULT_MAX_SCAN_SEGMENTS = DEFAULT_MAX_SCAN_SEGMENTS;
|
|
4089
|
+
exports.DEFAULT_MAX_WARM_SCAN_BYTES = DEFAULT_MAX_WARM_SCAN_BYTES;
|
|
4007
4090
|
exports.DEFAULT_OCC_BACKOFF = DEFAULT_OCC_BACKOFF;
|
|
4008
4091
|
exports.DEFAULT_PRICING = DEFAULT_PRICING;
|
|
4009
4092
|
exports.DEFAULT_RETRY_POLICY = DEFAULT_RETRY_POLICY;
|
|
@@ -4037,6 +4120,7 @@ exports.aadFor = aadFor;
|
|
|
4037
4120
|
exports.bulkLoadCrbmGeneration = bulkLoadCrbmGeneration;
|
|
4038
4121
|
exports.checkBudget = checkBudget;
|
|
4039
4122
|
exports.chunkRefKey = chunkRefKey;
|
|
4123
|
+
exports.collectWithinBudget = collectWithinBudget;
|
|
4040
4124
|
exports.compactSegment = compactSegment;
|
|
4041
4125
|
exports.destroySegment = destroySegment;
|
|
4042
4126
|
exports.eraseNamespace = eraseNamespace;
|