这种基于文件的数据库方法的可扩展性如何?
How scalable is this file-based DB approach?
我有一个简单的 PHP 脚本可以计算给定字符串输入的某些内容。它将结果缓存到数据库中,我们偶尔会删除超过特定天数的条目。
我们的程序员将此数据库实现为:
function cachedCalculateThing($input) {
$cacheFile = 'cache/' . sha1($input) . '.dat';
if (file_exists($cacheFile) {
return json_decode(file_get_contents($cacheFile));
}
$retval = ...
file_put_contents(json_encode($retval));
}
function cleanCache() {
$stale = time() - 7*24*3600;
foreach (new DirectoryIterator('cache/') as $fileInfo) {
if ($fileInfo->isFile() && $fileInfo->getCTime() < $stale) {
unlink($fileInfo->getRealPath());
}
}
我们使用 Ubuntu LAMP 和 ext3。缓存查找的条目数达到多少时变为非常量或违反硬限制?
虽然该特定代码根本不是很 "scalable"*,但有很多地方可以改进它:
- sha1 接受一个字符串。因此,在计算哈希值之前,非字符串 $input 变量必须先序列化或 json_encoded 。只需更改顺序即可防止意外输入。
- 使用crc32代替sha1,速度更快(Fastest hash for non-cryptographic uses?)
- 目录'cache/'是相对于当前目录的,所以随着页面工作目录的改变,缓存目录也会改变。缓存未命中次数人为偏高。
- 每次在 cachedCalculateThing() 中存储文件时,将文件名存储在 /dev/shm/indexedofcaches 中的索引中(或类似的名称)。在调用 file_exists 之前检查索引。 ext3 很慢,缓存和内核 ext3 索引将被调出。因此,这意味着每次询问是否 file_exists 时都会进行目录扫描。对于小型缓存,它足够快,但对于大型缓存,您会看到速度变慢。
- 写入会阻塞,因此当缓存为空时会达到服务器负载限制,当两个或更多 php 写入者同时尝试写入时,缓存文件名会发生冲突以前不存在的缓存文件。因此,您可能想尝试捕获这些错误 and/or 进行锁定文件测试。
- 我们正在一个有点原始的环境中考虑此代码。事实上,根据当前的磁盘利用率,写入也会阻塞一段不确定的时间。如果您的磁盘是旋转磁盘或较旧的 ssd,您可能会看到写入速度非常非常慢。检查
iostat -x 4
并查看您当前的磁盘利用率。如果它已经高于 25%,打开磁盘缓存会在随机时间使其达到 100% 并减慢所有 Web 服务。 (因为对磁盘的请求必须(通常)按顺序排队和服务(并非总是如此,但不要指望它))。
- 根据缓存文件的大小,可能直接将它们存储在 /dev/shm/my_cache_files/ 中。如果它们都适合内存,那么您就可以将磁盘完全置于服务链之外。然后你必须执行一个 cron 作业来检查整体缓存大小并确保它不会占用你所有的内存。缺点 = 非持久性。不过,您也可以对其进行备份调度。
- 不要在 runtime/service 代码中调用 cleanCache()。该目录迭代扫描将非常慢并且阻塞。
'*对于可扩展性,通常以线性请求速度或并行服务器资源来定义。该代码:
- (-) 取决于 when/where cleanCache() 函数是 运行 -- 它有效地阻止目录索引,直到缓存目录中的所有项目都被扫描。所以它应该进入一个cron工作。如果在 cron/shell 作业中,有更快的方法来删除过期的缓存。例如:
find ./cache -type f -mtime +7 -exec rm -f "{}" \;
- (-) 你提到 ext3 是正确的 -- ext3 对小文件和非常大的目录内容的索引和结果速度相对较差。 google 索引没有时间,如果你可以将缓存目录移动到一个单独的卷,你可以关闭日志,避免双重写入,或者使用一个单独的文件系统类型。或者查看您是否有 dir_index 可用作挂载选项。这是一个基准 link:http://fsi-viewer.blogspot.com/2011/10/filesystem-benchmarks-part-i.html
- (+) 使用 rsync 比数据库复制更容易将目录缓存条目分发到其他服务器。
- (+/-) 实际上,这取决于您将存储多少不同的缓存项以及访问频率。对于少量文件,比如 10-100 个,小于 100K,频繁点击,然后内核将缓存文件分页到内存中,你不会看到任何严重的减速(如果实施得当)。
要点是要实现真正的可扩展性和缓存系统的良好性能,比短代码块显示的要多考虑一点。可能有比我列举的更多的限制,但即使是这些限制也受变量的影响,例如大小、条目数、requests/sec 的数量、当前磁盘负载、文件系统类型等——在代码之外。这是意料之中的,因为缓存在代码之外持续存在。列出的代码可以执行少量请求的小型精品缓存,但可能不适用于需要缓存的较大尺寸。
此外,您 运行Apache 是以线程模式还是预分叉模式?它将影响 php 阻止其读取和写入的方式。
-- 嗯,我可能应该补充说你想跟踪你的对象和 key/hash.. 如果 $input 已经是一个字符串,它已经在它的基础 form/has 中计算、检索、序列化等。如果 $input 是键,则 file_put_contents() 需要放置其他内容(实际的 variable/contents)。如果 $input 是要查找的对象(可能是一个长字符串,甚至是一个短字符串),那么它需要一个查找键,否则没有计算 bypassed/saved.
我有一个简单的 PHP 脚本可以计算给定字符串输入的某些内容。它将结果缓存到数据库中,我们偶尔会删除超过特定天数的条目。
我们的程序员将此数据库实现为:
function cachedCalculateThing($input) {
$cacheFile = 'cache/' . sha1($input) . '.dat';
if (file_exists($cacheFile) {
return json_decode(file_get_contents($cacheFile));
}
$retval = ...
file_put_contents(json_encode($retval));
}
function cleanCache() {
$stale = time() - 7*24*3600;
foreach (new DirectoryIterator('cache/') as $fileInfo) {
if ($fileInfo->isFile() && $fileInfo->getCTime() < $stale) {
unlink($fileInfo->getRealPath());
}
}
我们使用 Ubuntu LAMP 和 ext3。缓存查找的条目数达到多少时变为非常量或违反硬限制?
虽然该特定代码根本不是很 "scalable"*,但有很多地方可以改进它:
- sha1 接受一个字符串。因此,在计算哈希值之前,非字符串 $input 变量必须先序列化或 json_encoded 。只需更改顺序即可防止意外输入。
- 使用crc32代替sha1,速度更快(Fastest hash for non-cryptographic uses?)
- 目录'cache/'是相对于当前目录的,所以随着页面工作目录的改变,缓存目录也会改变。缓存未命中次数人为偏高。
- 每次在 cachedCalculateThing() 中存储文件时,将文件名存储在 /dev/shm/indexedofcaches 中的索引中(或类似的名称)。在调用 file_exists 之前检查索引。 ext3 很慢,缓存和内核 ext3 索引将被调出。因此,这意味着每次询问是否 file_exists 时都会进行目录扫描。对于小型缓存,它足够快,但对于大型缓存,您会看到速度变慢。
- 写入会阻塞,因此当缓存为空时会达到服务器负载限制,当两个或更多 php 写入者同时尝试写入时,缓存文件名会发生冲突以前不存在的缓存文件。因此,您可能想尝试捕获这些错误 and/or 进行锁定文件测试。
- 我们正在一个有点原始的环境中考虑此代码。事实上,根据当前的磁盘利用率,写入也会阻塞一段不确定的时间。如果您的磁盘是旋转磁盘或较旧的 ssd,您可能会看到写入速度非常非常慢。检查
iostat -x 4
并查看您当前的磁盘利用率。如果它已经高于 25%,打开磁盘缓存会在随机时间使其达到 100% 并减慢所有 Web 服务。 (因为对磁盘的请求必须(通常)按顺序排队和服务(并非总是如此,但不要指望它))。 - 根据缓存文件的大小,可能直接将它们存储在 /dev/shm/my_cache_files/ 中。如果它们都适合内存,那么您就可以将磁盘完全置于服务链之外。然后你必须执行一个 cron 作业来检查整体缓存大小并确保它不会占用你所有的内存。缺点 = 非持久性。不过,您也可以对其进行备份调度。
- 不要在 runtime/service 代码中调用 cleanCache()。该目录迭代扫描将非常慢并且阻塞。
'*对于可扩展性,通常以线性请求速度或并行服务器资源来定义。该代码:
- (-) 取决于 when/where cleanCache() 函数是 运行 -- 它有效地阻止目录索引,直到缓存目录中的所有项目都被扫描。所以它应该进入一个cron工作。如果在 cron/shell 作业中,有更快的方法来删除过期的缓存。例如:
find ./cache -type f -mtime +7 -exec rm -f "{}" \;
- (-) 你提到 ext3 是正确的 -- ext3 对小文件和非常大的目录内容的索引和结果速度相对较差。 google 索引没有时间,如果你可以将缓存目录移动到一个单独的卷,你可以关闭日志,避免双重写入,或者使用一个单独的文件系统类型。或者查看您是否有 dir_index 可用作挂载选项。这是一个基准 link:http://fsi-viewer.blogspot.com/2011/10/filesystem-benchmarks-part-i.html
- (+) 使用 rsync 比数据库复制更容易将目录缓存条目分发到其他服务器。
- (+/-) 实际上,这取决于您将存储多少不同的缓存项以及访问频率。对于少量文件,比如 10-100 个,小于 100K,频繁点击,然后内核将缓存文件分页到内存中,你不会看到任何严重的减速(如果实施得当)。
要点是要实现真正的可扩展性和缓存系统的良好性能,比短代码块显示的要多考虑一点。可能有比我列举的更多的限制,但即使是这些限制也受变量的影响,例如大小、条目数、requests/sec 的数量、当前磁盘负载、文件系统类型等——在代码之外。这是意料之中的,因为缓存在代码之外持续存在。列出的代码可以执行少量请求的小型精品缓存,但可能不适用于需要缓存的较大尺寸。
此外,您 运行Apache 是以线程模式还是预分叉模式?它将影响 php 阻止其读取和写入的方式。
-- 嗯,我可能应该补充说你想跟踪你的对象和 key/hash.. 如果 $input 已经是一个字符串,它已经在它的基础 form/has 中计算、检索、序列化等。如果 $input 是键,则 file_put_contents() 需要放置其他内容(实际的 variable/contents)。如果 $input 是要查找的对象(可能是一个长字符串,甚至是一个短字符串),那么它需要一个查找键,否则没有计算 bypassed/saved.