← 中文资源

简体中文版本

预写式日志

预写式日志在改动数据文件前先记录变更,提供持久性与崩溃恢复,并支撑复制。

阅读英文原文

概念定义

预写式日志(WAL)是数据库意图所做变更的仅追加记录,在修改对应数据页之前先写入并刷入持久存储。崩溃时,通过重放日志恢复已提交事务并回滚未完成的事务。这条有序流同时支撑复制与变更数据捕获,因为下游系统可以应用日志来重建状态。

为什么重要

这是数据库在不把每次变更直接写到散落磁盘页上的情况下承诺持久性的方式。它还统一了三种需求:崩溃恢复、复制与变更数据捕获。副本应用日志流以保持一致;下游系统消费同一流来构建搜索索引、缓存或数仓,而无需轮询表。一份有序记录同时服务三者。

工程实践

尊重 fsync 边界:要有意识地去调你的持久性设置,因为关闭刷盘是用已提交数据换取速度。保持检查点与归档健康,使日志不会无界增长或撑满磁盘。对日志使用逻辑复制或变更数据捕获工具,而不是解析物理文件,以免被内部格式绑定。监控复制延迟,因为日志流延迟增长对副本与消费者都是早期警讯。

代码示例

import os
import tempfile
from pathlib import Path
from typing import Union


class AtomicWriteError(RuntimeError):
    """Raised when the atomic write could not be completed."""


def atomic_write(path: Union[str, Path], payload: Union[bytes, str], mode: int = 0o644) -> None:
    """Write payload to path atomically via same-directory temp file and rename."""
    dest = Path(path)
    parent = dest.parent
    if not parent.is_dir():
        raise AtomicWriteError(f"destination directory does not exist: {parent}")
    data = payload.encode("utf-8") if isinstance(payload, str) else payload
    fd, tmp_name = tempfile.mkstemp(dir=str(parent), prefix=f".{dest.name}.", suffix=".tmp")
    tmp = Path(tmp_name)
    try:
        with os.fdopen(fd, "wb") as handle:
            handle.write(data)
            handle.flush()
            os.fsync(handle.fileno())
        os.chmod(tmp, mode)
        os.replace(tmp, dest)
    except OSError as exc:
        tmp.unlink(missing_ok=True)
        raise AtomicWriteError(f"atomic write failed for {dest}: {exc}") from exc