@cloudbitmaps/roaring 0.2.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/bin/compact-segments.js +1 -1
- package/dist/bin/export-segments.js +1 -1
- package/dist/{chunk-JJJ6FRPO.js → chunk-TXSBMJBE.js} +144 -28
- package/dist/chunk-TXSBMJBE.js.map +1 -0
- package/dist/index.cjs +134 -18
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +112 -19
- package/dist/index.d.ts +112 -19
- package/dist/index.js +135 -19
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/dist/chunk-JJJ6FRPO.js.map +0 -1
package/dist/index.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { bulkLoadCrbmGeneration as bulkLoadCrbmGeneration$1, compactSegment as compactSegment$1, runCompactionCycle as runCompactionCycle$1, runExport as runExport$1, CodecBitmap, CodecInterface, IColdDriver, ColdChunkSource, IWarmDriver, IRegistryDriver, IKeystore, Clock, Rng, RetryPolicy, IMetricsSink,
|
|
1
|
+
import { bulkLoadCrbmGeneration as bulkLoadCrbmGeneration$1, compactSegment as compactSegment$1, runCompactionCycle as runCompactionCycle$1, runExport as runExport$1, CodecBitmap, CodecInterface, BudgetOption, IColdDriver, ColdChunkSource, IWarmDriver, IRegistryDriver, IKeystore, Clock, Rng, RetryPolicy, IMetricsSink, SegmentEngine, SegmentRef, PricingProfile, Workload, Topology, CostReport, IAuditSink, CompactionOptions, CompactionResult, ConsistencyReport, ExportSink, ExportOptions, ExportManifest, EstimateInput } from '@cloudbitmaps/core';
|
|
2
2
|
export * from '@cloudbitmaps/core';
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -21,7 +21,15 @@ type BulkLoad = typeof bulkLoadCrbmGeneration$1;
|
|
|
21
21
|
type CompactSegment = typeof compactSegment$1;
|
|
22
22
|
type RunCompactionCycle = typeof runCompactionCycle$1;
|
|
23
23
|
type RunExport = typeof runExport$1;
|
|
24
|
-
/**
|
|
24
|
+
/**
|
|
25
|
+
* {@link coreBulkLoad} with the roaring codec **and a real clock** pre-bound.
|
|
26
|
+
*
|
|
27
|
+
* The clock is what makes a large load **cooperative**: bulk-load yields the event loop periodically instead of
|
|
28
|
+
* stalling the process for its whole duration (measured at 819 ms for 1M ids before this). `core/` cannot
|
|
29
|
+
* default it — it is timer-free by lint, which is precisely why waiting goes through the `Clock` seam — so the
|
|
30
|
+
* flavor package supplies it, exactly as it supplies the codec. A caller who passes their own clock (a virtual
|
|
31
|
+
* one in a simulation, say) keeps it.
|
|
32
|
+
*/
|
|
25
33
|
declare const bulkLoadCrbmGeneration: BulkLoad;
|
|
26
34
|
/** {@link coreCompactSegment} with the roaring codec pre-bound. */
|
|
27
35
|
declare const compactSegment: CompactSegment;
|
|
@@ -191,11 +199,33 @@ interface CloudRoaringOptions {
|
|
|
191
199
|
*/
|
|
192
200
|
readonly warmReadConsistency?: 'strong' | 'eventual';
|
|
193
201
|
/**
|
|
194
|
-
* Max Warm chunk writes in flight per `addMany`/`removeMany` (the bounded flusher). Default **
|
|
195
|
-
*
|
|
196
|
-
*
|
|
202
|
+
* Max Warm chunk writes in flight per `addMany`/`removeMany` (the bounded flusher). Default **4**
|
|
203
|
+
* (`DEFAULT_WRITE_CONCURRENCY`); set it to `1` for strictly serial writes.
|
|
204
|
+
*
|
|
205
|
+
* Distinct chunks are independent OCC rows, so fanning them out cannot make them conflict with each other.
|
|
206
|
+
* The bound exists for the *backend*: a provisioned-capacity store answers a burst by throttling, and 4 stays
|
|
207
|
+
* comfortably inside what the transient-retry path absorbs (measured 8x below the first observed failure).
|
|
208
|
+
* Raise it if your backend is provisioned for the fan-out — on-demand DynamoDB, or a connection pool sized to
|
|
209
|
+
* match. A pool *smaller* than this needs no action: `pg` and `mysql2` both queue rather than reject, so the
|
|
210
|
+
* flusher simply degrades toward serial.
|
|
211
|
+
*
|
|
212
|
+
* As before, a mid-flush failure can leave a **partial result**; `addMany`/`removeMany` are not atomic. What
|
|
213
|
+
* concurrency changes is only how much of the batch may already have landed when the first error surfaces.
|
|
197
214
|
*/
|
|
198
215
|
readonly writeConcurrency?: number;
|
|
216
|
+
/**
|
|
217
|
+
* Hard ceiling on the warm-delta bytes one segment scan may hold resident. Default **64 MiB**.
|
|
218
|
+
*
|
|
219
|
+
* **A memory bound, deliberately separate from `budget`, and still enforced when `budget: false`.** The
|
|
220
|
+
* budget limits *cost* (billable requests); this limits *memory*. They are different axes, and treating the
|
|
221
|
+
* budget as a memory control is what allowed a segment with thousands of warm chunks to materialise ~12 MB
|
|
222
|
+
* before a `maxRequests: 2` budget could refuse it. It is also the only bound available to `intersect`,
|
|
223
|
+
* whose budget is `common keys × operands` — a product a single wide operand can legitimately exceed in row
|
|
224
|
+
* count while remaining entirely within contract.
|
|
225
|
+
*
|
|
226
|
+
* Raise it for genuinely large segments; it exists to keep a modest container alive, not to second-guess you.
|
|
227
|
+
*/
|
|
228
|
+
readonly maxWarmScanBytes?: number;
|
|
199
229
|
/**
|
|
200
230
|
* Per-op **denial-of-wallet** budget (Decision #3 / invariant T3): the max backend requests a
|
|
201
231
|
* single `count`/`iterate`/`intersect`/`subjectReport`/`eraseSubject` may fan out into before it's refused
|
|
@@ -389,6 +419,29 @@ declare class CloudRoaring {
|
|
|
389
419
|
* applied one chunk at a time, so if a later chunk fails (e.g. {@link WriteConflictError} after retries)
|
|
390
420
|
* earlier chunks are already applied. Within a single chunk the update is atomic.
|
|
391
421
|
*/
|
|
422
|
+
/** Options common to every chunk-aligned combine (`intersect` / `union` / `andNot`). */
|
|
423
|
+
interface BaseCombineOptions {
|
|
424
|
+
/** Max chunk keys resolved concurrently — bounds the Cold footprint. A positive integer. */
|
|
425
|
+
readonly concurrency?: number;
|
|
426
|
+
/** Override the store's per-op denial-of-wallet budget for this call (`false` lifts it). */
|
|
427
|
+
readonly budget?: BudgetOption;
|
|
428
|
+
}
|
|
429
|
+
/**
|
|
430
|
+
* A combine that can also subtract.
|
|
431
|
+
*
|
|
432
|
+
* `exclude` is why suppression does not need an intermediate segment: applied here it folds into the same
|
|
433
|
+
* chunk-aligned pass, and each exclude is read **only at the keys that survived** — so a large global opt-out
|
|
434
|
+
* list costs reads proportional to the audience, not to itself.
|
|
435
|
+
*/
|
|
436
|
+
interface CombineOptions extends BaseCombineOptions {
|
|
437
|
+
/** Segments whose ids are subtracted from the result. */
|
|
438
|
+
readonly exclude?: Segment[];
|
|
439
|
+
}
|
|
440
|
+
/** {@link CombineOptions} plus the write batching used by the `*Into` variants. */
|
|
441
|
+
interface CombineIntoOptions extends CombineOptions {
|
|
442
|
+
/** Ids buffered per `addMany` while materializing into `dest`. */
|
|
443
|
+
readonly batchSize?: number;
|
|
444
|
+
}
|
|
392
445
|
declare class Segment {
|
|
393
446
|
private readonly engine;
|
|
394
447
|
private readonly ref;
|
|
@@ -403,12 +456,24 @@ declare class Segment {
|
|
|
403
456
|
private timed;
|
|
404
457
|
/** Add an id (integer in `[0, 2^32)`). Throws {@link ValidationError} on a bad id. */
|
|
405
458
|
add(id: number): Promise<void>;
|
|
406
|
-
/**
|
|
407
|
-
|
|
459
|
+
/**
|
|
460
|
+
* Add many ids, from a **sync or async** iterable. Not atomic across chunks — see the class note.
|
|
461
|
+
*
|
|
462
|
+
* An async source means a database cursor streams straight in — `addMany(athenaCursor())` rather than
|
|
463
|
+
* hand-batching `page → addMany(page)`. Ids are grouped by chunk and each chunk is written **exactly once**,
|
|
464
|
+
* however long the stream: pending ids are held compressed, not buffered-and-flushed, so a long stream costs
|
|
465
|
+
* no more backend writes than a short one. Peak memory is the roaring representation of the ids you pass.
|
|
466
|
+
*
|
|
467
|
+
* **For a very large set, reach for `bulkLoadCrbmGeneration` instead.** `addMany` expresses a *delta* and
|
|
468
|
+
* writes one warm row per touched chunk — around 61,000 of them for a set spread across the whole id space.
|
|
469
|
+
* Bulk-load *replaces* the segment with one immutable Cold object. See the guide's cost comparison; picking
|
|
470
|
+
* the wrong one of these is the most expensive mistake available in this library.
|
|
471
|
+
*/
|
|
472
|
+
addMany(ids: Iterable<number> | AsyncIterable<number>): Promise<void>;
|
|
408
473
|
/** Remove an id (integer in `[0, 2^32)`). Throws {@link ValidationError} on a bad id. */
|
|
409
474
|
remove(id: number): Promise<void>;
|
|
410
|
-
/** Remove many ids. Not atomic across chunks — see
|
|
411
|
-
removeMany(ids: Iterable<number>): Promise<void>;
|
|
475
|
+
/** Remove many ids, from a **sync or async** iterable. Not atomic across chunks — see {@link addMany}. */
|
|
476
|
+
removeMany(ids: Iterable<number> | AsyncIterable<number>): Promise<void>;
|
|
412
477
|
has(id: number): Promise<boolean>;
|
|
413
478
|
count(): Promise<number>;
|
|
414
479
|
iterate(): AsyncIterable<number>;
|
|
@@ -421,16 +486,44 @@ declare class Segment {
|
|
|
421
486
|
* up front, so total memory also carries each operand's Warm size — negligible under Topology-A.) Pass
|
|
422
487
|
* `budget` to override the store's per-op denial-of-wallet budget for this call (or `false` to lift it).
|
|
423
488
|
*/
|
|
424
|
-
intersect(others: Segment[], options?: {
|
|
425
|
-
concurrency?: number;
|
|
426
|
-
budget?: BudgetOption;
|
|
427
|
-
}): AsyncIterable<number>;
|
|
428
489
|
/**
|
|
429
|
-
*
|
|
430
|
-
*
|
|
490
|
+
* Map the facade's `Segment` handles in `exclude` down to the plain refs `core` takes. A method rather than
|
|
491
|
+
* a module function because `ref` is class-private — the encapsulation is worth more than the free function.
|
|
431
492
|
*/
|
|
432
|
-
|
|
433
|
-
|
|
493
|
+
private refsIn;
|
|
494
|
+
intersect(others: Segment[], options?: CombineOptions): AsyncIterable<number>;
|
|
495
|
+
/**
|
|
496
|
+
* Materialize `this ∩ others…` (minus `exclude`) **into** `dest` (added, not replaced). Streaming +
|
|
497
|
+
* bounded-memory, but **not atomic** across chunks — see {@link addMany}.
|
|
498
|
+
*/
|
|
499
|
+
intersectInto(dest: Segment, others: Segment[], options?: CombineIntoOptions): Promise<void>;
|
|
500
|
+
/**
|
|
501
|
+
* `this ∪ others…`, minus `options.exclude` — streamed ascending.
|
|
502
|
+
*
|
|
503
|
+
* **The one composite read with no chunk-skipping,** and that is inherent to union rather than a limitation
|
|
504
|
+
* here: an id in *any* operand belongs to the result, so every chunk of every operand must be read.
|
|
505
|
+
* `intersect` prunes any key missing from any operand; union has nothing to prune. It is charged against the
|
|
506
|
+
* same per-op budget, so a wide union is refused rather than quietly billed — pass `budget` to raise it
|
|
507
|
+
* deliberately. If you find yourself unioning the same segments on every read, materializing the combined
|
|
508
|
+
* segment once (`unionInto`, or a bulk-load rebuild) is the cheaper shape.
|
|
509
|
+
*/
|
|
510
|
+
union(others: Segment[], options?: CombineOptions): AsyncIterable<number>;
|
|
511
|
+
/** Materialize `this ∪ others…` (minus `exclude`) **into** `dest`. **Not atomic** — see {@link addMany}. */
|
|
512
|
+
unionInto(dest: Segment, others: Segment[], options?: CombineIntoOptions): Promise<void>;
|
|
513
|
+
/**
|
|
514
|
+
* `this \ (excludes…)` — streamed ascending. Suppression on its own.
|
|
515
|
+
*
|
|
516
|
+
* Reads every chunk of `this` (any of them may survive the subtraction) but each exclude **only where it
|
|
517
|
+
* overlaps this segment**, so the cost tracks the segment being filtered rather than the size of the
|
|
518
|
+
* suppression list. Subtracting a 61,000-chunk global opt-out list from a narrow audience costs a handful of
|
|
519
|
+
* reads, not 61,000.
|
|
520
|
+
*
|
|
521
|
+
* To filter the *result of an intersection*, do not chain — pass `exclude` to {@link intersect} instead, so
|
|
522
|
+
* the suppression folds into the same pass rather than materializing an intermediate segment first.
|
|
523
|
+
*/
|
|
524
|
+
andNot(excludes: Segment[], options?: BaseCombineOptions): AsyncIterable<number>;
|
|
525
|
+
/** Materialize `this \ (excludes…)` **into** `dest`. **Not atomic** — see {@link addMany}. */
|
|
526
|
+
andNotInto(dest: Segment, excludes: Segment[], options?: BaseCombineOptions & {
|
|
434
527
|
batchSize?: number;
|
|
435
528
|
}): Promise<void>;
|
|
436
529
|
/**
|
|
@@ -446,6 +539,6 @@ declare class Segment {
|
|
|
446
539
|
}
|
|
447
540
|
|
|
448
541
|
/** Package version marker. Kept in sync with package.json at release. */
|
|
449
|
-
declare const VERSION = "0.
|
|
542
|
+
declare const VERSION = "0.4.0";
|
|
450
543
|
|
|
451
|
-
export { CloudRoaring, type CloudRoaringOptions, type EraseSubjectResult, SafeBitmap, Segment, type SegmentOptions, type SubjectErasureEntry, type SubjectReport, type SubjectSegmentRef, VERSION, bulkLoadCrbmGeneration, compactSegment, roaringCodec, runCompactionCycle, runExport };
|
|
544
|
+
export { type BaseCombineOptions, CloudRoaring, type CloudRoaringOptions, type CombineIntoOptions, type CombineOptions, type EraseSubjectResult, SafeBitmap, Segment, type SegmentOptions, type SubjectErasureEntry, type SubjectReport, type SubjectSegmentRef, VERSION, bulkLoadCrbmGeneration, compactSegment, roaringCodec, runCompactionCycle, runExport };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { bulkLoadCrbmGeneration as bulkLoadCrbmGeneration$1, compactSegment as compactSegment$1, runCompactionCycle as runCompactionCycle$1, runExport as runExport$1, CodecBitmap, CodecInterface, IColdDriver, ColdChunkSource, IWarmDriver, IRegistryDriver, IKeystore, Clock, Rng, RetryPolicy, IMetricsSink,
|
|
1
|
+
import { bulkLoadCrbmGeneration as bulkLoadCrbmGeneration$1, compactSegment as compactSegment$1, runCompactionCycle as runCompactionCycle$1, runExport as runExport$1, CodecBitmap, CodecInterface, BudgetOption, IColdDriver, ColdChunkSource, IWarmDriver, IRegistryDriver, IKeystore, Clock, Rng, RetryPolicy, IMetricsSink, SegmentEngine, SegmentRef, PricingProfile, Workload, Topology, CostReport, IAuditSink, CompactionOptions, CompactionResult, ConsistencyReport, ExportSink, ExportOptions, ExportManifest, EstimateInput } from '@cloudbitmaps/core';
|
|
2
2
|
export * from '@cloudbitmaps/core';
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -21,7 +21,15 @@ type BulkLoad = typeof bulkLoadCrbmGeneration$1;
|
|
|
21
21
|
type CompactSegment = typeof compactSegment$1;
|
|
22
22
|
type RunCompactionCycle = typeof runCompactionCycle$1;
|
|
23
23
|
type RunExport = typeof runExport$1;
|
|
24
|
-
/**
|
|
24
|
+
/**
|
|
25
|
+
* {@link coreBulkLoad} with the roaring codec **and a real clock** pre-bound.
|
|
26
|
+
*
|
|
27
|
+
* The clock is what makes a large load **cooperative**: bulk-load yields the event loop periodically instead of
|
|
28
|
+
* stalling the process for its whole duration (measured at 819 ms for 1M ids before this). `core/` cannot
|
|
29
|
+
* default it — it is timer-free by lint, which is precisely why waiting goes through the `Clock` seam — so the
|
|
30
|
+
* flavor package supplies it, exactly as it supplies the codec. A caller who passes their own clock (a virtual
|
|
31
|
+
* one in a simulation, say) keeps it.
|
|
32
|
+
*/
|
|
25
33
|
declare const bulkLoadCrbmGeneration: BulkLoad;
|
|
26
34
|
/** {@link coreCompactSegment} with the roaring codec pre-bound. */
|
|
27
35
|
declare const compactSegment: CompactSegment;
|
|
@@ -191,11 +199,33 @@ interface CloudRoaringOptions {
|
|
|
191
199
|
*/
|
|
192
200
|
readonly warmReadConsistency?: 'strong' | 'eventual';
|
|
193
201
|
/**
|
|
194
|
-
* Max Warm chunk writes in flight per `addMany`/`removeMany` (the bounded flusher). Default **
|
|
195
|
-
*
|
|
196
|
-
*
|
|
202
|
+
* Max Warm chunk writes in flight per `addMany`/`removeMany` (the bounded flusher). Default **4**
|
|
203
|
+
* (`DEFAULT_WRITE_CONCURRENCY`); set it to `1` for strictly serial writes.
|
|
204
|
+
*
|
|
205
|
+
* Distinct chunks are independent OCC rows, so fanning them out cannot make them conflict with each other.
|
|
206
|
+
* The bound exists for the *backend*: a provisioned-capacity store answers a burst by throttling, and 4 stays
|
|
207
|
+
* comfortably inside what the transient-retry path absorbs (measured 8x below the first observed failure).
|
|
208
|
+
* Raise it if your backend is provisioned for the fan-out — on-demand DynamoDB, or a connection pool sized to
|
|
209
|
+
* match. A pool *smaller* than this needs no action: `pg` and `mysql2` both queue rather than reject, so the
|
|
210
|
+
* flusher simply degrades toward serial.
|
|
211
|
+
*
|
|
212
|
+
* As before, a mid-flush failure can leave a **partial result**; `addMany`/`removeMany` are not atomic. What
|
|
213
|
+
* concurrency changes is only how much of the batch may already have landed when the first error surfaces.
|
|
197
214
|
*/
|
|
198
215
|
readonly writeConcurrency?: number;
|
|
216
|
+
/**
|
|
217
|
+
* Hard ceiling on the warm-delta bytes one segment scan may hold resident. Default **64 MiB**.
|
|
218
|
+
*
|
|
219
|
+
* **A memory bound, deliberately separate from `budget`, and still enforced when `budget: false`.** The
|
|
220
|
+
* budget limits *cost* (billable requests); this limits *memory*. They are different axes, and treating the
|
|
221
|
+
* budget as a memory control is what allowed a segment with thousands of warm chunks to materialise ~12 MB
|
|
222
|
+
* before a `maxRequests: 2` budget could refuse it. It is also the only bound available to `intersect`,
|
|
223
|
+
* whose budget is `common keys × operands` — a product a single wide operand can legitimately exceed in row
|
|
224
|
+
* count while remaining entirely within contract.
|
|
225
|
+
*
|
|
226
|
+
* Raise it for genuinely large segments; it exists to keep a modest container alive, not to second-guess you.
|
|
227
|
+
*/
|
|
228
|
+
readonly maxWarmScanBytes?: number;
|
|
199
229
|
/**
|
|
200
230
|
* Per-op **denial-of-wallet** budget (Decision #3 / invariant T3): the max backend requests a
|
|
201
231
|
* single `count`/`iterate`/`intersect`/`subjectReport`/`eraseSubject` may fan out into before it's refused
|
|
@@ -389,6 +419,29 @@ declare class CloudRoaring {
|
|
|
389
419
|
* applied one chunk at a time, so if a later chunk fails (e.g. {@link WriteConflictError} after retries)
|
|
390
420
|
* earlier chunks are already applied. Within a single chunk the update is atomic.
|
|
391
421
|
*/
|
|
422
|
+
/** Options common to every chunk-aligned combine (`intersect` / `union` / `andNot`). */
|
|
423
|
+
interface BaseCombineOptions {
|
|
424
|
+
/** Max chunk keys resolved concurrently — bounds the Cold footprint. A positive integer. */
|
|
425
|
+
readonly concurrency?: number;
|
|
426
|
+
/** Override the store's per-op denial-of-wallet budget for this call (`false` lifts it). */
|
|
427
|
+
readonly budget?: BudgetOption;
|
|
428
|
+
}
|
|
429
|
+
/**
|
|
430
|
+
* A combine that can also subtract.
|
|
431
|
+
*
|
|
432
|
+
* `exclude` is why suppression does not need an intermediate segment: applied here it folds into the same
|
|
433
|
+
* chunk-aligned pass, and each exclude is read **only at the keys that survived** — so a large global opt-out
|
|
434
|
+
* list costs reads proportional to the audience, not to itself.
|
|
435
|
+
*/
|
|
436
|
+
interface CombineOptions extends BaseCombineOptions {
|
|
437
|
+
/** Segments whose ids are subtracted from the result. */
|
|
438
|
+
readonly exclude?: Segment[];
|
|
439
|
+
}
|
|
440
|
+
/** {@link CombineOptions} plus the write batching used by the `*Into` variants. */
|
|
441
|
+
interface CombineIntoOptions extends CombineOptions {
|
|
442
|
+
/** Ids buffered per `addMany` while materializing into `dest`. */
|
|
443
|
+
readonly batchSize?: number;
|
|
444
|
+
}
|
|
392
445
|
declare class Segment {
|
|
393
446
|
private readonly engine;
|
|
394
447
|
private readonly ref;
|
|
@@ -403,12 +456,24 @@ declare class Segment {
|
|
|
403
456
|
private timed;
|
|
404
457
|
/** Add an id (integer in `[0, 2^32)`). Throws {@link ValidationError} on a bad id. */
|
|
405
458
|
add(id: number): Promise<void>;
|
|
406
|
-
/**
|
|
407
|
-
|
|
459
|
+
/**
|
|
460
|
+
* Add many ids, from a **sync or async** iterable. Not atomic across chunks — see the class note.
|
|
461
|
+
*
|
|
462
|
+
* An async source means a database cursor streams straight in — `addMany(athenaCursor())` rather than
|
|
463
|
+
* hand-batching `page → addMany(page)`. Ids are grouped by chunk and each chunk is written **exactly once**,
|
|
464
|
+
* however long the stream: pending ids are held compressed, not buffered-and-flushed, so a long stream costs
|
|
465
|
+
* no more backend writes than a short one. Peak memory is the roaring representation of the ids you pass.
|
|
466
|
+
*
|
|
467
|
+
* **For a very large set, reach for `bulkLoadCrbmGeneration` instead.** `addMany` expresses a *delta* and
|
|
468
|
+
* writes one warm row per touched chunk — around 61,000 of them for a set spread across the whole id space.
|
|
469
|
+
* Bulk-load *replaces* the segment with one immutable Cold object. See the guide's cost comparison; picking
|
|
470
|
+
* the wrong one of these is the most expensive mistake available in this library.
|
|
471
|
+
*/
|
|
472
|
+
addMany(ids: Iterable<number> | AsyncIterable<number>): Promise<void>;
|
|
408
473
|
/** Remove an id (integer in `[0, 2^32)`). Throws {@link ValidationError} on a bad id. */
|
|
409
474
|
remove(id: number): Promise<void>;
|
|
410
|
-
/** Remove many ids. Not atomic across chunks — see
|
|
411
|
-
removeMany(ids: Iterable<number>): Promise<void>;
|
|
475
|
+
/** Remove many ids, from a **sync or async** iterable. Not atomic across chunks — see {@link addMany}. */
|
|
476
|
+
removeMany(ids: Iterable<number> | AsyncIterable<number>): Promise<void>;
|
|
412
477
|
has(id: number): Promise<boolean>;
|
|
413
478
|
count(): Promise<number>;
|
|
414
479
|
iterate(): AsyncIterable<number>;
|
|
@@ -421,16 +486,44 @@ declare class Segment {
|
|
|
421
486
|
* up front, so total memory also carries each operand's Warm size — negligible under Topology-A.) Pass
|
|
422
487
|
* `budget` to override the store's per-op denial-of-wallet budget for this call (or `false` to lift it).
|
|
423
488
|
*/
|
|
424
|
-
intersect(others: Segment[], options?: {
|
|
425
|
-
concurrency?: number;
|
|
426
|
-
budget?: BudgetOption;
|
|
427
|
-
}): AsyncIterable<number>;
|
|
428
489
|
/**
|
|
429
|
-
*
|
|
430
|
-
*
|
|
490
|
+
* Map the facade's `Segment` handles in `exclude` down to the plain refs `core` takes. A method rather than
|
|
491
|
+
* a module function because `ref` is class-private — the encapsulation is worth more than the free function.
|
|
431
492
|
*/
|
|
432
|
-
|
|
433
|
-
|
|
493
|
+
private refsIn;
|
|
494
|
+
intersect(others: Segment[], options?: CombineOptions): AsyncIterable<number>;
|
|
495
|
+
/**
|
|
496
|
+
* Materialize `this ∩ others…` (minus `exclude`) **into** `dest` (added, not replaced). Streaming +
|
|
497
|
+
* bounded-memory, but **not atomic** across chunks — see {@link addMany}.
|
|
498
|
+
*/
|
|
499
|
+
intersectInto(dest: Segment, others: Segment[], options?: CombineIntoOptions): Promise<void>;
|
|
500
|
+
/**
|
|
501
|
+
* `this ∪ others…`, minus `options.exclude` — streamed ascending.
|
|
502
|
+
*
|
|
503
|
+
* **The one composite read with no chunk-skipping,** and that is inherent to union rather than a limitation
|
|
504
|
+
* here: an id in *any* operand belongs to the result, so every chunk of every operand must be read.
|
|
505
|
+
* `intersect` prunes any key missing from any operand; union has nothing to prune. It is charged against the
|
|
506
|
+
* same per-op budget, so a wide union is refused rather than quietly billed — pass `budget` to raise it
|
|
507
|
+
* deliberately. If you find yourself unioning the same segments on every read, materializing the combined
|
|
508
|
+
* segment once (`unionInto`, or a bulk-load rebuild) is the cheaper shape.
|
|
509
|
+
*/
|
|
510
|
+
union(others: Segment[], options?: CombineOptions): AsyncIterable<number>;
|
|
511
|
+
/** Materialize `this ∪ others…` (minus `exclude`) **into** `dest`. **Not atomic** — see {@link addMany}. */
|
|
512
|
+
unionInto(dest: Segment, others: Segment[], options?: CombineIntoOptions): Promise<void>;
|
|
513
|
+
/**
|
|
514
|
+
* `this \ (excludes…)` — streamed ascending. Suppression on its own.
|
|
515
|
+
*
|
|
516
|
+
* Reads every chunk of `this` (any of them may survive the subtraction) but each exclude **only where it
|
|
517
|
+
* overlaps this segment**, so the cost tracks the segment being filtered rather than the size of the
|
|
518
|
+
* suppression list. Subtracting a 61,000-chunk global opt-out list from a narrow audience costs a handful of
|
|
519
|
+
* reads, not 61,000.
|
|
520
|
+
*
|
|
521
|
+
* To filter the *result of an intersection*, do not chain — pass `exclude` to {@link intersect} instead, so
|
|
522
|
+
* the suppression folds into the same pass rather than materializing an intermediate segment first.
|
|
523
|
+
*/
|
|
524
|
+
andNot(excludes: Segment[], options?: BaseCombineOptions): AsyncIterable<number>;
|
|
525
|
+
/** Materialize `this \ (excludes…)` **into** `dest`. **Not atomic** — see {@link addMany}. */
|
|
526
|
+
andNotInto(dest: Segment, excludes: Segment[], options?: BaseCombineOptions & {
|
|
434
527
|
batchSize?: number;
|
|
435
528
|
}): Promise<void>;
|
|
436
529
|
/**
|
|
@@ -446,6 +539,6 @@ declare class Segment {
|
|
|
446
539
|
}
|
|
447
540
|
|
|
448
541
|
/** Package version marker. Kept in sync with package.json at release. */
|
|
449
|
-
declare const VERSION = "0.
|
|
542
|
+
declare const VERSION = "0.4.0";
|
|
450
543
|
|
|
451
|
-
export { CloudRoaring, type CloudRoaringOptions, type EraseSubjectResult, SafeBitmap, Segment, type SegmentOptions, type SubjectErasureEntry, type SubjectReport, type SubjectSegmentRef, VERSION, bulkLoadCrbmGeneration, compactSegment, roaringCodec, runCompactionCycle, runExport };
|
|
544
|
+
export { type BaseCombineOptions, CloudRoaring, type CloudRoaringOptions, type CombineIntoOptions, type CombineOptions, type EraseSubjectResult, SafeBitmap, Segment, type SegmentOptions, type SubjectErasureEntry, type SubjectReport, type SubjectSegmentRef, VERSION, bulkLoadCrbmGeneration, compactSegment, roaringCodec, runCompactionCycle, runExport };
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { IntegrityError, bulkLoadCrbmGeneration as bulkLoadCrbmGeneration$1, compactSegment as compactSegment$1, runCompactionCycle as runCompactionCycle$1, runExport as runExport$1, safeMetrics, NOOP_METRICS, BoundedLru, RetryingWarmDriver, RetryingColdChunkSource, ValidationError, resolveBudget, DEFAULT_BUDGET, SegmentEngine, UnsupportedError, validateSegmentRef, resolvePerOpBudget, splitId,
|
|
1
|
+
import { IntegrityError, bulkLoadCrbmGeneration as bulkLoadCrbmGeneration$1, compactSegment as compactSegment$1, runCompactionCycle as runCompactionCycle$1, runExport as runExport$1, safeMetrics, NOOP_METRICS, BoundedLru, RetryingWarmDriver, RetryingColdChunkSource, ValidationError, resolveBudget, DEFAULT_BUDGET, SegmentEngine, UnsupportedError, validateSegmentRef, resolvePerOpBudget, splitId, collectWithinBudget, mapWithConcurrency, validateCompactionOptions, runConsistencyCheck, estimateCost, groundedReport, CrbmColdChunkSource } from '@cloudbitmaps/core';
|
|
2
2
|
export * from '@cloudbitmaps/core';
|
|
3
3
|
import roaring from 'roaring';
|
|
4
4
|
|
|
@@ -92,12 +92,8 @@ var roaringCodec = {
|
|
|
92
92
|
fromValues: (values) => SafeBitmap.fromValues(values),
|
|
93
93
|
safeDeserialize: (bytes, maxBytes) => SafeBitmap.safeDeserialize(bytes, maxBytes)
|
|
94
94
|
};
|
|
95
|
-
var bulkLoadCrbmGeneration = (driver, key, ids, options = {}) => bulkLoadCrbmGeneration$1(driver, key, ids, { ...options, codec: options.codec ?? roaringCodec });
|
|
96
|
-
var compactSegment = (ref, deps, options) => compactSegment$1(ref, { ...deps, codec: deps.codec ?? roaringCodec }, options);
|
|
97
|
-
var runCompactionCycle = (deps, options) => runCompactionCycle$1({ ...deps, codec: deps.codec ?? roaringCodec }, options);
|
|
98
|
-
var runExport = (reader, registry, sink, options = {}) => runExport$1(reader, registry, sink, { ...options, codec: options.codec ?? roaringCodec });
|
|
99
95
|
|
|
100
|
-
// src/
|
|
96
|
+
// src/system-clock.ts
|
|
101
97
|
var SystemClock = class {
|
|
102
98
|
now() {
|
|
103
99
|
return Date.now();
|
|
@@ -108,7 +104,43 @@ var SystemClock = class {
|
|
|
108
104
|
setTimeout(resolve, ms);
|
|
109
105
|
});
|
|
110
106
|
}
|
|
107
|
+
/**
|
|
108
|
+
* `setImmediate`, and it has to be — this is the one place where the choice of primitive is the whole feature.
|
|
109
|
+
*
|
|
110
|
+
* A long CPU-bound loop in `core/` (bulk-load is the one that matters) periodically hands the loop back so a
|
|
111
|
+
* co-resident HTTP server keeps answering. Measured over a 61,035-chunk load, yielding every 1,024 chunks:
|
|
112
|
+
*
|
|
113
|
+
* ```text
|
|
114
|
+
* no yield 568 ms wall 568.0 ms worst event-loop gap
|
|
115
|
+
* sleep(0) 555 ms wall 555.2 ms ← a microtask. Yields NOTHING.
|
|
116
|
+
* setImmediate 569 ms wall 13.8 ms ← free, and 41x less starvation
|
|
117
|
+
* setTimeout(1) 625 ms wall 15.0 ms ← +10% wall for no extra relief
|
|
118
|
+
* ```
|
|
119
|
+
*
|
|
120
|
+
* `sleep(0)` resolving on a microtask is correct for its own contract and useless for this: microtasks drain
|
|
121
|
+
* before the loop ever advances a phase, so `await sleep(0)` in a tight loop never lets a single pending I/O
|
|
122
|
+
* callback run. `setImmediate` fires in the check phase *after* pending I/O, which is exactly the ordering a
|
|
123
|
+
* server wants — inbound requests are serviced before the loop resumes chewing.
|
|
124
|
+
*
|
|
125
|
+
* A timer is not ref'd here (unlike {@link sleep}): a yield expresses "someone else may go first", never
|
|
126
|
+
* "there is outstanding work" — and `setImmediate` cannot outlive the turn that scheduled it anyway.
|
|
127
|
+
*/
|
|
128
|
+
yieldNow() {
|
|
129
|
+
return new Promise((resolve) => {
|
|
130
|
+
setImmediate(resolve);
|
|
131
|
+
});
|
|
132
|
+
}
|
|
111
133
|
};
|
|
134
|
+
var bulkLoadCrbmGeneration = (driver, key, ids, options = {}) => bulkLoadCrbmGeneration$1(driver, key, ids, {
|
|
135
|
+
...options,
|
|
136
|
+
codec: options.codec ?? roaringCodec,
|
|
137
|
+
clock: options.clock ?? new SystemClock()
|
|
138
|
+
});
|
|
139
|
+
var compactSegment = (ref, deps, options) => compactSegment$1(ref, { ...deps, codec: deps.codec ?? roaringCodec }, options);
|
|
140
|
+
var runCompactionCycle = (deps, options) => runCompactionCycle$1({ ...deps, codec: deps.codec ?? roaringCodec }, options);
|
|
141
|
+
var runExport = (reader, registry, sink, options = {}) => runExport$1(reader, registry, sink, { ...options, codec: options.codec ?? roaringCodec });
|
|
142
|
+
|
|
143
|
+
// src/index.ts
|
|
112
144
|
var SystemRng = class {
|
|
113
145
|
next() {
|
|
114
146
|
return Math.random();
|
|
@@ -228,6 +260,7 @@ var CloudRoaring = class {
|
|
|
228
260
|
metrics,
|
|
229
261
|
warmReadConsistency: options.warmReadConsistency,
|
|
230
262
|
writeConcurrency: options.writeConcurrency,
|
|
263
|
+
maxWarmScanBytes: options.maxWarmScanBytes,
|
|
231
264
|
budget: this.budget
|
|
232
265
|
};
|
|
233
266
|
this.engine = new SegmentEngine(deps);
|
|
@@ -295,9 +328,11 @@ var CloudRoaring = class {
|
|
|
295
328
|
validateConcurrency(options.concurrency);
|
|
296
329
|
const budget = resolvePerOpBudget(options.budget, this.budget);
|
|
297
330
|
splitId(id);
|
|
298
|
-
const recs =
|
|
299
|
-
|
|
300
|
-
|
|
331
|
+
const recs = await collectWithinBudget(
|
|
332
|
+
registry.list(options.namespace),
|
|
333
|
+
budget,
|
|
334
|
+
"subjectReport"
|
|
335
|
+
);
|
|
301
336
|
const membership = await mapWithConcurrency(
|
|
302
337
|
recs,
|
|
303
338
|
options.concurrency ?? DEFAULT_ADMIN_CONCURRENCY,
|
|
@@ -352,9 +387,11 @@ var CloudRoaring = class {
|
|
|
352
387
|
validateConcurrency(options.concurrency);
|
|
353
388
|
const budget = resolvePerOpBudget(options.budget, this.budget);
|
|
354
389
|
splitId(id);
|
|
355
|
-
const recs =
|
|
356
|
-
|
|
357
|
-
|
|
390
|
+
const recs = await collectWithinBudget(
|
|
391
|
+
compaction.registry.list(options.namespace),
|
|
392
|
+
budget,
|
|
393
|
+
"eraseSubject"
|
|
394
|
+
);
|
|
358
395
|
const entries = await mapWithConcurrency(
|
|
359
396
|
recs,
|
|
360
397
|
options.concurrency ?? DEFAULT_ADMIN_CONCURRENCY,
|
|
@@ -481,7 +518,19 @@ var Segment = class {
|
|
|
481
518
|
add(id) {
|
|
482
519
|
return this.timed("add", () => this.engine.add(this.ref, id));
|
|
483
520
|
}
|
|
484
|
-
/**
|
|
521
|
+
/**
|
|
522
|
+
* Add many ids, from a **sync or async** iterable. Not atomic across chunks — see the class note.
|
|
523
|
+
*
|
|
524
|
+
* An async source means a database cursor streams straight in — `addMany(athenaCursor())` rather than
|
|
525
|
+
* hand-batching `page → addMany(page)`. Ids are grouped by chunk and each chunk is written **exactly once**,
|
|
526
|
+
* however long the stream: pending ids are held compressed, not buffered-and-flushed, so a long stream costs
|
|
527
|
+
* no more backend writes than a short one. Peak memory is the roaring representation of the ids you pass.
|
|
528
|
+
*
|
|
529
|
+
* **For a very large set, reach for `bulkLoadCrbmGeneration` instead.** `addMany` expresses a *delta* and
|
|
530
|
+
* writes one warm row per touched chunk — around 61,000 of them for a set spread across the whole id space.
|
|
531
|
+
* Bulk-load *replaces* the segment with one immutable Cold object. See the guide's cost comparison; picking
|
|
532
|
+
* the wrong one of these is the most expensive mistake available in this library.
|
|
533
|
+
*/
|
|
485
534
|
addMany(ids) {
|
|
486
535
|
return this.timed("addMany", () => this.engine.addMany(this.ref, ids));
|
|
487
536
|
}
|
|
@@ -489,7 +538,7 @@ var Segment = class {
|
|
|
489
538
|
remove(id) {
|
|
490
539
|
return this.timed("remove", () => this.engine.remove(this.ref, id));
|
|
491
540
|
}
|
|
492
|
-
/** Remove many ids. Not atomic across chunks — see
|
|
541
|
+
/** Remove many ids, from a **sync or async** iterable. Not atomic across chunks — see {@link addMany}. */
|
|
493
542
|
removeMany(ids) {
|
|
494
543
|
return this.timed("removeMany", () => this.engine.removeMany(this.ref, ids));
|
|
495
544
|
}
|
|
@@ -511,17 +560,84 @@ var Segment = class {
|
|
|
511
560
|
* up front, so total memory also carries each operand's Warm size — negligible under Topology-A.) Pass
|
|
512
561
|
* `budget` to override the store's per-op denial-of-wallet budget for this call (or `false` to lift it).
|
|
513
562
|
*/
|
|
563
|
+
/**
|
|
564
|
+
* Map the facade's `Segment` handles in `exclude` down to the plain refs `core` takes. A method rather than
|
|
565
|
+
* a module function because `ref` is class-private — the encapsulation is worth more than the free function.
|
|
566
|
+
*/
|
|
567
|
+
refsIn(options) {
|
|
568
|
+
if (options === void 0) return void 0;
|
|
569
|
+
const { exclude, ...rest } = options;
|
|
570
|
+
return exclude === void 0 ? rest : { ...rest, exclude: exclude.map((o) => o.ref) };
|
|
571
|
+
}
|
|
514
572
|
intersect(others, options) {
|
|
515
|
-
return this.engine.intersect([this.ref, ...others.map((o) => o.ref)], options);
|
|
573
|
+
return this.engine.intersect([this.ref, ...others.map((o) => o.ref)], this.refsIn(options));
|
|
516
574
|
}
|
|
517
575
|
/**
|
|
518
|
-
* Materialize `this ∩ others…` **into** `dest` (added, not replaced). Streaming +
|
|
519
|
-
* **not atomic** across chunks — see {@link addMany}.
|
|
576
|
+
* Materialize `this ∩ others…` (minus `exclude`) **into** `dest` (added, not replaced). Streaming +
|
|
577
|
+
* bounded-memory, but **not atomic** across chunks — see {@link addMany}.
|
|
520
578
|
*/
|
|
521
579
|
intersectInto(dest, others, options) {
|
|
522
580
|
return this.timed(
|
|
523
581
|
"intersectInto",
|
|
524
|
-
() => this.engine.intersectInto(
|
|
582
|
+
() => this.engine.intersectInto(
|
|
583
|
+
dest.ref,
|
|
584
|
+
[this.ref, ...others.map((o) => o.ref)],
|
|
585
|
+
this.refsIn(options)
|
|
586
|
+
)
|
|
587
|
+
);
|
|
588
|
+
}
|
|
589
|
+
/**
|
|
590
|
+
* `this ∪ others…`, minus `options.exclude` — streamed ascending.
|
|
591
|
+
*
|
|
592
|
+
* **The one composite read with no chunk-skipping,** and that is inherent to union rather than a limitation
|
|
593
|
+
* here: an id in *any* operand belongs to the result, so every chunk of every operand must be read.
|
|
594
|
+
* `intersect` prunes any key missing from any operand; union has nothing to prune. It is charged against the
|
|
595
|
+
* same per-op budget, so a wide union is refused rather than quietly billed — pass `budget` to raise it
|
|
596
|
+
* deliberately. If you find yourself unioning the same segments on every read, materializing the combined
|
|
597
|
+
* segment once (`unionInto`, or a bulk-load rebuild) is the cheaper shape.
|
|
598
|
+
*/
|
|
599
|
+
union(others, options) {
|
|
600
|
+
return this.engine.union([this.ref, ...others.map((o) => o.ref)], this.refsIn(options));
|
|
601
|
+
}
|
|
602
|
+
/** Materialize `this ∪ others…` (minus `exclude`) **into** `dest`. **Not atomic** — see {@link addMany}. */
|
|
603
|
+
unionInto(dest, others, options) {
|
|
604
|
+
return this.timed(
|
|
605
|
+
"unionInto",
|
|
606
|
+
() => this.engine.unionInto(
|
|
607
|
+
dest.ref,
|
|
608
|
+
[this.ref, ...others.map((o) => o.ref)],
|
|
609
|
+
this.refsIn(options)
|
|
610
|
+
)
|
|
611
|
+
);
|
|
612
|
+
}
|
|
613
|
+
/**
|
|
614
|
+
* `this \ (excludes…)` — streamed ascending. Suppression on its own.
|
|
615
|
+
*
|
|
616
|
+
* Reads every chunk of `this` (any of them may survive the subtraction) but each exclude **only where it
|
|
617
|
+
* overlaps this segment**, so the cost tracks the segment being filtered rather than the size of the
|
|
618
|
+
* suppression list. Subtracting a 61,000-chunk global opt-out list from a narrow audience costs a handful of
|
|
619
|
+
* reads, not 61,000.
|
|
620
|
+
*
|
|
621
|
+
* To filter the *result of an intersection*, do not chain — pass `exclude` to {@link intersect} instead, so
|
|
622
|
+
* the suppression folds into the same pass rather than materializing an intermediate segment first.
|
|
623
|
+
*/
|
|
624
|
+
andNot(excludes, options) {
|
|
625
|
+
return this.engine.andNot(
|
|
626
|
+
this.ref,
|
|
627
|
+
excludes.map((o) => o.ref),
|
|
628
|
+
options
|
|
629
|
+
);
|
|
630
|
+
}
|
|
631
|
+
/** Materialize `this \ (excludes…)` **into** `dest`. **Not atomic** — see {@link addMany}. */
|
|
632
|
+
andNotInto(dest, excludes, options) {
|
|
633
|
+
return this.timed(
|
|
634
|
+
"andNotInto",
|
|
635
|
+
() => this.engine.andNotInto(
|
|
636
|
+
dest.ref,
|
|
637
|
+
this.ref,
|
|
638
|
+
excludes.map((o) => o.ref),
|
|
639
|
+
options
|
|
640
|
+
)
|
|
525
641
|
);
|
|
526
642
|
}
|
|
527
643
|
/**
|
|
@@ -542,7 +658,7 @@ var Segment = class {
|
|
|
542
658
|
});
|
|
543
659
|
}
|
|
544
660
|
};
|
|
545
|
-
var VERSION = "0.
|
|
661
|
+
var VERSION = "0.4.0";
|
|
546
662
|
|
|
547
663
|
export { CloudRoaring, SafeBitmap, Segment, VERSION, bulkLoadCrbmGeneration, compactSegment, roaringCodec, runCompactionCycle, runExport };
|
|
548
664
|
//# sourceMappingURL=index.js.map
|