@cloudbitmaps/core 0.3.0 → 0.4.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 +244 -48
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +168 -9
- package/dist/index.d.ts +168 -9
- package/dist/index.js +244 -49
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -37,6 +37,14 @@ function applyRemove(delta, remainder) {
|
|
|
37
37
|
delta.removes.add(remainder);
|
|
38
38
|
delta.adds.remove(remainder);
|
|
39
39
|
}
|
|
40
|
+
function applyAddAll(delta, ids) {
|
|
41
|
+
delta.adds.orInPlace(ids);
|
|
42
|
+
delta.removes.andNotInPlace(ids);
|
|
43
|
+
}
|
|
44
|
+
function applyRemoveAll(delta, ids) {
|
|
45
|
+
delta.removes.orInPlace(ids);
|
|
46
|
+
delta.adds.andNotInPlace(ids);
|
|
47
|
+
}
|
|
40
48
|
function effective(cold, delta) {
|
|
41
49
|
const out = cold.clone();
|
|
42
50
|
out.orInPlace(delta.adds);
|
|
@@ -130,6 +138,19 @@ async function collectWithinBudget(source, budget, op) {
|
|
|
130
138
|
return out;
|
|
131
139
|
}
|
|
132
140
|
|
|
141
|
+
// src/core/cooperative.ts
|
|
142
|
+
var YIELD_EVERY = 1024;
|
|
143
|
+
function yieldEvery(clock, everyN = YIELD_EVERY) {
|
|
144
|
+
if (clock === void 0) return () => null;
|
|
145
|
+
const pause = clock.yieldNow !== void 0 ? () => clock.yieldNow() : () => clock.sleep(1);
|
|
146
|
+
let n = 0;
|
|
147
|
+
return () => {
|
|
148
|
+
if (++n < everyN) return null;
|
|
149
|
+
n = 0;
|
|
150
|
+
return pause();
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
|
|
133
154
|
// src/core/keys.ts
|
|
134
155
|
var FIELD = " ";
|
|
135
156
|
var NO_NAMESPACE = "/";
|
|
@@ -309,6 +330,8 @@ async function withRetry(op, policy, deps) {
|
|
|
309
330
|
var DEFAULT_MAX_BITMAP_BYTES = 1 << 20;
|
|
310
331
|
var DEFAULT_MAX_RETRIES = 16;
|
|
311
332
|
var DEFAULT_INTERSECT_CONCURRENCY = 8;
|
|
333
|
+
var MUTATE_STAGE_IDS = 1 << 20;
|
|
334
|
+
var YIELD_EVERY_IDS = 1 << 14;
|
|
312
335
|
var InstantClock = class {
|
|
313
336
|
now() {
|
|
314
337
|
return 0;
|
|
@@ -323,6 +346,7 @@ var ZeroRng = class {
|
|
|
323
346
|
}
|
|
324
347
|
};
|
|
325
348
|
var DEFAULT_MAX_WARM_SCAN_BYTES = 64 * 1024 * 1024;
|
|
349
|
+
var DEFAULT_WRITE_CONCURRENCY = 4;
|
|
326
350
|
var SegmentEngine = class {
|
|
327
351
|
warm;
|
|
328
352
|
cold;
|
|
@@ -352,7 +376,7 @@ var SegmentEngine = class {
|
|
|
352
376
|
this.rng = deps.rng ?? new ZeroRng();
|
|
353
377
|
this.occBackoff = deps.occBackoff ?? DEFAULT_OCC_BACKOFF;
|
|
354
378
|
this.readOpts = { consistent: (deps.warmReadConsistency ?? "strong") !== "eventual" };
|
|
355
|
-
this.writeConcurrency = deps.writeConcurrency ??
|
|
379
|
+
this.writeConcurrency = deps.writeConcurrency ?? DEFAULT_WRITE_CONCURRENCY;
|
|
356
380
|
this.maxWarmScanBytes = deps.maxWarmScanBytes ?? DEFAULT_MAX_WARM_SCAN_BYTES;
|
|
357
381
|
if (!Number.isFinite(this.maxWarmScanBytes) || this.maxWarmScanBytes < 1) {
|
|
358
382
|
throw new ValidationError(
|
|
@@ -372,10 +396,10 @@ var SegmentEngine = class {
|
|
|
372
396
|
return this.readModifyWrite(seg, chunkKey, (d) => applyRemove(d, remainder));
|
|
373
397
|
}
|
|
374
398
|
addMany(seg, ids) {
|
|
375
|
-
return this.mutateMany(seg, ids,
|
|
399
|
+
return this.mutateMany(seg, ids, applyAddAll);
|
|
376
400
|
}
|
|
377
401
|
removeMany(seg, ids) {
|
|
378
|
-
return this.mutateMany(seg, ids,
|
|
402
|
+
return this.mutateMany(seg, ids, applyRemoveAll);
|
|
379
403
|
}
|
|
380
404
|
async has(seg, id, opts) {
|
|
381
405
|
const { chunkKey, remainder } = splitId(id);
|
|
@@ -456,9 +480,44 @@ var SegmentEngine = class {
|
|
|
456
480
|
* re-resolves fresh (bypassing the TTL), which can hop generations even sub-TTL — still whole/immutable per
|
|
457
481
|
* read, never torn. Full intra-op snapshot isolation is deferred (gap #4).
|
|
458
482
|
*/
|
|
459
|
-
|
|
483
|
+
/**
|
|
484
|
+
* `segs[0] ∩ segs[1] ∩ …`, minus every id in `options.exclude` — streamed ascending.
|
|
485
|
+
*
|
|
486
|
+
* The `exclude` operands are what make suppression **compose without materialising**. Applying suppression
|
|
487
|
+
* afterwards (intersect into a temp segment, then remove) writes an intermediate segment that nobody wants;
|
|
488
|
+
* applying it here folds it into the same chunk-aligned pass. It is also cheaper than it looks: an exclude
|
|
489
|
+
* operand is read **only at the keys that survived the intersection**, so a 61,000-chunk global opt-out list
|
|
490
|
+
* intersected against a narrow audience costs a handful of reads, not 61,000.
|
|
491
|
+
*/
|
|
492
|
+
intersect(segs, options) {
|
|
493
|
+
return this.combine(segs, options?.exclude ?? [], "all", "intersect", options);
|
|
494
|
+
}
|
|
495
|
+
/**
|
|
496
|
+
* `segs[0] ∪ segs[1] ∪ …`, minus every id in `options.exclude` — streamed ascending.
|
|
497
|
+
*
|
|
498
|
+
* **Union is the one composite read with no chunk-skipping**, and that is a property of the operation rather
|
|
499
|
+
* than of this implementation: an id in *any* operand belongs to the result, so every chunk of every operand
|
|
500
|
+
* has to be read. `intersect` can prune a key absent from any operand; union cannot prune anything. It is
|
|
501
|
+
* budgeted exactly like `intersect` so a wide union is refused rather than quietly billed.
|
|
502
|
+
*/
|
|
503
|
+
union(segs, options) {
|
|
504
|
+
return this.combine(segs, options?.exclude ?? [], "any", "union", options);
|
|
505
|
+
}
|
|
506
|
+
/**
|
|
507
|
+
* `seg \ (excludes[0] ∪ excludes[1] ∪ …)` — streamed ascending. Suppression on its own.
|
|
508
|
+
*
|
|
509
|
+
* Reads every chunk of `seg` (any of them may survive) but each exclude **only where it overlaps `seg`**, so
|
|
510
|
+
* the cost scales with the segment being filtered rather than with the size of the suppression list.
|
|
511
|
+
*/
|
|
512
|
+
andNot(seg, excludes, options) {
|
|
513
|
+
return this.combine([seg], excludes, "all", "andNot", options);
|
|
514
|
+
}
|
|
515
|
+
async *combine(segs, excludeSegs, mode, op, options) {
|
|
460
516
|
if (segs.length === 0) {
|
|
461
|
-
throw new ValidationError(
|
|
517
|
+
throw new ValidationError(`${op} requires at least one segment`);
|
|
518
|
+
}
|
|
519
|
+
if (op === "andNot" && excludeSegs.length === 0) {
|
|
520
|
+
throw new ValidationError("andNot requires at least one segment to subtract");
|
|
462
521
|
}
|
|
463
522
|
const limit = options?.concurrency ?? DEFAULT_INTERSECT_CONCURRENCY;
|
|
464
523
|
if (!Number.isInteger(limit) || limit < 1) {
|
|
@@ -467,34 +526,52 @@ var SegmentEngine = class {
|
|
|
467
526
|
);
|
|
468
527
|
}
|
|
469
528
|
const budget = resolvePerOpBudget(options?.budget, this.budget);
|
|
470
|
-
const
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
529
|
+
const extract = async (seg) => {
|
|
530
|
+
const warmRows = await this.collectWarm(seg, op, null);
|
|
531
|
+
const keys = await this.chunkKeys(seg, warmRows);
|
|
532
|
+
const gen = await this.currentGen(seg);
|
|
533
|
+
return { seg, warmRows, keys: new Set(keys), gen };
|
|
534
|
+
};
|
|
535
|
+
const [operands, excludes] = await Promise.all([
|
|
536
|
+
Promise.all(segs.map(extract)),
|
|
537
|
+
Promise.all(excludeSegs.map(extract))
|
|
538
|
+
]);
|
|
539
|
+
const candidates = [];
|
|
540
|
+
if (mode === "all") {
|
|
541
|
+
const pivot = operands.reduce((a, b) => a.keys.size <= b.keys.size ? a : b);
|
|
542
|
+
for (const k of pivot.keys) {
|
|
543
|
+
if (operands.every((o) => o.keys.has(k))) candidates.push(k);
|
|
544
|
+
}
|
|
545
|
+
} else {
|
|
546
|
+
const seen = /* @__PURE__ */ new Set();
|
|
547
|
+
for (const o of operands) for (const k of o.keys) seen.add(k);
|
|
548
|
+
for (const k of seen) candidates.push(k);
|
|
549
|
+
}
|
|
550
|
+
candidates.sort((a, b) => a - b);
|
|
551
|
+
const common = candidates;
|
|
552
|
+
let units = 0;
|
|
553
|
+
for (const key of common) {
|
|
554
|
+
for (const o of operands) if (mode === "all" || o.keys.has(key)) units += 1;
|
|
555
|
+
for (const e of excludes) if (e.keys.has(key)) units += 1;
|
|
556
|
+
}
|
|
557
|
+
checkBudget(budget, units, op);
|
|
485
558
|
if (this.metricsOn) {
|
|
486
559
|
const distinctKeys = /* @__PURE__ */ new Set();
|
|
487
|
-
for (const
|
|
488
|
-
for (const k of
|
|
560
|
+
for (const op2 of operands) {
|
|
561
|
+
for (const k of op2.keys) distinctKeys.add(k);
|
|
489
562
|
}
|
|
563
|
+
for (const e of excludes) for (const k of e.keys) distinctKeys.add(k);
|
|
490
564
|
this.metrics.onEvent({
|
|
491
565
|
kind: "intersect",
|
|
492
|
-
|
|
566
|
+
op,
|
|
567
|
+
operands: segs.length + excludes.length,
|
|
493
568
|
fetchedChunks: common.length,
|
|
569
|
+
// Never negative: under 'any' every candidate key came from some operand, so `distinctKeys` is a
|
|
570
|
+
// superset of `common`; excludes only ever add keys here.
|
|
494
571
|
skippedChunks: distinctKeys.size - common.length
|
|
495
572
|
});
|
|
496
573
|
}
|
|
497
|
-
const startAt = (key) => this.
|
|
574
|
+
const startAt = (key) => this.combineChunk(operands, excludes, mode, key).then(
|
|
498
575
|
(result) => ({ key, result }),
|
|
499
576
|
(error) => ({ key, result: null, error })
|
|
500
577
|
);
|
|
@@ -516,16 +593,33 @@ var SegmentEngine = class {
|
|
|
516
593
|
* first operand's effective chunk and mutates it in place — safe because `effectiveChunk` always returns
|
|
517
594
|
* a FRESH bitmap (never a cached/shared Cold instance), so this can't poison the HOT cache.
|
|
518
595
|
*/
|
|
519
|
-
async
|
|
596
|
+
async combineChunk(operands, excludes, mode, chunkKey) {
|
|
597
|
+
const present = mode === "all" ? operands : operands.filter((o) => o.keys.has(chunkKey));
|
|
598
|
+
if (present.length === 0) return null;
|
|
520
599
|
const chunks = await Promise.all(
|
|
521
|
-
|
|
600
|
+
present.map((op) => this.effectiveChunk(op.seg, chunkKey, op.warmRows, op.gen))
|
|
522
601
|
);
|
|
523
602
|
const acc = chunks[0];
|
|
524
603
|
for (let i = 1; i < chunks.length; i++) {
|
|
525
|
-
|
|
526
|
-
|
|
604
|
+
if (mode === "all") {
|
|
605
|
+
acc.andInPlace(chunks[i]);
|
|
606
|
+
if (acc.isEmpty) return null;
|
|
607
|
+
} else {
|
|
608
|
+
acc.orInPlace(chunks[i]);
|
|
609
|
+
}
|
|
527
610
|
}
|
|
528
|
-
|
|
611
|
+
if (acc.isEmpty) return null;
|
|
612
|
+
const relevant = excludes.filter((e) => e.keys.has(chunkKey));
|
|
613
|
+
if (relevant.length > 0) {
|
|
614
|
+
const cuts = await Promise.all(
|
|
615
|
+
relevant.map((e) => this.effectiveChunk(e.seg, chunkKey, e.warmRows, e.gen))
|
|
616
|
+
);
|
|
617
|
+
for (const cut of cuts) {
|
|
618
|
+
acc.andNotInPlace(cut);
|
|
619
|
+
if (acc.isEmpty) return null;
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
return acc;
|
|
529
623
|
}
|
|
530
624
|
/**
|
|
531
625
|
* Materialize `seg[0] ∩ seg[1] ∩ …` into `dest` via batched `addMany` (streaming, bounded memory).
|
|
@@ -533,9 +627,30 @@ var SegmentEngine = class {
|
|
|
533
627
|
* `dest` is added to, not replaced.
|
|
534
628
|
*/
|
|
535
629
|
async intersectInto(dest, segs, options) {
|
|
536
|
-
|
|
630
|
+
await this.drainInto(dest, this.intersect(segs, options), options?.batchSize);
|
|
631
|
+
}
|
|
632
|
+
/**
|
|
633
|
+
* Materialize `segs[0] ∪ segs[1] ∪ …` (minus `options.exclude`) into `dest`. **Not atomic**, like
|
|
634
|
+
* {@link intersectInto}. See {@link union} for why this one cannot skip any chunk.
|
|
635
|
+
*/
|
|
636
|
+
async unionInto(dest, segs, options) {
|
|
637
|
+
await this.drainInto(dest, this.union(segs, options), options?.batchSize);
|
|
638
|
+
}
|
|
639
|
+
/** Materialize `seg \ (excludes…)` into `dest`. **Not atomic**, like {@link intersectInto}. */
|
|
640
|
+
async andNotInto(dest, seg, excludes, options) {
|
|
641
|
+
await this.drainInto(dest, this.andNot(seg, excludes, options), options?.batchSize);
|
|
642
|
+
}
|
|
643
|
+
/**
|
|
644
|
+
* Stream a combine's results into `dest` in bounded batches.
|
|
645
|
+
*
|
|
646
|
+
* `dest` is **added to, not replaced**, and this is **not atomic** — ids land chunk-by-chunk, so a failure
|
|
647
|
+
* mid-stream leaves a partial result, exactly as `addMany` does. Batching keeps memory bounded no matter how
|
|
648
|
+
* large the result is.
|
|
649
|
+
*/
|
|
650
|
+
async drainInto(dest, source, batchSizeOption) {
|
|
651
|
+
const batchSize = Math.max(1, batchSizeOption ?? 4096);
|
|
537
652
|
let batch = [];
|
|
538
|
-
for await (const id of
|
|
653
|
+
for await (const id of source) {
|
|
539
654
|
batch.push(id);
|
|
540
655
|
if (batch.length >= batchSize) {
|
|
541
656
|
await this.addMany(dest, batch);
|
|
@@ -544,19 +659,77 @@ var SegmentEngine = class {
|
|
|
544
659
|
}
|
|
545
660
|
if (batch.length > 0) await this.addMany(dest, batch);
|
|
546
661
|
}
|
|
662
|
+
/**
|
|
663
|
+
* Group ids by chunk, then apply each chunk's whole batch in a single OCC read-modify-write.
|
|
664
|
+
*
|
|
665
|
+
* Accepts a **sync or async** iterable, so an Athena/BigQuery cursor streams straight in without the caller
|
|
666
|
+
* hand-batching `page → addMany(page)`.
|
|
667
|
+
*
|
|
668
|
+
* **The pending ids are held as BITMAPS, not as JS numbers, and that is the load-bearing choice.** Streaming
|
|
669
|
+
* input is unbounded by definition, so something has to give. The obvious answer — buffer N ids, flush them to
|
|
670
|
+
* the backend, repeat — bounds memory and is quietly catastrophic on cost: with ids arriving in arbitrary
|
|
671
|
+
* order, every flush touches nearly every chunk again, so an 11M-id stream at a 1M-id buffer would issue
|
|
672
|
+
* **11x** the OCC round-trips of a single pass. In a library whose headline claim is honest cost, silently
|
|
673
|
+
* multiplying someone's write bill by the size of their input is not a bounded-memory strategy; it is a
|
|
674
|
+
* different bug.
|
|
675
|
+
*
|
|
676
|
+
* So the staging buffer is capped, but it folds into per-chunk **compressed bitmaps** rather than being
|
|
677
|
+
* written out. Peak memory is the roaring representation of the ids seen so far — which is what the segment
|
|
678
|
+
* itself costs, and the floor for any implementation that writes each chunk exactly once — while the backend
|
|
679
|
+
* still sees exactly one read-modify-write per distinct chunk regardless of stream length. This is strictly
|
|
680
|
+
* less memory than the previous sync-only version, which held every remainder as an uncompressed JS number.
|
|
681
|
+
*
|
|
682
|
+
* There is no way to do better without a sorted stream: a chunk can only be written once it is known that no
|
|
683
|
+
* further ids for it will arrive, and unsorted input never gives that guarantee. Callers streaming *very*
|
|
684
|
+
* large sets want `bulkLoadCrbmGeneration` instead — it writes one immutable Cold object rather than tens of
|
|
685
|
+
* thousands of warm rows. The crossover is documented in the guide.
|
|
686
|
+
*/
|
|
547
687
|
async mutateMany(seg, ids, apply) {
|
|
548
688
|
const byChunk = /* @__PURE__ */ new Map();
|
|
549
|
-
|
|
689
|
+
const staged = /* @__PURE__ */ new Map();
|
|
690
|
+
let pending = 0;
|
|
691
|
+
const tickId = yieldEvery(this.clock, YIELD_EVERY_IDS);
|
|
692
|
+
const compact = () => {
|
|
693
|
+
for (const [chunkKey, rems] of staged) {
|
|
694
|
+
if (rems.length === 0) continue;
|
|
695
|
+
const existing = byChunk.get(chunkKey);
|
|
696
|
+
if (existing === void 0) byChunk.set(chunkKey, this.codec.fromValues(rems));
|
|
697
|
+
else existing.addMany(rems);
|
|
698
|
+
rems.length = 0;
|
|
699
|
+
}
|
|
700
|
+
pending = 0;
|
|
701
|
+
};
|
|
702
|
+
const stage = (id) => {
|
|
550
703
|
const { chunkKey, remainder } = splitId(id);
|
|
551
|
-
|
|
704
|
+
let rems = staged.get(chunkKey);
|
|
705
|
+
if (rems === void 0) {
|
|
706
|
+
rems = [];
|
|
707
|
+
staged.set(chunkKey, rems);
|
|
708
|
+
}
|
|
552
709
|
rems.push(remainder);
|
|
553
|
-
|
|
710
|
+
pending += 1;
|
|
711
|
+
};
|
|
712
|
+
if (Symbol.asyncIterator in ids) {
|
|
713
|
+
for await (const id of ids) {
|
|
714
|
+
stage(id);
|
|
715
|
+
if (pending >= MUTATE_STAGE_IDS) compact();
|
|
716
|
+
const pause = tickId();
|
|
717
|
+
if (pause !== null) await pause;
|
|
718
|
+
}
|
|
719
|
+
} else {
|
|
720
|
+
for (const id of ids) {
|
|
721
|
+
stage(id);
|
|
722
|
+
if (pending >= MUTATE_STAGE_IDS) compact();
|
|
723
|
+
const pause = tickId();
|
|
724
|
+
if (pause !== null) await pause;
|
|
725
|
+
}
|
|
554
726
|
}
|
|
727
|
+
compact();
|
|
555
728
|
await mapWithConcurrency(
|
|
556
729
|
[...byChunk],
|
|
557
730
|
this.writeConcurrency,
|
|
558
|
-
([chunkKey,
|
|
559
|
-
|
|
731
|
+
([chunkKey, bitmap]) => this.readModifyWrite(seg, chunkKey, (d) => {
|
|
732
|
+
apply(d, bitmap);
|
|
560
733
|
})
|
|
561
734
|
);
|
|
562
735
|
}
|
|
@@ -1662,6 +1835,7 @@ function coldBlobReader(driver, key) {
|
|
|
1662
1835
|
}
|
|
1663
1836
|
var DEFAULT_CURRENT_GEN_TTL_MS = 2e3;
|
|
1664
1837
|
var BULK_FLUSH_IDS = 1 << 20;
|
|
1838
|
+
var YIELD_EVERY_IDS2 = 1 << 14;
|
|
1665
1839
|
var DEFAULT_MAX_OPEN_SEGMENTS = 1024;
|
|
1666
1840
|
var DEFAULT_MAX_OPEN_INDEX_BYTES = 64 * 1024 * 1024;
|
|
1667
1841
|
var CrbmColdChunkSource = class {
|
|
@@ -1880,11 +2054,14 @@ var CrbmColdChunkSource = class {
|
|
|
1880
2054
|
};
|
|
1881
2055
|
function writeCrbmGeneration(driver, key, chunks, options = {}) {
|
|
1882
2056
|
const sorted = [...chunks].sort((a, b) => a.chunkKey - b.chunkKey);
|
|
2057
|
+
const tick = yieldEvery(options.clock);
|
|
1883
2058
|
return driver.putImmutable(key, async (sink) => {
|
|
1884
2059
|
const writer = new CrbmWriter(sink, { generation: key.generation, crypto: options.crypto });
|
|
1885
2060
|
for (const { chunkKey, bitmap } of sorted) {
|
|
1886
2061
|
if (bitmap.isEmpty) continue;
|
|
1887
2062
|
await writer.addChunk(chunkKey, bitmap.serialize(), bitmap.size);
|
|
2063
|
+
const pause = tick();
|
|
2064
|
+
if (pause !== null) await pause;
|
|
1888
2065
|
}
|
|
1889
2066
|
await writer.finish();
|
|
1890
2067
|
});
|
|
@@ -1892,6 +2069,7 @@ function writeCrbmGeneration(driver, key, chunks, options = {}) {
|
|
|
1892
2069
|
async function writeCrbmGenerationStream(driver, key, chunks, options = {}) {
|
|
1893
2070
|
const chunkKeys = [];
|
|
1894
2071
|
let cardinality = 0;
|
|
2072
|
+
const tick = yieldEvery(options.clock);
|
|
1895
2073
|
const { size, sha256 } = await driver.putImmutable(key, async (sink) => {
|
|
1896
2074
|
const writer = new CrbmWriter(sink, { generation: key.generation, crypto: options.crypto });
|
|
1897
2075
|
for await (const { chunkKey, bitmap } of chunks) {
|
|
@@ -1899,6 +2077,8 @@ async function writeCrbmGenerationStream(driver, key, chunks, options = {}) {
|
|
|
1899
2077
|
await writer.addChunk(chunkKey, bitmap.serialize(), bitmap.size);
|
|
1900
2078
|
chunkKeys.push(chunkKey);
|
|
1901
2079
|
cardinality += bitmap.size;
|
|
2080
|
+
const pause = tick();
|
|
2081
|
+
if (pause !== null) await pause;
|
|
1902
2082
|
}
|
|
1903
2083
|
await writer.finish();
|
|
1904
2084
|
});
|
|
@@ -1941,17 +2121,21 @@ async function bulkLoadCrbmGeneration(driver, key, ids, options = {}) {
|
|
|
1941
2121
|
const byChunk = /* @__PURE__ */ new Map();
|
|
1942
2122
|
const pendingByChunk = /* @__PURE__ */ new Map();
|
|
1943
2123
|
let pending = 0;
|
|
1944
|
-
const
|
|
2124
|
+
const tickChunk = yieldEvery(options.clock);
|
|
2125
|
+
const tickId = yieldEvery(options.clock, YIELD_EVERY_IDS2);
|
|
2126
|
+
const flushPending = async () => {
|
|
1945
2127
|
for (const [chunkKey, rems] of pendingByChunk) {
|
|
1946
2128
|
if (rems.length === 0) continue;
|
|
1947
2129
|
const existingBitmap = byChunk.get(chunkKey);
|
|
1948
2130
|
if (existingBitmap === void 0) byChunk.set(chunkKey, codec.fromValues(rems));
|
|
1949
2131
|
else existingBitmap.addMany(rems);
|
|
1950
2132
|
rems.length = 0;
|
|
2133
|
+
const pause = tickChunk();
|
|
2134
|
+
if (pause !== null) await pause;
|
|
1951
2135
|
}
|
|
1952
2136
|
pending = 0;
|
|
1953
2137
|
};
|
|
1954
|
-
|
|
2138
|
+
const ingest = (id) => {
|
|
1955
2139
|
const { chunkKey, remainder } = splitId(id);
|
|
1956
2140
|
let bucket = pendingByChunk.get(chunkKey);
|
|
1957
2141
|
if (bucket === void 0) {
|
|
@@ -1959,14 +2143,29 @@ async function bulkLoadCrbmGeneration(driver, key, ids, options = {}) {
|
|
|
1959
2143
|
pendingByChunk.set(chunkKey, bucket);
|
|
1960
2144
|
}
|
|
1961
2145
|
bucket.push(remainder);
|
|
1962
|
-
|
|
2146
|
+
return ++pending >= BULK_FLUSH_IDS;
|
|
2147
|
+
};
|
|
2148
|
+
if (Symbol.asyncIterator in ids) {
|
|
2149
|
+
for await (const id of ids) {
|
|
2150
|
+
if (ingest(id)) await flushPending();
|
|
2151
|
+
const pause = tickId();
|
|
2152
|
+
if (pause !== null) await pause;
|
|
2153
|
+
}
|
|
2154
|
+
} else {
|
|
2155
|
+
for (const id of ids) {
|
|
2156
|
+
if (ingest(id)) await flushPending();
|
|
2157
|
+
const pause = tickId();
|
|
2158
|
+
if (pause !== null) await pause;
|
|
2159
|
+
}
|
|
1963
2160
|
}
|
|
1964
|
-
flushPending();
|
|
2161
|
+
await flushPending();
|
|
1965
2162
|
let cardinality = 0;
|
|
1966
2163
|
const chunks = [];
|
|
1967
2164
|
for (const [chunkKey, bitmap] of byChunk) {
|
|
1968
2165
|
cardinality += bitmap.size;
|
|
1969
2166
|
chunks.push({ chunkKey, bitmap });
|
|
2167
|
+
const pause = tickChunk();
|
|
2168
|
+
if (pause !== null) await pause;
|
|
1970
2169
|
}
|
|
1971
2170
|
if (options.keystore !== void 0 && options.registry === void 0) {
|
|
1972
2171
|
throw new ValidationError("bulk-load encryption requires a registry to store the wrapped DEK");
|
|
@@ -1989,7 +2188,10 @@ async function bulkLoadCrbmGeneration(driver, key, ids, options = {}) {
|
|
|
1989
2188
|
crypto = { aead: minted.aead, aadFor: (scope) => aadFor(key, key.generation, scope) };
|
|
1990
2189
|
}
|
|
1991
2190
|
}
|
|
1992
|
-
const { size, sha256 } = await writeCrbmGeneration(driver, key, chunks, {
|
|
2191
|
+
const { size, sha256 } = await writeCrbmGeneration(driver, key, chunks, {
|
|
2192
|
+
crypto,
|
|
2193
|
+
clock: options.clock
|
|
2194
|
+
});
|
|
1993
2195
|
if (options.registry !== void 0) {
|
|
1994
2196
|
const becameCurrent = await publishGeneration(options.registry, key, {
|
|
1995
2197
|
wrappedDeks: newWrapped
|
|
@@ -2005,13 +2207,6 @@ async function bulkLoadCrbmGeneration(driver, key, ids, options = {}) {
|
|
|
2005
2207
|
}
|
|
2006
2208
|
return { size, sha256, chunkCount: chunks.length, cardinality };
|
|
2007
2209
|
}
|
|
2008
|
-
async function* toAsyncIterable(src) {
|
|
2009
|
-
if (Symbol.asyncIterator in src) {
|
|
2010
|
-
yield* src;
|
|
2011
|
-
} else {
|
|
2012
|
-
yield* src;
|
|
2013
|
-
}
|
|
2014
|
-
}
|
|
2015
2210
|
|
|
2016
2211
|
// src/core/compaction.ts
|
|
2017
2212
|
var DEFAULT_MAX_BITMAP_BYTES2 = 1 << 20;
|
|
@@ -3757,6 +3952,6 @@ var RetryingRegistryDriver = class {
|
|
|
3757
3952
|
}
|
|
3758
3953
|
};
|
|
3759
3954
|
|
|
3760
|
-
export { AWS_US_EAST_1_ONDEMAND, BoundedLru, BufferReader, BufferSink, CountingMetricsSink, CrbmColdChunkSource, CrbmReader, CrbmWriter, DEFAULT_BUDGET, DEFAULT_MAX_SCAN_SEGMENTS, DEFAULT_MAX_WARM_SCAN_BYTES, DEFAULT_OCC_BACKOFF, DEFAULT_PRICING, DEFAULT_RETRY_POLICY, InProcessKeystore, LocalFsColdDriver, LocalFsRegistryDriver, LocalFsWarmDriver, MemoryColdChunkSource, MemoryColdDriver, MemoryRegistryDriver, MemoryWarmDriver, NOOP_AUDIT, NOOP_METRICS, NodeAead, RecordingAuditSink, RetryingColdChunkSource, RetryingColdDriver, RetryingRegistryDriver, RetryingWarmDriver, SegmentEngine, aadFor, bulkLoadCrbmGeneration, checkBudget, chunkRefKey, collectWithinBudget, compactSegment, destroySegment, eraseNamespace, estimateCost, findCompactable, gcOrphanGenerations, groundedReport, isTransient, joinId, publishGeneration, resolveBudget, resolvePerOpBudget, runCompactionCycle, runConsistencyCheck, runExport, safeMetrics, segmentKey, splitId, validateCompactionOptions, withRetry, writeCrbmGeneration };
|
|
3955
|
+
export { AWS_US_EAST_1_ONDEMAND, BoundedLru, BufferReader, BufferSink, CountingMetricsSink, CrbmColdChunkSource, CrbmReader, CrbmWriter, DEFAULT_BUDGET, DEFAULT_MAX_SCAN_SEGMENTS, DEFAULT_MAX_WARM_SCAN_BYTES, DEFAULT_OCC_BACKOFF, DEFAULT_PRICING, DEFAULT_RETRY_POLICY, DEFAULT_WRITE_CONCURRENCY, InProcessKeystore, LocalFsColdDriver, LocalFsRegistryDriver, LocalFsWarmDriver, MemoryColdChunkSource, MemoryColdDriver, MemoryRegistryDriver, MemoryWarmDriver, NOOP_AUDIT, NOOP_METRICS, NodeAead, RecordingAuditSink, RetryingColdChunkSource, RetryingColdDriver, RetryingRegistryDriver, RetryingWarmDriver, SegmentEngine, aadFor, bulkLoadCrbmGeneration, checkBudget, chunkRefKey, collectWithinBudget, compactSegment, destroySegment, eraseNamespace, estimateCost, findCompactable, gcOrphanGenerations, groundedReport, isTransient, joinId, publishGeneration, resolveBudget, resolvePerOpBudget, runCompactionCycle, runConsistencyCheck, runExport, safeMetrics, segmentKey, splitId, validateCompactionOptions, withRetry, writeCrbmGeneration };
|
|
3761
3956
|
//# sourceMappingURL=index.js.map
|
|
3762
3957
|
//# sourceMappingURL=index.js.map
|