RxDB Memory-Mapped RxStorage:内存读写与底层持久化相结合的混合存储加速方案 📅 发布时间:2026/9/20 22:12:47 👁 浏览次数: RxDB Memory-Mapped RxStorage内存读写与底层持久化相结合的混合存储加速方案【免费下载链接】rxdbThe local-first database that runs on every JS runtime and replicates with your existing backend - no vendor, no lock-in - https://rxdb.info/项目地址: https://gitcode.com/gh_mirrors/rx/rxdbMemory-Mapped RxStorage 是 RxDB Premium 提供的一个存储包装器wrapper它在内存中维护一份完整数据副本用于查询与写入同时在后台把变更同步到任意底层持久化 RxStorage。本文从架构原理、优缺点、多标签页限制、加密组合、写入持久化保证、块大小配置到存储迁移完整讲解这一内存加速 磁盘持久混合方案的配置方式与适用场景。什么是 Memory-Mapped RxStorageRxDB 本身不自带数据引擎所有数据都存放在实现了 RxStorage 接口 的存储实现中例如浏览器里的 LocalStorage、IndexedDBNode.js 下的 SQLite、FoundationDB 等。这种可插拔设计让你可以根据运行环境与性能需求自由更换底层存储详见 RxStorage 概览。Memory-Mapped RxStorage下文简称内存映射存储正是这一生态中的一种包装器型存储它包装任意其他 RxStorage在内存中创建一份用于查询与写入的存储实例并让这份内存实例与给定的底层存储保持持久同步。其核心思路与普通的 Memory RxStorage 不同——后者只存在内存、进程退出即丢失而内存映射存储把内存当作读写的热层把底层存储当作落盘的冷层从而同时获得内存级的速度与磁盘级的持久性。从底层实现看内存热层本质上复用了基于纯 JavaScript 数组与二分查找实现的 getRxStorageMemory它没有磁盘 I/O、没有 JSON 序列化开销这正是读写性能的来源而落盘与同步逻辑则由getMemoryMappedRxStorage()来自rxdb-premium/plugins/storage-memory-mapped负责协调。工作原理内存热层 持久冷层要理解内存映射存储的行为需要把握三个关键机制初始化批量加载创建数据库时存储会把底层持久层中的全部数据通过一次批量请求读入内存。正因为是单次 bulk 读取首次页面加载的数据读取开销可以被压缩到最小若检测到数据库是全新创建底层没有任何数据甚至无需等待持久存储实例创建完成即可开始使用。写入先内存、落盘异步写操作默认直接作用于内存态并立刻返回持久化在后台异步进行。区块链式块结构为了兼顾快速首屏加载与低写入延迟内存映射存储将数据以类似区块链的追加式块block结构保存——写入被追加进新的块而不是原地修改状态。这些块会在 CPU 空闲时被惰性清理与合并参见下文块大小限制一节底层对应 RxDB 的 requestIdlePromise经由数据库的 idleQueue 实现见 rx-database.ts。这种写追加、空闲合并的设计把随机写转换为顺序追加写显著降低了写入延迟同时通过空闲期的后台整理保证后续读取与初始加载的高效。优点Pros读写性能提升查询与写入直接运行在内存存储上绕过了磁盘 I/O 与序列化开销。首屏加载更快全部数据在一次 bulk 请求中载入首次使用时甚至无需等待持久存储的创建。加密数据可查询可以把文档数据加密后落盘同时仍然能够在未加密的内存态上运行查询详见下文持久化数据的加密一节。缺点与限制Cons不支持附件attachments因为大体积附件数据不应保存在内存中见 rx-attachment.md。异常终止可能丢失写入当 JavaScript 进程被非正常终止如浏览器崩溃、电脑断电时部分尚未同步到父存储的内存写入可能丢失。可通过awaitWritePersistence标志规避。数据必须能全部装入内存内存映射存储要求全部数据放入 JavaScript 进程的内存。通常情况下这不是问题——现代浏览器内存充足纯 JSON 文档数据量并不大。首次加载可能变慢因为需要把底层已有数据一次性读入内存当已存数据很多时初始页面加载时间可能增加。存储文档数量小于约10k时通常没有影响。基本用法包装任意持久化存储内存映射存储的接入非常简单先用getRxStorageIndexedDB()或其他任意 RxStorage创建持久层再用getMemoryMappedRxStorage({ storage: parentStorage })包装它最后把这个包装后的 storage 传入createRxDatabase即可。其余 RxDB API 完全不受影响。import { getRxStorageIndexedDB } from rxdb-premium/plugins/storage-indexeddb; import { getMemoryMappedRxStorage } from rxdb-premium/plugins/storage-memory-mapped; /** * 这里使用 IndexedDB RxStorage 作为持久化存储 * 任何其他 RxStorage 也都可以使用。 */ const parentStorage getRxStorageIndexedDB(); // 用内存映射存储包装持久化存储 const storage getMemoryMappedRxStorage({ storage: parentStorage }); // 像使用任何其他 RxStorage 一样创建 RxDatabase const db await createRxDatabase({ name: myDatabase, storage, }); /** ... **/典型组合浏览器低延迟场景在 RxStorage 配置示例 中官方给出了一个面向低写入延迟 简单读取的浏览器配置用内存映射存储做热层用主线程的 OPFS 存储做持久层再叠加 LocalStorage Meta Optimizer 优化初始化元数据读取。之所以不用 Worker是因为主线程与 Worker 之间来回传输数据本身会增加延迟import { getLocalstorageMetaOptimizerRxStorage } from rxdb-premium/plugins/storage-localstorage-meta-optimizer; import { getMemoryMappedRxStorage } from rxdb-premium/plugins/storage-memory-mapped; import { getRxStorageOPFSMainThread } from rxdb-premium/plugins/storage-worker; const myDatabase await createRxDatabase({ storage: getLocalstorageMetaOptimizerRxStorage({ storage: getMemoryMappedRxStorage({ storage: getRxStorageOPFSMainThread() }) }) });典型组合Node.js 服务端场景在 Node.js 数据库指南 中官方展示了如何在 Node.js 下用 FoundationDB 作为持久层实现内存数据库的性能 数据的持久化import { createRxDatabase } from rxdb; import { getRxStorageFoundationDB } from rxdb/plugins/storage-foundationdb; import { getMemoryMappedRxStorage } from rxdb-premium/plugins/storage-memory-mapped; const db await createRxDatabase({ name: exampledb, storage: getMemoryMappedRxStorage({ storage: getRxStorageFoundationDB({ apiVersion: 620, clusterFile: /path/to/fdb.cluster }) }) });需要注意这种内存 持久方案有两个固有代价数据库大小受限于内存容量Node.js 进程若在写入内存态与后台持久化之间退出写入可能丢失。针对后者请在服务端场景设置awaitWritePersistence: true。在 RxDB Server 扩容指南 中也有类似用法面对大量用户请求时把内存映射存储放在用户侧用文件系统 Node 存储做持久层从而让服务器端读取直接命中内存层文中同样提醒若担心服务器崩溃导致内存层写入丢失应在内存映射存储配置中打开awaitWritePersistence。多标签页支持Multi-Tab由于内存映射存储的工作方式所限同一个存储无法在多个 JavaScript 进程中同时打开。因此在浏览器应用里当应用被多个浏览器标签页使用时你不能在多个标签页中各自打开数据库。解决方案是配合 SharedWorker Plugin让内存映射存储运行在 SharedWorker 中且只初始化一次随后被所有浏览器标签页复用。如果你运行在单一 JavaScript 进程中例如 React Native 应用则无需关心这一点直接在主进程中使用内存映射存储即可。持久化数据的加密常规情况下RxDB 无法在加密字段上运行查询。但使用内存映射存储后你可以把文档数据加密后写入磁盘同时仍然能在未加密的内存态上执行查询。关键点在于加密存储包装器要包在持久化存储外面而不是包在内存映射存储整体外面。即先wrappedKeyEncryptionWebCryptoStorage({ storage: getRxStorageIndexedDB() })得到加密持久层再把它作为storage传入getMemoryMappedRxStorageimport { getRxStorageIndexedDB } from rxdb-premium/plugins/storage-indexeddb; import { getMemoryMappedRxStorage } from rxdb-premium/plugins/storage-memory-mapped; import { wrappedKeyEncryptionWebCryptoStorage } from rxdb-premium/plugins/encryption-web-crypto; const storage getMemoryMappedRxStorage({ storage: wrappedKeyEncryptionWebCryptoStorage({ storage: getRxStorageIndexedDB() }) }); const db await createRxDatabase({ name: myDatabase, storage, }); /** ... **/这样落盘数据是密文、内存态是明文查询在明文上进行兼顾了数据静态加密与字段可查询。加密的完整配置与密码管理方式见 encryption.md。Await Write Persistence等待落盘完成默认情况下内存映射存储上的操作在内存态执行完就立即返回变更在后台异步持久化。若你希望确保某个写操作确实已持久化到底层存储可以把awaitWritePersistence设为trueconst storage getMemoryMappedRxStorage({ awaitWritePersistence: true, storage: getRxStorageIndexedDB() });代价是每次写入都要等待持久层完成写入延迟会相应上升。因此它适合宁可慢一点也不能丢数据的关键写入场景而默认的异步模式适合对吞吐与延迟更敏感、可容忍极端情况下少量写入丢失的场景。Block Size Limit控制合并块大小在清理cleanup过程中内存映射存储会把许多小的写入块合并成少数大块以换取更好的初始加载性能。blockSizeLimit定义了单个块中最多能存放多少文档默认值为10000const storage getMemoryMappedRxStorage({ blockSizeLimit: 1000, storage: getRxStorageIndexedDB() });调小该值会产生更多、更小的块清理更频繁调大则产生更少、更大的块首屏加载更快但单次合并开销更大。当你的单集合文档数接近或超过默认值时建议结合数据集规模评估是否需要调整。从其他存储迁移到内存映射存储当你从普通持久化存储如 IndexedDB 或 SQLite切换到内存映射存储时必须使用 Storage Migrator存储迁移插件 迁移数据不能直接在一个已存在的数据库上替换存储适配器——因为内存映射存储使用完全不同的内部数据结构区块链式块结构。存储迁移的基本用法以从 LocalStorage 迁移到 IndexedDB 为例同样的流程适用于迁入内存映射存储import { migrateStorage } from rxdb/plugins/migration-storage; import { getRxStorageIndexedDB } from rxdb-premium/plugins/storage-indexeddb; import { getRxStorageLocalstorage } from rxdb-old/plugins/storage-localstorage; // 创建新的 RxDatabase新库名必须与旧库不同 const db await createRxDatabase({ name: dbLocation, storage: getRxStorageIndexedDB(), multiInstance: false }); await migrateStorage({ database: db as any, oldDatabaseName: myOldDatabaseName, // 旧数据库名 oldStorage: getRxStorageLocalstorage(), // 旧数据库使用的 RxStorage batchSize: 500, // 批大小 parallel: false, // true: 并行迁移所有集合false(默认): 串行迁移 afterMigrateBatch: (input: AfterMigrateBatchHandlerInput) { console.log(storage migration: batch processed); } });需要特别注意的几点迁移期间不要同时改 schema若还想改 schema应先完成存储迁移再执行常规的 schema 迁移否则可能导致数据库不可用。只迁移已定义的集合调用migrateStorage()时新数据库中不存在的集合不会被迁移会被跳过你可以借此只迁移部分集合。删除的文档会被丢弃存储迁移会过滤并丢弃已删除文档。决策建议何时使用内存映射存储综合本文的分析可以给出如下选型判断场景是否推荐原因需要查询/写入性能又要求数据落盘✅ 推荐内存热层 磁盘冷层的组合性能与持久性兼得数据量可完整装入内存约 10k 文档✅ 推荐初始加载影响可忽略收益最大需要加密落盘且字段可查询✅ 推荐加密持久层 明文内存态是独有能力浏览器多标签页同时使用⚠️ 需配合 SharedWorker同一存储不可被多进程打开写入后进程随时可能被杀⚠️ 需开启awaitWritePersistence否则极端情况下可能丢写需要存储附件attachments❌ 不推荐内存映射存储不支持附件数据集远超内存容量❌ 不推荐数据必须整体装入内存简言之内存映射存储最适合数据量可控、读多写多、需要持久化的本地优先local-first应用接入前务必评估数据规模、多标签页需求与写入可靠性要求必要时配合 SharedWorker、awaitWritePersistence与存储迁移插件一起使用。【免费下载链接】rxdbThe local-first database that runs on every JS runtime and replicates with your existing backend - no vendor, no lock-in - https://rxdb.info/项目地址: https://gitcode.com/gh_mirrors/rx/rxdb创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考