“默认情况下,未记录在日志中的最大丢失写入是在最后100毫秒内进行的。”这是来自管理日志记录的内容,这表明您可能会失去自上次将日志刷新到磁盘以来所做的写入。
如果想要更多的耐久性,“要强制mongod更频繁地提交到日志,请指定j:true。 当具有j:true的写入操作挂起时,mongod将减少journalCommitInterval到设置值的三分之一。”
即使在这种情况下,似乎将日志刷新到磁盘是异步的,因此仍有可能丢失写入。我是否忽略了确保不丢失写入的方法?
为了澄清问题,我发布了一个新答案。我进行了测试并再次阅读了源代码,我确定烦恼来自于写关注文档中的一句不幸的话。启用日志记录并使用j:true
写关注时,写操作是耐久的,并且没有神秘的数据丢失窗口。
即使启用了日志记录,MongoDB仍然有可能丢失写操作吗?
是的,因为耐久性也取决于单个操作的写关注。
"默认情况下,最大程度上丢失的写入(即未被写入日志的写入)是在最后100毫秒内进行的。"
这是从管理日志中得出的结论,它表明您可能会失去自上次将日志刷新到磁盘以来所做的写入。
没错。日志由单独的线程异步刷新,因此您可能会失去自上次刷新以来所有内容。
如果我想要更高的耐久性,“要强制mongod更频繁地提交日志,可以指定
j:true
。当带有j:true
的写操作正在等待时,mongod将把journalCommitInterval
减少到设置值的三分之一。”
这也让我感到烦恼。这是它的意思:
当您发送带有j:true
的写操作时,它不会立即触发磁盘刷新,也不会在网络线程上触发。这是有道理的,因为可能有数十个应用程序与同一个mongod实例交谈。如果每个应用程序都大量使用日志记录,则db将非常缓慢,因为它一直在fsync。
相反,发生的情况是,“耐久性线程”将获取所有待处理的日志提交并将其刷新到磁盘。该线程的实现方式如下(我的评论):
sleepmillis(oneThird); //dur.cpp, line 801
for( unsigned i = 1; i <= 2; i++ ) {
// break, if any j:true write is pending
if( commitJob._notify.nWaiting() )
break;
// or the number of bytes is greater than some threshold
if( commitJob.bytes() > UncommittedBytesLimit / 2 )
break;
// otherwise, sleep another third
sleepmillis(oneThird);
}
// fsync all pending writes
durThreadGroupCommit();
因此,待处理的j:true
操作将导致日志提交线程比通常情况下更早地提交,并将提交所有未设置j:true
的挂起写入到日志中。
即使在这种情况下,似乎将日志刷新到磁盘是异步的,因此仍有可能丢失写入。我是否缺少了确保不丢失写入的方法?
使用j:true
日志记录写入关注点进行的写入(或getLastError
命令)将等待持久性线程完成同步,因此不存在数据丢失的风险(只要操作系统和硬件可以保证)。
句子“但是,在日志提交之间存在一个时间窗口,写入操作尚未完全持久”可能指的是启用了日志记录的mongod接受了不使用j:true
写入关注点的写入。在这种情况下,由于上次日志提交以来,写入可能会丢失。
我已经为此提交了文档错误报告。
j: true
,请求确认指定在 w: <value>
中的 mongod 实例已写入到磁盘日志。” - undefinedjournalCommitInterval
的默认值为 100ms,否则为 30ms(因此最好将日志放在单独的磁盘上)。journalCommitInterval
更改为至少 2ms,但它会增加写操作的数量并降低总体写入性能。因此,最好的情况下,有一个2毫秒的时间窗口可以丢失数据。然而,这对于许多应用程序来说是不足够的。
fsync
命令会强制刷新所有数据文件到磁盘,但如果您使用日志记录,则这是不必要的,而且效率低下。
现实生活中的耐久性
即使您每次写入都进行日志记录,如果数据中心管理员在您的硬件上使用电锯或硬件本身出现故障,那么这有什么好处呢?
在许多情况下,不像RAID那样在块设备级别上进行冗余存储,而是在更高的级别上进行冗余存储,是更好的选择:将数据放置在不同的位置或至少在不同的机器上使用副本集,并使用启用了日志记录的w:majority
写关注(尽管日志记录仅适用于主服务器)。在单个机器上使用RAID以增加您的运气。
证据
简而言之:我认为我上面的答案是正确的。
文档可能有点烦人,特别是关于 wtimeout
的部分,因此我查看了源代码。虽然我不是 mongo 源代码的专家,但请谨慎对待以下内容:
在 write_concern.cpp
中,我们发现(为简洁起见进行了编辑):
if ( cmdObj["j"].trueValue() ) {
if( !getDur().awaitCommit() ) {
// --journal is off
result->append("jnote", "journaling not enabled on this server");
} // ...
}
else if ( cmdObj["fsync"].trueValue() ) {
if( !getDur().awaitCommit() ) {
// if get here, not running with --journal
log() << "fsync from getlasterror" << endl;
result->append( "fsyncFiles" , MemoryMappedFile::flushAll( true ) );
}
fsync
,则调用MemoryMappedFile :: flushAll(true)
。此调用显然不在第一个分支中。否则,耐久性在单独的线程上处理(相关文件前缀为dur_
)。wtimeout
的作用:它是指等待从节点的时间,并与服务器上的I/O或fsync无关。日志记录是为了使特定的mongod上的数据保持一致性状态,即使在出现极端情况下也能够恢复,但是通过客户端设置写关注(writeconcern),可以强制实现耐久性。有关写关注的详细信息请查看此文档。
有一个选项j:1
,您可以在此处了解更多信息,它可以确保特定的写操作等待确认,直到它被写入磁盘上的日志文件(而不仅仅是内存映射中)。然而,这文档表述相反。:) 我会选择第一种情况,因为它让我感觉更加舒适。
如果你使用这个选项运行很多命令,MongoDB会自动调整日志的提交间隔大小以加速处理速度,您可以在此处了解更多信息,您也提到了这点,正如其他人已经说过的那样,您可以指定2-300ms之间的间隔。
在我看来,与w:2选项相比,耐久性得到了更好的保证,因为如果更新/写操作被复制集中的两个成员确认,则在同一分钟内失去两者的可能性非常小(数据文件刷新间隔),但不是不可能的。
同时使用这两个选项将导致这样一种情况,即当数据库集群确认操作时,该操作将存在于两个不同的框箱中,并且在其中一个框箱中它将位于一致可恢复的磁盘位置。
我同意Sammaye的观点,日志记录与持久性关系不大。然而,如果你想得到一个答案,是否可以真正信任mongodb以良好的一致性存储你的数据,那么我建议你阅读这篇博客文章。这篇文章有10gen对该帖子的回复,以及作者对10gen帖子的回复。我建议你仔细阅读,做出明智的决定。我自己花了一些时间才理解所有细节,但这篇文章已经涵盖了基础知识。
这里是mongodb制造公司10gen对博客文章的回应。
在这篇文章上,教授对回应进行了回应。
它详细解释了Mongodb如何分片数据,它的实际功能以及如果添加额外的安全锁会带来的性能影响。我强烈想说,这三篇文章是目前最好的东西,也是关于mongodb利弊的最全面的东西,如果你认为它是片面的,请看看评论,还要看看人们的意见,因为如果某个东西得到了软件制造公司的回复,那么它肯定至少提出了一些好的观点。
一般来说,在每个系统中,只要存在缓存、延迟写入等与系统运行时和永久(非易失性)存储介质之间的处理,即使在操作系统级别(例如写入后缓存),都会出现数据丢失问题。因此,即使您使用的具体提供商(如MongoDB)提供了事务耐久性功能,但最终写入数据的还是底层操作系统,甚至在设备级别也存在缓存...而这只是更低层次的问题,如果将系统高度并发、分布式且高性能,则问题会更加严重。
简而言之,没有绝对的耐久性,只有实际/最终/盼望着有耐久性,特别是对于像Mongo这样的NoSQL存储,它并不是为了一致性和耐久性而设计的。