@cloudbitmaps/roaring

中文文档
0.27.0发布于 2026/11/03Apache-2.0TypeScriptES Module构建来源已验证近 30 天下载 2401

Distributed, cloud-native Roaring Bitmaps — query and intersect billion-scale integer sets straight out of object storage, from a bounded RAM cache over immutable objects in your own bucket, at a fraction of an always-on cache.

原始使用文档
文档目录9 节
  1. Install
  2. Use
  3. Options
  4. What it costs
  5. Before production
  6. Coming from Redis bitmaps?
  7. Documentation
  8. Credits
  9. License

@cloudbitmaps/roaring

Large sets of integer ids, stored in your own object storage and read from anywhere. Load a set as one immutable file, then ask has, count, iterate, intersect, union and andNot, with no server or cache to run. Part of CloudBitmaps; this is the package you import from.

ESM-only, Node ≥ 22.12. Use import; for require(), Jest and TypeScript, see CommonJS, Jest and TypeScript.

Pre-1.0: the API and the on-disk format can still change. On npm these docs match the version they ship in; on GitHub they describe main, and the changelog says what each release changed.

Install

pnpm add @cloudbitmaps/roaring              # the store, with in-memory and local-disk backends
pnpm add @cloudbitmaps/s3                   # the storage you have: or @cloudbitmaps/gcs, or @cloudbitmaps/azure-blob
pnpm add @cloudbitmaps/tools                # optional: price a store, offline

Install the same version of every @cloudbitmaps/* package: they release together, and each pins the exact version of core it was built with.

The store's Roaring is written in TypeScript, with no native code: what that means.

Use

No setup needed: MemoryStorage keeps everything in the process, which is right for a first look and for tests. Save this as first-run.mjs and run it with node:

import { CloudRoaring, MemoryStorage } from '@cloudbitmaps/roaring';

const store = new CloudRoaring({ storage: new MemoryStorage() });

await store.load({ segment: 'shoppers' }, [5, 99_999, 1_234_567_890]); // a load replaces the segment
await store.load({ segment: 'active' }, [5, 7, 1_234_567_890]);

const shoppers = store.segment('shoppers');
console.log(await shoppers.has(99_999)); // true
console.log(await shoppers.count()); // 3
for await (const id of shoppers.intersect([store.segment('active')])) console.log(id); // 5, then 1234567890

To persist, pass a different backend as storage. Nothing else changes:

BackendFromUse it for
MemoryStorage@cloudbitmaps/roaringtests and a first look
LocalFsStorage('./.cloudbitmaps')@cloudbitmaps/roaringone process on one folder: a laptop or a CI job
S3Storage({ bucket, prefix })@cloudbitmaps/s3production: the validated one
GcsStorage({ bucket, prefix })@cloudbitmaps/gcsGoogle Cloud Storage; outside the validated envelope
AzureBlobStorage({ connectionString, container })@cloudbitmaps/azure-blobAzure Blob Storage; outside the validated envelope

RoaringBitmap is the codec's bitmap as a public class: a mutable set of unsigned 32-bit ids with the portable Roaring bytes, in TypeScript with no native code. Import it from @cloudbitmaps/roaring, the same line as the store: import { CloudRoaring, MemoryStorage, RoaringBitmap } from '@cloudbitmaps/roaring'. A store loads one as it is, and RoaringBitmap.deserialize(bytes) is the one door for bytes you did not write.

Two calls are slower than the native roaring package by a ratio above 10 in the bench, and ship with their figures shown. The first deserialize in a fresh process has a measured ratio of about 14.6 (median 2.20 ms against 0.151 ms); it is a one-time cost of the first call (the expected reason is code not yet optimized plus compile time), and later decodes have measured ratios of 2.2 to 5.9 across the five data sets. The union of 64 dense bitmaps with orMany has a measured ratio of about 11. Every figure is in the table.

import { CloudRoaring, MemoryStorage, RoaringBitmap } from '@cloudbitmaps/roaring';

const store = new CloudRoaring({ storage: new MemoryStorage() });

const vip = new RoaringBitmap([5, 99_999, 1_234_567_890]); // any iterable of ids
vip.add(7);
vip.removeMany([99_999]);
vip.addMany(new RoaringBitmap([8, 9])); // another bitmap, or a Uint32Array, works too

await store.load({ segment: 'vip' }, vip); // read, never changed or kept

// Bytes from another process go through the one safe door: capped, checked, one whole bitmap.
const received = RoaringBitmap.deserialize(vip.serialize());
console.log(received.size, received.minimum(), received.maximum()); // 5 5 1234567890

The API reference has every method and where it differs from the roaring package.

A stored segment has no add or remove (RoaringBitmap does, for a bitmap in memory): a segment changes only by loading a new generation. A load is a batch job, not a request handler. store.exists(ref) says whether a segment has been loaded. A load takes ids, or a bitmap you already hold: store.load(ref, bitmap) for a RoaringBitmap (or a RoaringBitmap32), or { serialized } for portable Roaring bytes, checked before anything is written and loaded with no per-id work, into a generation holding chunk for chunk what its ids would write (its footer carries its own write identity).

To write many results over the same stored operands, store.materializeMany reads each operand once for a whole batch of and / or / andNot outputs instead of once per *Into call, and a set you already hold in memory streams in as its feed. Many outputs from one pass covers both.

Options

new CloudRoaring({ storage, ...options }). Only storage is required. A key the store does not take is refused by name.

OptionWhat it holds
storage (required)a backend (see above)
cachemaxChunks, ttlMs (an optional age limit on a cached chunk; omitted, a chunk stays until evicted), genTtlMs (how soon a reader sees a new load, default 2000 ms), readerMax, readerMaxBytes
encryption{ keystore, required }: encrypt segments at rest with your own key
retrytune the retry of reads, or false to turn it off
metricsa metrics sink; off by default
budget{ maxRequests } or false: the per-call request ceiling
seams{ clock, rng, entropy }: determinism, for tests and replayable jobs; the source of each generation's write identity is the last

What it costs

A cold intersect of two 500,000-id segments sharing 100 of their 1,999 chunks made 6 GETs and took 107.87 ms at the median, $2.40 per million at list prices: measured on S3 in-region, in the run of 2026-10-30, whose client had 128 sockets. On a spread layout it reads more bytes than it needs, which cross-region can cost more than the requests it saves. A segment's first single-part store.load() made 2 PUT + 3 GET, $11.20 per million at the default prices, pointer included: the requests are measured on S3 in-region, in the same run. A steady single-part store.load() at keep: 12 that deletes a generation by name costs $12.00 per million, for 2 PUT + 5 GET and a free delete, the requests measured on S3 in-region, in the same run. A cold count() is one pointer read: it reads no payload and no object.

The trade is stated plainly. A membership check that misses the cache costs a ranged GET against object storage, where an in-process store costs a memory read. If you need sub-millisecond answers on a working set that fits a bounded cache, use Redis. The benchmarks give the method and what the numbers do not establish. To price your own store, from the sizes seg.stat() reports, use @cloudbitmaps/tools (the cost guide).

Before production

  • Run await store.preflight() once before the first load. It reads the registry, writes, tries to write again, reads, lists and deletes one probe object and checks the backend's lifecycle rules, and answers { ok, checks }; see store.preflight(). It does not check s3:AbortMultipartUpload, the registry's If-Match, or a read after a delete, among others: the full list.
  • Grant the permissions the library issues and add a lifecycle rule to abort incomplete uploads. The S3 and GCS backends' checkLifecycle() read the bucket's rules and say whether one would delete or archive what the store keeps; Azure Blob's answers unchecked and says how to check by hand; MemoryStorage and LocalFsStorage have no rules and answer ok.
  • Use a bucket that honors conditional writes and is strongly consistent (a read or a listing made after a write or a delete sees it), and @aws-sdk/client-s3 3.700.0 or later if you pass your own S3 client. Preflight reports a second write that succeeds (which is how an SDK of 3.640.0 or older shows) and a read or listing that lags a write, but not an SDK from 3.641.0 to 3.699.0, which keeps If-None-Match but drops If-Match: the SDK floor stays yours to check.
  • Set a request timeout on your storage client. Reads retry by themselves, and a read can be timed on every backend (readTimeoutMs); a write that fails transiently is yours to re-run.
  • Back up the registry with the data, and the keystore too if you encrypt. If you lose the key, the data is gone.
  • Schedule your loads, and retireExpired if you use retention. Run loads off the request path.

The production checklist covers each item.

Coming from Redis bitmaps?

GETBIT is has, BITCOUNT is count, and BITOP AND / OR / DIFF are intersect / union / andNot, with intersectInto, unionInto and andNotInto publishing the result as a new generation, and materializeMany publishing many such results from one pass. EXPIRE becomes setRetention plus a retireExpired sweep you schedule, per segment, never per id. SETBIT has no equivalent: there is no per-id write. Build the set upstream and load it, and do not loop one id at a time. Remove one id everywhere with eraseSubject, a rewrite for compliance, not a hot-path verb. This is not a drop-in replacement, and what does not carry over is the write model and the raw bytes: exportSegments writes portable Roaring and ndjson, not Redis's layout. The full mapping says what else differs.

Documentation

Credits

The Roaring Bitmap format and its algorithms are the work of Daniel Lemire and colleagues. See roaringbitmap.org and the Roaring format specification, which defines the portable bytes every chunk of a .crbm object holds. The store's codec implements that format and those algorithms in TypeScript (how it is held to CRoaring); CRoaring, through the roaring binding, is the reference its tests compare it against. The papers:

  • S. Chambi, D. Lemire, O. Kaser, R. Godin, "Better bitmap performance with Roaring bitmaps", Software: Practice and Experience 46(5), 2016. arXiv:1402.6407
  • D. Lemire, G. Ssi-Yan-Kai, O. Kaser, "Consistently faster and smaller compressed bitmaps with Roaring", Software: Practice and Experience 46(11), 2016. arXiv:1603.06549
  • D. Lemire, O. Kaser, N. Kurz, L. Deri, C. O'Hara, F. Saint-Jacques, G. Ssi-Yan-Kai, "Roaring Bitmaps: Implementation of an Optimized Software Library", Software: Practice and Experience 48(4), 2018. arXiv:1709.07821

License

Apache-2.0

版本与发布记录

@cloudbitmaps/roaring 共发布 40 个版本,首次发布于 2026/07/26;过去 12 个月发布了 40 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
0.27.0latest2026/11/036 天前
0.26.02026/11/028 天前
0.25.02026/10/3010 天前
0.24.02026/10/2417 天前
0.23.02026/10/2120 天前
0.22.02026/10/1823 天前

关于 @cloudbitmaps/roaring 的常见问题

@cloudbitmaps/roaring 是什么?

Distributed, cloud-native Roaring Bitmaps — query and intersect billion-scale integer sets straight out of object storage, from a bounded RAM cache over immutable objects in your own bucket, at a fraction of an always-on cache.

如何安装 @cloudbitmaps/roaring?

在项目目录运行 npm install @cloudbitmaps/roaring,或使用 pnpm add / yarn add / bun add。它还提供命令行工具 export-segments,可以用 npx export-segments 直接运行。

@cloudbitmaps/roaring 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@cloudbitmaps/roaring 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@cloudbitmaps/roaring 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=22.12。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@cloudbitmaps/roaring 可以商用吗?

@cloudbitmaps/roaring 使用 Apache-2.0 许可。宽松许可:允许商业使用、修改和再分发,并附带明确的专利授权;分发时需保留许可与 NOTICE 文件,修改过的文件要注明改动。以上为概括,不构成法律意见,请以许可原文为准。

@cloudbitmaps/roaring 的下载量有多少?

根据 npm 官方下载统计,@cloudbitmaps/roaring 在 2026/10/10 – 2026/11/08 这 30 天内被下载 2,401 次,日均约 80 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@cloudbitmaps/roaring 还在维护吗?

最新版本 0.27.0 发布于 2026/11/03(6 天前);至今共发布 40 个版本,其中过去 12 个月发布了 40 个;首次发布于 2026/07/26。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。