注意
本文档适用于 Ceph 的开发版本。
Ceph SQLite VFS
此 SQLite VFS 可用于存储和访问由 RADOS 支持的 SQLite 数据库。这允许您使用 Ceph 的对象存储完全去中心化您的数据库,以提高可用性、可访问性和存储利用率。
请注意这不是什么:分布式 SQL 引擎。RADOS 上的 SQLite 可以看作是 RBD 与 CephFS 的比较:RBD 将磁盘镜像放在 RADOS 上,以便机器独占访问,并且通常不允许其他机器并行访问;另一方面,CephFS 允许从许多客户端挂载对文件系统进行完全分布式访问。RADOS 上的 SQLite 旨在在给定时间由单个 SQLite 客户端数据库连接访问。数据库只能通过 Ceph SQLite VFS 管理的 RADOS 锁以串行方式由多个客户端安全地操作。
用法
普通未修改的应用程序(包括 sqlite 命令行工具集二进制文件)可以使用 SQLite 扩展加载 API 加载 ceph VFS。
.LOAD libcephsqlite.so
或在调用 sqlite3 期间
sqlite3 -cmd '.load libcephsqlite.so'
数据库文件格式为 SQLite URI
file:///<"*"poolid|poolname>:[namespace]/<dbname>?vfs=ceph
RADOS namespace 是可选的。请注意路径中的三斜杠 ///。URI 权限在 SQLite 中必须为空或 localhost。仅解析 URI 的路径部分。因此,如果您只使用两个 //,URI 将无法正确解析。
一个(可选)创建数据库和打开的完整示例
sqlite3 -cmd '.load libcephsqlite.so' -cmd '.open file:///foo:bar/baz.db?vfs=ceph'
请注意,您不能将数据库文件指定为 sqlite3 的正常位置参数。这是因为 .load libcephsqlite.so 命令是在打开数据库之后应用的,但打开数据库取决于扩展首先被加载。
一个传递池整数 id 且没有 RADOS 命名空间的示例
sqlite3 -cmd '.load libcephsqlite.so' -cmd '.open file:///*2:/baz.db?vfs=ceph'
与其他 Ceph 工具一样,ceph VFS 会查看一些有助于配置要与之通信的 Ceph 集群和要使用的凭据的环境变量。这里是一个典型的配置
export CEPH_CONF=/path/to/ceph.conf
export CEPH_KEYRING=/path/to/ceph.keyring
export CEPH_ARGS='--id myclientid'
./runmyapp
# or
sqlite3 -cmd '.load libcephsqlite.so' -cmd '.open file:///foo:bar/baz.db?vfs=ceph'
默认操作将使用 client.admin 用户查看标准 Ceph 配置文件路径。
用户
ceph VFS 需要一个用户凭据,该凭据具有对监视器的读取访问权限、阻止数据库的死客户端的能力以及对托管数据库的 OSD 的访问权限。这可以通过授权来完成,就像
ceph auth get-or-create client.X mon 'allow r, allow command "osd blocklist" with blocklistop=add' osd 'allow rwx'
注意
术语从 blacklist 更改为 blocklist;较旧的集群可能需要使用旧术语。
您也可以简化使用 simple-rados-client-with-blocklist 配置文件
ceph auth get-or-create client.X mon 'profile simple-rados-client-with-blocklist' osd 'allow rwx'
要了解为什么需要阻止列表,请参阅 如何损坏数据库。
页面大小
SQLite 允许在创建新数据库之前配置页面大小。建议在使用 RADOS 支持的数据库时将此配置增加到 65536(64K),以减少 OSD 读取/写入次数,从而提高吞吐量和延迟。
PRAGMA page_size = 65536
您也可以根据您的应用程序需求尝试其他值,但请注意,64K 是 SQLite 施加的最大值。
缓存
ceph VFS 不进行任何读取缓存或写入缓冲。相反,更恰当地使用了 SQLite 页面缓存。您可能会发现它对于大多数工作负载来说太小了,因此应该显着增加它
PRAGMA cache_size = 4096
这将缓存 4096 页或 256MB(使用 64K page_cache)。
日志持久性
默认情况下,SQLite 会删除每个事务的日志。这可能很昂贵,因为 ceph VFS 必须为每个事务删除支持日志的每个对象。因此,要求 SQLite 持久化 日志会更快、更简单。在此模式下,SQLite 将通过写入其标头使日志无效。这是通过以下方式完成的
PRAGMA journal_mode = PERSIST
这样做的代价可能是根据回滚日志的高水位大小(基于事务类型和大小)增加未使用的空间。
排他锁定模式
SQLite 在 NORMAL 锁定模式下运行,其中每个事务都需要锁定支持数据库文件。当您知道给定时间只有一个数据库用户时,这会给事务增加不必要的开销。您可以使用以下方式让 SQLite 在连接期间锁定一次数据库
PRAGMA locking_mode = EXCLUSIVE
这可以使执行事务所需的时间减少一半以上。请记住,这会阻止其他客户端访问数据库。
在此锁定模式下,对数据库的每次写入事务都需要 3 个同步事件:一次写入日志,另一次写入数据库文件,最后一次写入使日志标头无效(在 PERSIST 日志模式下)。
WAL 日志
WAL 日志模式 仅当 SQLite 在排他锁定模式下运行时才可用。这是因为它在 NORMAL 锁定模式下需要与其他读取器和写入器进行共享内存通信。
与本地磁盘数据库一样,WAL 模式可以显着减少小事务延迟。测试表明,它可以在排他锁定模式下比持久回滚日志提供超过 50% 的加速。您可以预期每秒大约 150-250 个事务,具体取决于大小。
性能说明
RADOS 上数据库的文件后端尽可能异步。尽管如此,性能可能比 SSD 上的本地数据库慢 3 到 10 倍。延迟可能是一个主要因素。建议熟悉 SQL 事务和其他高效数据库更新策略。根据底层池的性能,您可以预期小事务需要长达 30 毫秒才能完成。如果您使用 EXCLUSIVE 锁定模式,则可以进一步减少到每个事务 15 毫秒。在 EXCLUSIVE 锁定模式下的 WAL 日志可以进一步减少到低至约 2-5 毫秒(或完成 RADOS 写入的时间;您不会得到更好的了!)。
Ceph VFS 对 RADOS 上的 SQLite 数据库大小没有限制。需要注意标准的 SQLite 限制,尤其是最大数据库大小为 281 TB。大型数据库在 Ceph 上可能具有也可能不具有性能。建议针对您自己的用例进行试验。
请注意,读取繁重的查询可能需要大量时间,因为读取必然是同步的(由于 VFS API)。VFS 尚未执行任何预读。
推荐用例
此模块的最初目的是支持将需要跨越多个对象的关联或大型数据保存在 RADOS 中。许多具有琐碎状态的当前应用程序尝试在单个对象上使用 RADOS omap 存储,但这无法在不跨越多个对象条带化数据的情况下进行扩展。不幸的是,设计一个跨越多个对象且一致且易于使用的存储是非平凡的。SQLite 可用于弥合这一差距。
并行访问
VFS 尚不支持并发读取器。所有数据库访问均受单个排他锁保护。
从 RADOS 导出或提取数据库
数据库在 RADOS 上进行条带化,可以使用 RADOS cli 工具集提取。
rados --pool=foo --striper get bar.db local-bar.db
rados --pool=foo --striper get bar.db-journal local-bar.db-journal
sqlite3 local-bar.db ...
请记住,回滚日志也进行了条带化,如果数据库处于事务中间,则也需要提取。如果您使用 WAL,则也需要提取该日志。
请记住,使用 striper 提取数据库使用的 RADOS 锁与 ceph VFS 使用的锁相同。但是,日志文件锁未被 ceph VFS 使用(SQLite 仅锁定主数据库文件),因此在提取两个文件时可能存在与其他 SQLite 客户端的竞争。这可能导致获取损坏的日志。
建议使用 SQLite 备份 机制,而不是手动提取文件。
临时表
不支持由 ceph VFS 支持的临时表。主要原因是 VFS 缺乏有关应将数据库放在何处(即哪个 RADOS 池)的上下文。与临时数据库关联的持久数据库未通过 SQLite VFS API 进行通信。
相反,建议附加一个辅助本地或 内存数据库,并将临时表放在那里。或者,您可以设置连接 pragma
PRAGMA temp_store=memory
打破锁
对数据库文件的访问受数据库第一个对象条带上的排他锁保护。如果应用程序在未解锁数据库的情况下失败(例如,分段错误),即使客户端连接随后被阻止列表,锁也不会自动解锁。最终,锁将根据配置超时
cephsqlite_lock_renewal_timeout = 30000
超时以毫秒为单位。一旦达到超时,OSD 将使锁过期并允许客户端重新锁定。发生这种情况时,数据库将由 SQLite 恢复,正在进行的事务将回滚。恢复数据库的新客户端也将阻止旧客户端,以防止流氓写入导致潜在的数据库损坏。
数据库排他锁的持有者将定期续订锁,以免丢失锁。这对于大型事务或在 EXCLUSIVE 锁定模式下运行的数据库连接是必要的。锁续订间隔可通过以下方式调整
cephsqlite_lock_renewal_interval = 2000
此配置的单位也是毫秒。
如果您知道客户端已永久消失(例如,被阻止列表),则可以提前打破锁。这允许立即恢复对客户端的数据库访问。例如
$ rados --pool=foo --namespace bar lock info baz.db.0000000000000000 striper.lock
{"name":"striper.lock","type":"exclusive","tag":"","lockers":[{"name":"client.4463","cookie":"555c7208-db39-48e8-a4d7-3ba92433a41a","description":"SimpleRADOSStriper","expiration":"0.000000","addr":"127.0.0.1:0/1831418345"}]}
$ rados --pool=foo --namespace bar lock break baz.db.0000000000000000 striper.lock client.4463 --lock-cookie 555c7208-db39-48e8-a4d7-3ba92433a41a
如何损坏数据库
在使用此工具之前,您应该查看关于 如何损坏 SQLite 数据库 的常规阅读材料。除此之外,您最有可能损坏数据库的方式是流氓进程暂时失去网络连接,然后恢复其工作。它持有的排他 RADOS 锁将丢失,但它无法立即知道。它在重新获得网络连接后可能执行的任何工作都可能损坏数据库。
ceph VFS 库默认设置不允许发生这种情况。如果 Ceph VFS 检测到不完整的清理,它将阻止数据库排他锁的最后一个所有者。
通过阻止旧客户端,旧客户端在返回时不再可能恢复对数据库的工作(取决于阻止列表过期时间,默认为 3600 秒)。要关闭阻止先前客户端,请更改
cephsqlite_blocklist_dead_locker = false
除非您知道由于其他保证而不会导致数据库损坏,否则不要这样做。如果此配置为 true(默认值),则如果 ceph VFS 无法阻止先前实例(例如,由于缺乏授权),它将懦弱地失败。
一个存在带外机制来阻止数据库排他锁的最后一个死持有者的示例是在 ceph-mgr 中。监视器会知道用于 ceph VFS 的 RADOS 连接,并在 ceph-mgr 故障转移期间阻止实例。这可以防止僵尸 ceph-mgr 继续工作并可能损坏数据库。因此,新实例中的 ceph VFS 没有必要执行阻止列表命令(但它仍然这样做,无害)。
要手动阻止 ceph VFS,您可以使用 ceph_status SQL 函数查看 ceph VFS 的实例地址
SELECT ceph_status();
{"id":788461300,"addr":"172.21.10.4:0/1472139388"}
您可以使用 JSON1 扩展 轻松操作该信息
SELECT json_extract(ceph_status(), '$.addr');
172.21.10.4:0/3563721180
这是您将传递给 ceph blocklist 命令的地址
ceph osd blocklist add 172.21.10.4:0/3082314560
性能统计
ceph VFS 提供了一个 SQLite 函数 ceph_perf,用于查询 VFS 的性能统计信息。数据来自“性能计数器”,就像通常通过 admin socket 查询的其他 Ceph 服务一样。
SELECT ceph_perf();
{"libcephsqlite_vfs":{"op_open":{"avgcount":2,"sum":0.150001291,"avgtime":0.075000645},"op_delete":{"avgcount":0,"sum":0.000000000,"avgtime":0.000000000},"op_access":{"avgcount":1,"sum":0.003000026,"avgtime":0.003000026},"op_fullpathname":{"avgcount":1,"sum":0.064000551,"avgtime":0.064000551},"op_currenttime":{"avgcount":0,"sum":0.000000000,"avgtime":0.000000000},"opf_close":{"avgcount":1,"sum":0.000000000,"avgtime":0.000000000},"opf_read":{"avgcount":3,"sum":0.036000310,"avgtime":0.012000103},"opf_write":{"avgcount":0,"sum":0.000000000,"avgtime":0.000000000},"opf_truncate":{"avgcount":0,"sum":0.000000000,"avgtime":0.000000000},"opf_sync":{"avgcount":0,"sum":0.000000000,"avgtime":0.000000000},"opf_filesize":{"avgcount":2,"sum":0.000000000,"avgtime":0.000000000},"opf_lock":{"avgcount":1,"sum":0.158001360,"avgtime":0.158001360},"opf_unlock":{"avgcount":1,"sum":0.101000871,"avgtime":0.101000871},"opf_checkreservedlock":{"avgcount":1,"sum":0.002000017,"avgtime":0.002000017},"opf_filecontrol":{"avgcount":4,"sum":0.000000000,"avgtime":0.000000000},"opf_sectorsize":{"avgcount":0,"sum":0.000000000,"avgtime":0.000000000},"opf_devicecharacteristics":{"avgcount":4,"sum":0.000000000,"avgtime":0.000000000}},"libcephsqlite_striper":{"update_metadata":0,"update_allocated":0,"update_size":0,"update_version":0,"shrink":0,"shrink_bytes":0,"lock":1,"unlock":1}}
您可以使用 JSON1 扩展 轻松操作该信息
SELECT json_extract(ceph_perf(), '$.libcephsqlite_vfs.opf_sync.avgcount');
776
这告诉您 SQLite 调用 VFS 的 SQLite IO Methods 的 xSync 方法的次数(对于进程中的所有打开的数据库连接)。您可以在查询之前和之后分析性能统计信息,以查看所需的“文件系统同步”次数(这只与事务次数成比例)。或者,您可能对完成写入的平均延迟更感兴趣
SELECT json_extract(ceph_perf(), '$.libcephsqlite_vfs.opf_write');
{"avgcount":7873,"sum":0.675005797,"avgtime":0.000085736}
这将告诉您已进行了 7873 次写入,平均完成时间为 85 微秒。这清楚地表明调用是异步执行的。回到同步
SELECT json_extract(ceph_perf(), '$.libcephsqlite_vfs.opf_sync');
{"avgcount":776,"sum":4.802041199,"avgtime":0.006188197}
平均花费 6 毫秒执行同步调用。这会收集所有异步写入以及对条带化文件大小的异步更新。
调试
可以通过以下方式打开 libcephsqlite 调试
debug_cephsqlite
如果运行 sqlite3 命令行工具,请使用
env CEPH_ARGS='--log_to_file true --log-file sqlite3.log --debug_cephsqlite 20 --debug_ms 1' sqlite3 ...
这将所有常见的 Ceph 调试保存到文件 sqlite3.log 中以供检查。