@cloudbitmaps/roaring 0.6.0 → 0.8.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/README.md +22 -0
- package/dist/bin/compact-segments.js +1 -1
- package/dist/bin/export-segments.js +1 -1
- package/dist/{chunk-ZU3QGCS7.js → chunk-TOY3LBPO.js} +83 -6
- package/dist/chunk-TOY3LBPO.js.map +1 -0
- package/dist/index.cjs +80 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +77 -4
- package/dist/index.d.ts +77 -4
- package/dist/index.js +81 -4
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/dist/chunk-ZU3QGCS7.js.map +0 -1
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, 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';
|
|
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, DropResult, ConsistencyReport, ExportSink, ExportOptions, ExportManifest, EstimateInput } from '@cloudbitmaps/core';
|
|
2
2
|
export * from '@cloudbitmaps/core';
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -114,8 +114,13 @@ declare const roaringCodec: CodecInterface;
|
|
|
114
114
|
* In-process lifecycle helpers — `compact`, `eraseSubject`, `subjectReport` — reuse the store's own drivers, so
|
|
115
115
|
* you never re-pass them (they need the store built with a raw cold driver + registry). Out-of-process cold
|
|
116
116
|
* writers (the compaction daemon, the seed/bulk-load CLI, `destroySegment`/`eraseNamespace`) wire their own deps
|
|
117
|
-
* against the same drivers — they run in separate processes.
|
|
118
|
-
*
|
|
117
|
+
* against the same drivers — they run in separate processes.
|
|
118
|
+
*
|
|
119
|
+
* **You do not need any of them to start.** Construct a store and call `add`/`addMany`/`has`/`remove` — a segment
|
|
120
|
+
* with no Cold generation at all is fully functional, because a read merges `(cold ∪ warm.adds) \ warm.removes`
|
|
121
|
+
* and an absent Cold tier just makes that merge trivial. `bulkLoadCrbmGeneration` is an **import** path for data
|
|
122
|
+
* you already have elsewhere, not an initialization step, and compaction is a **cost** optimization, never a
|
|
123
|
+
* correctness requirement. See the README and the getting-started guide.
|
|
119
124
|
*/
|
|
120
125
|
|
|
121
126
|
/**
|
|
@@ -396,6 +401,48 @@ declare class CloudRoaring {
|
|
|
396
401
|
* a small daemon-less deployment, or right after a targeted `remove`.
|
|
397
402
|
*/
|
|
398
403
|
compact(ref: SegmentRef, options: CompactionOptions): Promise<CompactionResult>;
|
|
404
|
+
/**
|
|
405
|
+
* **Dispose of a segment — tombstone it, delete its Warm rows, delete its Cold objects.** Irreversible.
|
|
406
|
+
*
|
|
407
|
+
* The operation a rolling window needs: `destroySegment` crypto-shreds (bytes unreadable everywhere including
|
|
408
|
+
* backups, but still sitting in your bucket and still billed, and it requires encryption), while this one
|
|
409
|
+
* removes the storage and works on a cleartext segment. On an encrypted segment it does both.
|
|
410
|
+
*
|
|
411
|
+
* Pass `{ dryRun: true }` first — it reports the generations it *would* delete and changes nothing. That
|
|
412
|
+
* matters more than the `confirmSegment` guard for anything automated, because in a loop the guard is the same
|
|
413
|
+
* variable twice.
|
|
414
|
+
*
|
|
415
|
+
* ```ts
|
|
416
|
+
* for (const day of expiredDays) {
|
|
417
|
+
* // A colon is NOT legal in a name — the family goes in the namespace, the date in the segment.
|
|
418
|
+
* const ref = { namespace: 'active-daily', segment: day };
|
|
419
|
+
* await store.dropSegment(ref, { confirmSegment: ref.segment });
|
|
420
|
+
* }
|
|
421
|
+
* ```
|
|
422
|
+
*
|
|
423
|
+
* **Omitting the namespace addresses a different segment and is a silent no-op** — you get
|
|
424
|
+
* `{ dropped: false, reason: 'absent' }`, not a throw, so a retention loop with that mistake deletes nothing
|
|
425
|
+
* forever and quietly. Check `reason` in an automated sweep.
|
|
426
|
+
*
|
|
427
|
+
* **Inspect `generationsRemaining`.** Empty is the normal outcome; non-empty means the storage was NOT fully
|
|
428
|
+
* reclaimed and the drop should be re-run. A compaction that was already in flight when the tombstone landed
|
|
429
|
+
* still finishes staging one more object, so a single sweep can miss it — this call re-sweeps and then reports
|
|
430
|
+
* whatever it still could not remove rather than returning a result that looks like a clean drop.
|
|
431
|
+
*
|
|
432
|
+
* Reads become empty within `coldGenTtlMs` (default 2 s), not instantly: a store that had already read this
|
|
433
|
+
* segment may answer from its cached generation + hot chunks until that window lapses. A reader that never
|
|
434
|
+
* touched it sees empty at once. **That bound needs a clock and `coldGenTtlMs > 0`** — a store built without a
|
|
435
|
+
* clock, or with `coldGenTtlMs: 0` ("pin forever"), holds its resolved snapshot for its own lifetime and can
|
|
436
|
+
* keep answering `true` for a dropped segment indefinitely; restart it.
|
|
437
|
+
*
|
|
438
|
+
* Needs the store built with a **raw cold driver + a registry** (throws {@link UnsupportedError} otherwise),
|
|
439
|
+
* because it has to enumerate and delete generations — a pre-built `ColdChunkSource` only reads.
|
|
440
|
+
*/
|
|
441
|
+
dropSegment(ref: SegmentRef, options: {
|
|
442
|
+
confirmSegment: string;
|
|
443
|
+
dryRun?: boolean;
|
|
444
|
+
audit?: IAuditSink;
|
|
445
|
+
}): Promise<DropResult>;
|
|
399
446
|
/**
|
|
400
447
|
* **Cross-tier DR consistency check (audit gap #11).** After a restore/failover, verify every registered
|
|
401
448
|
* segment's `currentGen` actually has its `.crbm` present in Cold — catching a **torn restore** where the
|
|
@@ -494,6 +541,32 @@ declare class Segment {
|
|
|
494
541
|
remove(id: number): Promise<void>;
|
|
495
542
|
/** Remove many ids, from a **sync or async** iterable. Not atomic across chunks — see {@link addMany}. */
|
|
496
543
|
removeMany(ids: Iterable<number> | AsyncIterable<number>): Promise<void>;
|
|
544
|
+
/**
|
|
545
|
+
* **Claim ids atomically — add them, and get back only the ones that were not already there.**
|
|
546
|
+
*
|
|
547
|
+
* The durable analogue of Redis `SETBIT` returning the prior bit, which is what an exactly-once *"have I
|
|
548
|
+
* already sent to / already processed this id?"* check needs. `has()` then `add()` cannot give you this: two
|
|
549
|
+
* workers can both read absent and both proceed.
|
|
550
|
+
*
|
|
551
|
+
* ```ts
|
|
552
|
+
* const sent = store.segment(day, { namespace: 'sent-daily' });
|
|
553
|
+
* const toSend = await sent.claimMany(candidateIds); // only the ids this worker won
|
|
554
|
+
* for (const id of toSend) enqueue(id); // ...so nobody else will send them
|
|
555
|
+
* ```
|
|
556
|
+
*
|
|
557
|
+
* **Pass a batch, not one id.** A Warm write rewrites a whole 64K-id chunk bitmap, so claiming ids one at a
|
|
558
|
+
* time is the single most expensive way to use this library — measured at ~5,000 writes and ~23 MB for 5,000
|
|
559
|
+
* ids, against 1 write and ~8 KB for the same ids in one call. This method does one OCC read-modify-write per
|
|
560
|
+
* distinct chunk, giving you Redis's *semantics* without Redis's per-id *cost shape*.
|
|
561
|
+
*
|
|
562
|
+
* **Exactly-once holds per id**, which is the guarantee that matters: each id lives in one chunk, a chunk is
|
|
563
|
+
* one OCC row, so exactly one concurrent claimer sees any given id as new. Like `addMany` it is **not** atomic
|
|
564
|
+
* across chunks — a mid-flight failure can leave some chunks claimed — but re-running is safe, because
|
|
565
|
+
* already-claimed ids simply come back as not-new.
|
|
566
|
+
*
|
|
567
|
+
* Order of the returned ids is unspecified.
|
|
568
|
+
*/
|
|
569
|
+
claimMany(ids: Iterable<number>): Promise<number[]>;
|
|
497
570
|
has(id: number): Promise<boolean>;
|
|
498
571
|
count(): Promise<number>;
|
|
499
572
|
iterate(): AsyncIterable<number>;
|
|
@@ -559,6 +632,6 @@ declare class Segment {
|
|
|
559
632
|
}
|
|
560
633
|
|
|
561
634
|
/** Package version marker. Kept in sync with package.json at release. */
|
|
562
|
-
declare const VERSION = "0.
|
|
635
|
+
declare const VERSION = "0.8.0";
|
|
563
636
|
|
|
564
637
|
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, collectWithinBudget, mapWithConcurrency, validateCompactionOptions, runConsistencyCheck, estimateCost, groundedReport, CrbmColdChunkSource } from '@cloudbitmaps/core';
|
|
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, gcOrphanGenerations, dropSegment, runConsistencyCheck, estimateCost, groundedReport, CrbmColdChunkSource } from '@cloudbitmaps/core';
|
|
2
2
|
export * from '@cloudbitmaps/core';
|
|
3
3
|
import roaring from 'roaring';
|
|
4
4
|
|
|
@@ -305,7 +305,7 @@ var CloudRoaring = class {
|
|
|
305
305
|
compactionDeps() {
|
|
306
306
|
if (this.coldDriver === void 0) {
|
|
307
307
|
throw new UnsupportedError(
|
|
308
|
-
"compact/eraseSubject/checkConsistency need the store built with a raw cold driver (IColdDriver), not a pre-built ColdChunkSource \u2014 or call the compactSegment/runConsistencyCheck free functions with explicit deps"
|
|
308
|
+
"compact/dropSegment/eraseSubject/checkConsistency need the store built with a raw cold driver (IColdDriver), not a pre-built ColdChunkSource \u2014 or call the compactSegment/runConsistencyCheck free functions with explicit deps"
|
|
309
309
|
);
|
|
310
310
|
}
|
|
311
311
|
if (this.registry === void 0) {
|
|
@@ -466,7 +466,56 @@ var CloudRoaring = class {
|
|
|
466
466
|
*/
|
|
467
467
|
async compact(ref, options) {
|
|
468
468
|
validateSegmentRef(ref);
|
|
469
|
-
|
|
469
|
+
const deps = this.compactionDeps();
|
|
470
|
+
const result = await compactSegment$1(ref, deps, options);
|
|
471
|
+
if (result.compacted) {
|
|
472
|
+
await gcOrphanGenerations(ref, { cold: deps.cold, registry: deps.registry }).catch(
|
|
473
|
+
() => void 0
|
|
474
|
+
);
|
|
475
|
+
}
|
|
476
|
+
return result;
|
|
477
|
+
}
|
|
478
|
+
/**
|
|
479
|
+
* **Dispose of a segment — tombstone it, delete its Warm rows, delete its Cold objects.** Irreversible.
|
|
480
|
+
*
|
|
481
|
+
* The operation a rolling window needs: `destroySegment` crypto-shreds (bytes unreadable everywhere including
|
|
482
|
+
* backups, but still sitting in your bucket and still billed, and it requires encryption), while this one
|
|
483
|
+
* removes the storage and works on a cleartext segment. On an encrypted segment it does both.
|
|
484
|
+
*
|
|
485
|
+
* Pass `{ dryRun: true }` first — it reports the generations it *would* delete and changes nothing. That
|
|
486
|
+
* matters more than the `confirmSegment` guard for anything automated, because in a loop the guard is the same
|
|
487
|
+
* variable twice.
|
|
488
|
+
*
|
|
489
|
+
* ```ts
|
|
490
|
+
* for (const day of expiredDays) {
|
|
491
|
+
* // A colon is NOT legal in a name — the family goes in the namespace, the date in the segment.
|
|
492
|
+
* const ref = { namespace: 'active-daily', segment: day };
|
|
493
|
+
* await store.dropSegment(ref, { confirmSegment: ref.segment });
|
|
494
|
+
* }
|
|
495
|
+
* ```
|
|
496
|
+
*
|
|
497
|
+
* **Omitting the namespace addresses a different segment and is a silent no-op** — you get
|
|
498
|
+
* `{ dropped: false, reason: 'absent' }`, not a throw, so a retention loop with that mistake deletes nothing
|
|
499
|
+
* forever and quietly. Check `reason` in an automated sweep.
|
|
500
|
+
*
|
|
501
|
+
* **Inspect `generationsRemaining`.** Empty is the normal outcome; non-empty means the storage was NOT fully
|
|
502
|
+
* reclaimed and the drop should be re-run. A compaction that was already in flight when the tombstone landed
|
|
503
|
+
* still finishes staging one more object, so a single sweep can miss it — this call re-sweeps and then reports
|
|
504
|
+
* whatever it still could not remove rather than returning a result that looks like a clean drop.
|
|
505
|
+
*
|
|
506
|
+
* Reads become empty within `coldGenTtlMs` (default 2 s), not instantly: a store that had already read this
|
|
507
|
+
* segment may answer from its cached generation + hot chunks until that window lapses. A reader that never
|
|
508
|
+
* touched it sees empty at once. **That bound needs a clock and `coldGenTtlMs > 0`** — a store built without a
|
|
509
|
+
* clock, or with `coldGenTtlMs: 0` ("pin forever"), holds its resolved snapshot for its own lifetime and can
|
|
510
|
+
* keep answering `true` for a dropped segment indefinitely; restart it.
|
|
511
|
+
*
|
|
512
|
+
* Needs the store built with a **raw cold driver + a registry** (throws {@link UnsupportedError} otherwise),
|
|
513
|
+
* because it has to enumerate and delete generations — a pre-built `ColdChunkSource` only reads.
|
|
514
|
+
*/
|
|
515
|
+
async dropSegment(ref, options) {
|
|
516
|
+
validateSegmentRef(ref);
|
|
517
|
+
const deps = this.compactionDeps();
|
|
518
|
+
return dropSegment(ref, { registry: deps.registry, warm: deps.warm, cold: deps.cold }, options);
|
|
470
519
|
}
|
|
471
520
|
/**
|
|
472
521
|
* **Cross-tier DR consistency check (audit gap #11).** After a restore/failover, verify every registered
|
|
@@ -565,6 +614,34 @@ var Segment = class {
|
|
|
565
614
|
removeMany(ids) {
|
|
566
615
|
return this.timed("removeMany", () => this.engine.removeMany(this.ref, ids));
|
|
567
616
|
}
|
|
617
|
+
/**
|
|
618
|
+
* **Claim ids atomically — add them, and get back only the ones that were not already there.**
|
|
619
|
+
*
|
|
620
|
+
* The durable analogue of Redis `SETBIT` returning the prior bit, which is what an exactly-once *"have I
|
|
621
|
+
* already sent to / already processed this id?"* check needs. `has()` then `add()` cannot give you this: two
|
|
622
|
+
* workers can both read absent and both proceed.
|
|
623
|
+
*
|
|
624
|
+
* ```ts
|
|
625
|
+
* const sent = store.segment(day, { namespace: 'sent-daily' });
|
|
626
|
+
* const toSend = await sent.claimMany(candidateIds); // only the ids this worker won
|
|
627
|
+
* for (const id of toSend) enqueue(id); // ...so nobody else will send them
|
|
628
|
+
* ```
|
|
629
|
+
*
|
|
630
|
+
* **Pass a batch, not one id.** A Warm write rewrites a whole 64K-id chunk bitmap, so claiming ids one at a
|
|
631
|
+
* time is the single most expensive way to use this library — measured at ~5,000 writes and ~23 MB for 5,000
|
|
632
|
+
* ids, against 1 write and ~8 KB for the same ids in one call. This method does one OCC read-modify-write per
|
|
633
|
+
* distinct chunk, giving you Redis's *semantics* without Redis's per-id *cost shape*.
|
|
634
|
+
*
|
|
635
|
+
* **Exactly-once holds per id**, which is the guarantee that matters: each id lives in one chunk, a chunk is
|
|
636
|
+
* one OCC row, so exactly one concurrent claimer sees any given id as new. Like `addMany` it is **not** atomic
|
|
637
|
+
* across chunks — a mid-flight failure can leave some chunks claimed — but re-running is safe, because
|
|
638
|
+
* already-claimed ids simply come back as not-new.
|
|
639
|
+
*
|
|
640
|
+
* Order of the returned ids is unspecified.
|
|
641
|
+
*/
|
|
642
|
+
claimMany(ids) {
|
|
643
|
+
return this.timed("claimMany", () => this.engine.claimMany(this.ref, ids));
|
|
644
|
+
}
|
|
568
645
|
has(id) {
|
|
569
646
|
return this.timed("has", () => this.engine.has(this.ref, id));
|
|
570
647
|
}
|
|
@@ -681,7 +758,7 @@ var Segment = class {
|
|
|
681
758
|
});
|
|
682
759
|
}
|
|
683
760
|
};
|
|
684
|
-
var VERSION = "0.
|
|
761
|
+
var VERSION = "0.8.0";
|
|
685
762
|
|
|
686
763
|
export { CloudRoaring, SafeBitmap, Segment, VERSION, bulkLoadCrbmGeneration, compactSegment, roaringCodec, runCompactionCycle, runExport };
|
|
687
764
|
//# sourceMappingURL=index.js.map
|