十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

libSQL Bottomless:基于 S3 兼容存储的虚拟 WAL 持续备份与恢复方案

libSQL Bottomless:基于 S3 兼容存储的虚拟 WAL 持续备份与恢复方案 libSQL Bottomless基于 S3 兼容存储的虚拟 WAL 持续备份与恢复方案【免费下载链接】libsqllibSQL is a fork of SQLite that is both Open Source, and Open Contributions.项目地址: https://gitcode.com/GitHub_Trending/li/libsqlBottomless 是 libSQL 项目中的一个虚拟 write-ahead logWAL实现它把 SQLite/libSQL 提交的每一个页面写入异步复制到 S3 兼容的对象存储中从而实现数据库的持续备份与按时间点恢复。本文以 bottomless/README.md 为主线结合仓库内 bottomless/ 的 Rust 源码与测试脚本完整讲解其构建、配置、使用、命令行工具与底层工作原理读完即可在自己的 libSQL 环境中搭建一套基于 MinIO 或 AWS S3 的数据库自动备份与恢复方案。项目定位与工作原理Bottomless 的核心思路是让 SQLite 的 WAL 不落在本地磁盘而是落到 S3 兼容存储。SQLite 本身通过 VFS/WAL 机制抽象页面写入libSQL 在此基础上提供了可插拔的libsql_wal_methodsBottomless 通过注册自己的 WAL 方法实现边写边备份。其工作流程可以概括为对应 bottomless/README.md 的 Details 一节异步复制所有提交到数据库的页面写入都会被异步复制到 S3 兼容存储启动恢复每次启动时如果主数据库文件为空则从远端存储恢复数据新世代上传如果本地数据库文件比远端更新则以新的 generation世代编号上传到远端WAL 追加上传如果检测到本地 WAL 文件比远端数据更新也会一并上传。从源码看这一机制通过 bottomless/src/bottomless_wal.rs 中的BottomlessWalWrapper实现它实现了 libSQL 的WrapWaltrait对底层 WAL 的insert_frames、checkpoint、savepoint_undo等关键操作进行包装insert_frames在底层 WAL 写入帧后立即将帧号范围提交给复制器replicator.submit_frames(...)见 bottomless/src/bottomless_wal.rscheckpoint只接受TRUNCATE级别的强 checkpoint因为只有这种模式能保证阻塞写入、把 WAL 全部拷回主库文件并重置帧号比它弱的 checkpoint 请求会被忽略并返回SQLITE_BUSY同时 checkpoint 前会等待 S3 复制器确认帧已备份完成见 bottomless/src/bottomless_wal.rs。复制逻辑集中在 bottomless/src/replicator.rs 的Replicator中它使用官方aws-sdk-s3Rust SDK 与 S3 交互见 bottomless/Cargo.toml支持 gzip / zstd 压缩async-compression依赖还支持可选的库级加密encryptionfeature。构建生成可加载的共享库扩展Bottomless 编译产物是一个可被 SQLite/libSQL 动态加载的.so扩展。仓库根目录的 bottomless/Makefile 封装了完整构建流程需要指定 libSQL 仓库目录# 调试模式默认 LIBSQL_DIR/path/to/your/libsql/directory make# Release 模式 LIBSQL_DIR/path/to/your/libsql/directory make release两种模式都会生成对应的bottomless.so调试模式../target/debug/bottomless.so相对 bottomless/ 目录即仓库的target/debug/bottomless.soRelease 模式../target/release/bottomless.so。从 bottomless/Makefile 可以看出构建细节先cargo build -p bottomless编译 Rust 库再用clang -fPIC -shared -DLIBSQL_ENABLE_BOTTOMLESS_WAL把 C 桥接文件 bottomless/bottomless.c 与libbottomless.a静态库链接成共享库。C 桥接层定义了扩展入口sqlite3_bottomless_init通过libsql_wal_methods_find(0)拿到原始 WAL 方法后用bottomless_methods(orig)包装并注册见 bottomless/bottomless.c。配置环境变量与 AWS 凭证体系Bottomless 的配置完全通过环境变量完成。默认情况下S3 存储期望运行在http://localhost:9000例如本地开发的 MinIO 服务器认证信息则通过标准的 S3 SDK 机制获取环境变量以及~/.aws/credentials文件如果存在。核心配置变量环境变量说明默认值LIBSQL_BOTTOMLESS_ENDPOINTS3 兼容服务端点地址http://localhost:9000LIBSQL_BOTTOMLESS_BUCKET用于复制的存储桶名bottomless例如覆盖默认端点export LIBSQL_BOTTOMLESS_ENDPOINThttp://localhost:9042自定义存储桶export LIBSQL_BOTTOMLESS_BUCKETcustom-bucketAWS 标准变量Bottomless 构建在官方 Rust S3 SDK 之上因此所有 AWS 标准环境变量都生效包括AWS_DEFAULT_REGIONAWS_ACCESS_KEY_IDAWS_SECRET_ACCESS_KEY~/.aws/credentials文件源码中定义的高级配置项阅读 bottomless/src/replicator.rs 中的Options::from_env()见 bottomless/src/replicator.rs可以发现更多可调参数这些参数在 README 之外为运维提供了精细控制环境变量说明默认值LIBSQL_BOTTOMLESS_DATABASE_ID数据库标识远端对象的目录名前缀未设置会报错无LIBSQL_BOTTOMLESS_AWS_ACCESS_KEY_ID/LIBSQL_BOTTOMLESS_AWS_SECRET_ACCESS_KEY/LIBSQL_BOTTOMLESS_AWS_SESSION_TOKEN显式指定静态凭证无LIBSQL_BOTTOMLESS_BATCH_INTERVAL_SECS批量帧的最长同步间隔未触发帧数阈值或 checkpoint 时兜底刷新15秒LIBSQL_BOTTOMLESS_BATCH_MAX_FRAMES每个 S3 对象最多容纳的 WAL 帧数10000LIBSQL_BOTTOMLESS_S3_PARALLEL_MAX并行的 S3 上传请求数上限32LIBSQL_BOTTOMLESS_S3_MAX_RETRIESS3 操作最大重试次数10LIBSQL_BOTTOMLESS_COMPRESSION帧压缩算法可选zstd/gzip等zstdLIBSQL_BOTTOMLESS_VERIFY_CRC恢复时是否校验帧校验和再写回主库trueLIBSQL_BOTTOMLESS_SKIP_SNAPSHOT每个 checkpoint 跳过快照上传falseLIBSQL_BOTTOMLESS_SKIP_SHUTDOWN_UPLOAD关闭时跳过快照上传falseLIBSQL_BOTTOMLESS_ENCRYPTION_CIPHER/LIBSQL_BOTTOMLESS_ENCRYPTION_KEY启用库级加密需编译encryptionfeature无需要说明的是LIBSQL_BOTTOMLESS_ENDPOINT这类端点配置目前以环境变量方式生效README 中提到未来会支持在 libSQL 的连接 URI 中直接作为参数传递。使用在 libSQL shell 中启用 Bottomless WAL从 libSQL shell 加载扩展并以bottomlessWAL 打开数据库文件.load ../target/debug/bottomless .open file:test.db?walbottomless PRAGMA journal_modewal;关键注意事项必须把日志模式设置为WAL并且至少要在写入任何内容之前执行一次否则不会使用自定义 WAL 实现。也就是说PRAGMA journal_modewal;是开启 Bottomless 备份的前置条件。日志级别可通过RUST_LOG环境变量定制例如RUST_LOGinfo ./libsql仓库内置了一个演示脚本 bottomless/test/smoke_test.sh执行LIBSQL_DIR/path/to/your/libsql/directory make test该脚本实际调用${LIBSQL_DIR}/libsql smoke_test.sql见 bottomless/test/smoke_test.sql完整展示了从加载扩展到写入、事务、SAVEPOINT 回滚、checkpoint 的典型流程.bail on .echo on .load ../../target/debug/bottomless .open file:test.db?walbottomless PRAGMA page_size65536; PRAGMA journal_modewal; PRAGMA page_size; DROP TABLE IF EXISTS test; CREATE TABLE test(v); INSERT INTO test VALUES (42); INSERT INTO test VALUES (zeroblob(8193)); INSERT INTO test VALUES (hey); .mode column BEGIN; INSERT INTO test VALUES (presavepoint); INSERT INTO test VALUES (zeroblob(1600000)); INSERT INTO test VALUES (zeroblob(1600000)); INSERT INTO test VALUES (zeroblob(2400000)); SAVEPOINT test1; INSERT INTO test VALUES (43); INSERT INTO test VALUES (zeroblob(2000000)); INSERT INTO test VALUES (zeroblob(2000000)); INSERT INTO test VALUES (zeroblob(2000000)); INSERT INTO test VALUES (heyyyy); ROLLBACK TO SAVEPOINT test1; COMMIT; BEGIN; INSERT INTO test VALUES (3.16); INSERT INTO test VALUES (zeroblob(1000000)); INSERT INTO test VALUES (zeroblob(1000000)); INSERT INTO test VALUES (zeroblob(1000000)); ROLLBACK; PRAGMA wal_checkpoint(FULL); INSERT INTO test VALUES (3.14); INSERT INTO test VALUES (zeroblob(31400)); PRAGMA wal_checkpoint(PASSIVE); PRAGMA wal_checkpoint(PASSIVE); INSERT INTO test VALUES (997); SELECT v, length(v) FROM test; .exit这段脚本刻意覆盖了多种边界场景大对象多 MB 的 zeroblob、SAVEPOINT 回滚后事务提交、完整回滚的事务、以及 FULL/PASSIVE 两种 checkpoint。对照源码 bottomless/src/bottomless_wal.rs可以印证为什么 PASSIVE checkpoint 需要执行两次——Bottomless 会忽略比 TRUNCATE 弱的 checkpoint 请求并返回SQLITE_BUSY因此这里调用两次是测试脚本对这种行为的适配。命令行工具 bottomless-clibottomless-cli 支持浏览、恢复和删除快照世代generation可独立安装为可执行文件RUSTFLAGS--cfg uuid_unstable cargo install bottomless-cli也可以直接在仓库中通过cargo run使用对应 bottomless-cli/ 目录。可用命令一览$ bottomless-cli --help Bottomless CLI Usage: bottomless-cli [OPTIONS] COMMAND Commands: ls List available generations restore Restore the database rm Remove given generation from remote storage help Print this message or the help of the given subcommand(s) Options: -e, --endpoint ENDPOINT -b, --bucket BUCKET -d, --database DATABASE -h, --help Print help information除 README 中列出的ls、restore、rm外从 bottomless-cli/src/main.rs 的Commands枚举可以看到仓库当前版本还扩展了copy把世代复制到本地目录、create从已有数据库创建新世代、verify校验数据库完整性内部执行PRAGMA integrity_check见 bottomless-cli/src/main.rs和snapshot为指定世代生成并上传快照等子命令。列出世代快照$ bottomless-cli -e http://localhost:9000 ls -v -l3 e4eb3c21-ff53-7b2e-a6ea-ca396f4df9b1 created at (UTC): 2022-12-23 08:24:52.500 change counter: [0, 0, 0, 51] consistent WAL frame: 0 WAL frame checksum: 0 main database snapshot: object size: 408 last modified: 2022-12-23T08:24:53Z e4eb3c22-0359-7af6-9acb-285ed7b6ed59 created at (UTC): 2022-12-23 08:24:51.470 change counter: [0, 0, 0, 51] consistent WAL frame: 1 WAL frame checksum: 5335f2a044d2f455 main database snapshot: object size: 399 last modified: 2022-12-23T08:24:52Z e4eb3c22-0941-73eb-85df-4e8552a0e88c created at (UTC): 2022-12-23 08:24:49.958 change counter: [0, 0, 0, 50] consistent WAL frame: 10 WAL frame checksum: 6ac65882f9a2dba7 main database snapshot: object size: 401 last modified: 2022-12-23T08:24:51Z其中-v输出每个世代的详细信息-l3限制只列出最新的 3 个世代。每个世代对应一个 UUID v7 标识输出包括创建时间、change counterSQLite 数据库头的修改计数器、一致的 WAL 帧号及其校验和、主数据库快照的对象大小与最后修改时间。当前版本的ls还支持--older-than/--newer-than日期过滤参数见 bottomless-cli/src/main.rs。恢复数据库$ RUST_LOGinfo bottomless-cli -e http://localhost:9000 restore 2022-12-23T10:16:10.703557Z INFO bottomless::replicator: Bucket bottomless exists and is accessible 2022-12-23T10:16:10.709526Z INFO bottomless_cli: Database: test.db 2022-12-23T10:16:10.713070Z INFO bottomless::replicator: Restoring from generation e4eb3c29-fe84-7347-a0c0-b9a3a71d0fc2 2022-12-23T10:16:10.727646Z INFO bottomless::replicator: Restored the main database file默认恢复最新世代当前版本还支持-g generation指定世代、--utc-time timestamp指定作为恢复事务上界的 UTC 时间戳实现按时间点恢复以及--from-dir从本地目录恢复见 bottomless-cli/src/main.rs。恢复完成后 CLI 会执行PRAGMA integrity_check验证数据库完整性并输出Verification: ok。删除旧快照$ bottomless-cli -e http://localhost:9000 rm -v --older-than 2022-12-15 Removed 4 generationsrm删除远端存储中指定世代结合--older-than可以批量清理给定日期之前的所有世代。远端存储布局与世代机制理解世代generation是使用 bottomless-cli 的前提。从 bottomless/src/replicator.rs 的Options注释可以还原 S3 对象布局每个数据库对应若干{db-name}-{uuid-v7}子目录即一个世代对应一个 UUID v7 目录每个世代目录内包含.meta文件记录数据库页大小与初始 WAL 校验和一系列{first-frame-no}-{last-frame-no}.{compression-kind}文件包含可供恢复的 WAL 帧批次。世代之间通过父代依赖关联新世代通常由 checkpoint 触发创建见 bottomless/src/bottomless_wal.rs 中replicator.new_generation()与snapshot_main_db_file()的调用。恢复时复制器从最新世代开始先取主数据库快照再按批次回放该世代的 WAL 帧MAX_RESTORE_STACK_DEPTH 100见 bottomless/src/replicator.rs限定了恢复时最多回溯的连续世代数也就是说每 100 个连续世代中至少要有一个包含完整快照从而保证恢复路径可收敛。值得注意的机制细节快照记录的是上一个世代结束时的数据库状态。如 bottomless-cli/src/main.rs 的注释所述generation N 的快照 generation N-1 的最终状态因此从世代 N 恢复 快照 该世代内的全部 WAL 帧这保证了恢复效率与数据完整性。本地测试MinIO smoke/restore 脚本仓库提供了一套完全本地的测试方案使用本地 S3 兼容服务器例如 MinIO。假设服务运行在 HTTP 端口 9000cd test/ export LIBSQL_BOTTOMLESS_ENDPOINThttp://localhost:9000 ./smoke_test.sh ./restore_test.sh两个脚本的行为bottomless/test/smoke_test.sh新建一个 WAL 模式、64KiB 页大小的数据库test.db然后向其中插入若干记录具体 SQL 见上文bottomless/test/restore_test.sh与复制服务器同步并获取最新数据库只要smoke_test至少运行过一次即使本地test.db文件被删除restore_test也总能取回数据库数据。从 bottomless/test/restore_test.sql 可以看到恢复侧的关键技巧以file:test.db?walbottomlessimmutable1方式打开数据库即直接以不可变模式读取远端恢复的数据库只做查询验证。这套测试同样适用于远端服务器。以 AWS S3 为例只需确保 AWS SDK 凭证有效且用户具有管理所选存储桶的权限即可。源码结构指引如果你希望深入理解实现建议按以下顺序阅读 bottomless/ 目录bottomless/src/replicator.rs核心复制器负责世代管理、帧批量上传、S3 客户端构建、恢复流程与进度指标基于metrics库暴露bottomless_s3_write_time、bottomless_snapshot_upload_time、bottomless_restore_time等指标bottomless/src/bottomless_wal.rsWrapWal实现把 SQLite WAL 的帧插入、checkpoint、SAVEPOINT 回滚与复制器桥接bottomless/src/backup.rsWalCopier负责把帧批量写入本地并转交 S3 上传队列bottomless/src/read.rsBatchReader从 S3 读取帧批次用于恢复bottomless/src/completion_progress.rsCompletionProgress/SavepointTracker跟踪上传进度与 SAVEPOINT 状态bottomless/bottomless.cC 扩展入口向 libSQL 注册包装后的 WAL 方法bottomless-cli/src/main.rsCLI 子命令与参数解析。小结Bottomless 为 libSQL 提供了一条零额外服务、纯对象存储的持续备份路径通过虚拟 WAL 将每次提交异步复制到 S3 兼容存储以世代generation 快照 WAL 帧批次的组合支持按世代、按时间点的恢复并提供ls/restore/rm/verify/snapshot等完整的生命周期管理命令。配合本地 MinIO 可以在几分钟内完成从构建到端到端验证的完整流程是理解 libSQL 虚拟 WAL 机制与 S3 集成的最佳切入点。注意该项目在 README 中标注为heavy progress快速迭代中实际使用时请以当前仓库代码与最新文档为准。【免费下载链接】libsqllibSQL is a fork of SQLite that is both Open Source, and Open Contributions.项目地址: https://gitcode.com/GitHub_Trending/li/libsql创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表