Skip to the content.

低水位标记(Low-Water Mark)

一个 write-ahead log 的索引,它代表的是日志的哪一部分能丢弃

问题

write ahead log 每天更新维护到持久化存储。他可以随着时间无线增长。分段日志可以一次处理很小的文件,但是如果不检查的话,总磁盘内存也会无限增长。

解决方案

有一个机制告诉日志哪一部分可以安全的丢弃掉。这个机制给定一个最低偏移量或低水位标记,它指出了哪里可以丢弃。在后台作业有一个任务运行,在单独的线程里,它会一直检查日志可以被丢弃的部分以及根据条件在磁盘删除它。

this.logCleaner = newLogCleaner(config);
this.logCleaner.startup();

日志清理器可以作为一个调度任务实现

public void startup() {
    scheduleLogCleaning();
}

private void scheduleLogCleaning() {
    singleThreadedExecutor.schedule(() -> {
        cleanLogs();
    }, config.getCleanTaskIntervalMs(), TimeUnit.MILLISECONDS);
}

基于快照的 Low-Water Mark

像 Zookeeper 或 etcd(定义在 RAFT) 这种大多数一致性实现中,实现了快照机制。在这个实现里,存储引擎会定期的获取快照。除了快照,它还会存储它成功应用的日志索引。参照(referring to)Write-Ahead Log 模式中的简单的键值存储实现中,就像下面:

public SnapShot takeSnapshot() {
    Long snapShotTakenAtLogIndex = wal.getLastLogEntryId();
    return new SnapShot(serializeState(kv), snapShotTakenAtLogIndex);
}

一旦快照成功的持久化到了磁盘,日志管理器就会给定一个 low water mark 来表明丢弃的旧日志。

List<WALSegment> getSegmentsBefore(Long snapshotIndex) {
    List<WALSegment> markedForDeletion = new ArrayList<>();
    List<WALSegment> sortedSavedSegments = wal.sortedSavedSegments;
    for (WALSegment sortedSavedSegment : sortedSavedSegments) {
        if (sortedSavedSegment.getLastLogEntryId() < snapshotIndex) {
            markedForDeletion.add(sortedSavedSegment);
        }
    }
    return markedForDeletion;
}

基于时间的 Low-Water Mark

在有些系统中,使用日志来记录更新系统的状态是不必要的,日志可以在给定一个时间窗口后丢弃掉,不需要等待其它的子系统共享它能删除的最低日志索引。例如,在像 Kafka 的系统中,日志主要维护 7 周;所有的日志一旦超过了 7 周,就会被丢弃。对于这种实现,每个日志条目都包含了创建日志时的时间戳。日志清理器就能检查每个日志分段的最后日志条目,并把那些超过(老)配置的时间窗口的日志片段丢弃掉。

private List<WALSegment> getSegmentsPast(Long logMaxDurationMs) {
    long now = System.currentTimeMillis();
    List<WALSegment> markedForDeletion = new ArrayList<>();
    List<WALSegment> sortedSavedSegments = wal.sortedSavedSegments;
    for (WALSegment sortedSavedSegment : sortedSavedSegments) {
        if (timeElaspedSince(now, sortedSavedSegment.getLastLogEntryTimestamp()) > logMaxDurationMs) {
            markedForDeletion.add(sortedSavedSegment);
        }
    }
    return markedForDeletion;
}

private long timeElaspedSince(long now, long lastLogEntryTimestamp) {
    return now - lastLogEntryTimestamp;
}

例子