Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

现在通过 snapshot 持久化 apply index 是不是有问题? #414

Open
lygn128 opened this issue Aug 1, 2023 · 6 comments
Open

现在通过 snapshot 持久化 apply index 是不是有问题? #414

lygn128 opened this issue Aug 1, 2023 · 6 comments

Comments

@lygn128
Copy link

lygn128 commented Aug 1, 2023

1 对于 kv 存储 上次 snap shot 之后,apply 了许多条日志 ,但是重启之后,这些 日志会被重新 apply 了一遍,如果这些日志的操作是 ++i 这种操作的话就是有问题的吧? 谢谢

@ehds
Copy link
Contributor

ehds commented Aug 2, 2023

首先 snapshot 保存的状态是此时此刻 $ApplyIndex_{t}$ 完成时的状态机状态,在做 snapshot 的时候状态机是不会继续往前 apply 的,(串行化同步执行)。

是重启之后,这些 日志会被重新 apply 了一遍

重启时,braft 是会从最近的一次快照进行恢复 load_snapshot , 将状态机恢复到该 snapshot 的 $ApplyIndex_{t}$ 是的状态,然后继续执行 $LogIndex_{t+1}$

如果这些日志的操作是 ++i 这种操作的话就是有问题的吧?

不会有问题, 因为恢复快照的里面的状态机只保存了快照时的状态,并不会保有快照后的状态,所以即使日志被 apply 了两次,但是都是基于同一个历史在apply,等价于只执行了一次。

例如你的状态机是执行 ++i 的操作。
当状态机执行到第 $3$ 条日志时,此时状态机 $i = 3$, 此时触发快照,快照内容为 $ApplyIndex = 3, i=3$。 然后后又有两条日志被执行,此时 $ApplyIndex=5, i=5$。 这时重启节点,braft 会从快照恢复状态为 $ ApplyIndex = 3, i=3$, 再继续重放后面 2 个日志到 $ApplyIndex=5, i=5$。完成恢复。

在实际情况中,可能真正做 snapshot 是很复杂或者耗时的,快照的执行可能会和状态机异步执行,取决于用户的实现,但是也得保证上述的条件,snapshot 的状态必须是此时此刻的状态(状态机的状态和ApplyIndex),否则在恢复过程中会造成数据的不一致。

@lygn128
Copy link
Author

lygn128 commented Aug 2, 2023

首先 snapshot 保存的状态是此时此刻 ApplyIndext 完成时的状态机状态,在做 snapshot 的时候状态机是不会继续往前 apply 的,(串行化同步执行)。

是重启之后,这些 日志会被重新 apply 了一遍

重启时,braft 是会从最近的一次快照进行恢复 load_snapshot , 将状态机恢复到该 snapshot 的 ApplyIndext 是的状态,然后继续执行 LogIndext+1 。

如果这些日志的操作是 ++i 这种操作的话就是有问题的吧?

不会有问题, 因为恢复快照的里面的状态机只保存了快照时的状态,并不会保有快照后的状态,所以即使日志被 apply 了两次,但是都是基于同一个历史在apply,等价于只执行了一次。

例如你的状态机是执行 ++i 的操作。 当状态机执行到第 3 条日志时,此时状态机 i=3, 此时触发快照,快照内容为 ApplyIndex=3,i=3。 然后后又有两条日志被执行,此时 ApplyIndex=5,i=5。 这时重启节点,braft 会从快照恢复状态为 $ ApplyIndex = 3, i=3$, 再继续重放后面 2 个日志到 ApplyIndex=5,i=5。完成恢复。

在实际情况中,可能真正做 snapshot 是很复杂或者耗时的,快照的执行可能会和状态机异步执行,取决于用户的实现,但是也得保证上述的条件,snapshot 的状态必须是此时此刻的状态(状态机的状态和ApplyIndex),否则在恢复过程中会造成数据的不一致。

多谢大佬,这么做的话确实可以解决 重复 apply 的问题; 具体对于如果要使用 rocksdb 存储的系统,这种是不是会带来空间的浪费?要保存两份数据,一份 rocksdb 的数据,一份 rocksdb 快照的数据,每次重启后相当于删除掉rocksdb 的数据,然后从快照 apply ? 谢谢

@lygn128
Copy link
Author

lygn128 commented Aug 2, 2023

rocksdb 快照硬链接会减少一部分空间,但如果持续性的写入,快照对应的文件一直删除不掉,应该依旧会带来空间的浪费吧?

@ehds
Copy link
Contributor

ehds commented Aug 2, 2023

应该依旧会带来空间的浪费吧

我们之前的做法是在 SaveSnapshot 的时候通过 RocksDB 的 snapshot 拿到当时的状态,然后遍历 DBIter 写到 SSTFile。这中做法虽然能保证snapshot的正确性,但是会有你提到的空间浪费,数据的大小最坏的情况可能占有实际 数据量3倍(2个快照+1个数据库文件)。而且 Iter 也会有很大的读开销。

后面我们使用的是 RocksDB 提供的 CheckPoint 功能,也就是你说的硬链接的方式。这种方式的好处是 快照里面的数据文件 和此时数据库的数据文件 有相同 FileNumber 的是共享的。只有不同的文件(即 snapshot 之后新增/删除 也就是 由 Flush/Compaction 产生/删除的)会占用一定的磁盘空间, 但是这部分文件实际占有总数据量很少的一部分 (10%)左右,主要取决于你数据库的负载和 snapshot 的频率,已经是可以接受的范围。

@lygn128
Copy link
Author

lygn128 commented Aug 2, 2023

应该依旧会带来空间的浪费吧

我们之前的做法是在 SaveSnapshot 的时候通过 RocksDB 的 snapshot 拿到当时的状态,然后遍历 DBIter 写到 SSTFile。这中做法虽然能保证snapshot的正确性,但是会有你提到的空间浪费,数据的大小最坏的情况可能占有实际 数据量3倍(2个快照+1个数据库文件)。而且 Iter 也会有很大的读开销。

后面我们使用的是 RocksDB 提供的 CheckPoint 功能,也就是你说的硬链接的方式。这种方式的好处是 快照里面的数据文件 和此时数据库的数据文件 有相同 FileNumber 的是共享的。只有不同的文件(即 snapshot 之后新增/删除 也就是 由 Flush/Compaction 产生/删除的)会占用一定的磁盘空间, 但是这部分文件实际占有总数据量很少的一部分 (10%)左右,主要取决于你数据库的负载和 snapshot 的频率,已经是可以接受的范围。

谢大佬,我看有的实现是,在rocksdb 每次插入数据的时候,都吧 applay index 和数据 batch 写入了,不确定这个对性能影响有多大

@ehds
Copy link
Contributor

ehds commented Aug 2, 2023 via email

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants