pi_append_log 0.1.0

Storage-agnostic append-only block log traits, codec, layout, and file backend
Documentation
//! 与存储介质无关的追加日志接口和共享公共类型。

use std::io;

use crate::format::BlockDecoder;

/// 控制单次追加操作的选项。
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct AppendOptions {
    /// 是否要求成功返回前跨越实现定义的持久化屏障。
    ///
    /// 为 `true` 时,只有完整数据块及恢复该数据块所需的元数据均已跨越实现文档中
    /// 声明的持久化边界,`append` 才能返回 `Ok(_)`。文件存储通常需要同步相关数据
    /// 和元数据;远端存储则需要取得其耐久模型所定义的确认。此选项不承诺超过底层
    /// 介质或服务自身耐久模型的能力。
    ///
    /// 为 `false` 时,实现可以在完整数据块进入易失性缓冲后确认成功,但数据块的完整
    /// 可见性以及错误结果不确定的契约保持不变。
    pub durable: bool,
}

impl Default for AppendOptions {
    fn default() -> Self {
        Self { durable: true }
    }
}

/// Builder 初始化期间访问已提交历史数据块的顺序。
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ReadOrder {
    /// 从最旧的数据块到最新的数据块访问已提交历史。
    Forward,
    /// 从最新的数据块到最旧的数据块访问已提交历史。
    Backward,
}

/// 追加日志初始化期间接收历史数据块的访问器。
///
/// 历史读取仅允许发生在 `AppendLogBuilder::build` 中;运行期 `AppendLog` 不提供读取
/// 操作。
pub trait AppendLogVisitor {
    /// 访问一个已提交的历史数据块及其物理结构边界上下文。
    ///
    /// block 只在本次 visit 调用期间有效;Visitor 若要在返回后继续使用数据,必须自行复制。
    /// Builder 不会把整个历史恢复结果以 Vec<Vec<u8>> 的形式交给 Visitor。
    ///
    /// `context.is_first_in_structure` 表示当前 block 是所属物理结构的第一个 block;
    /// `context.is_last_in_structure` 表示当前 block 是所属物理结构的最后一个 block。
    ///
    /// 返回 `Ok(false)` 表示继续读取;返回 `Ok(true)` 表示提前停止初始历史读取,但
    /// Builder 仍须完成初始化并返回 storage;返回 `Err(_)` 表示中止 build 并将错误
    /// 返回给调用方。
    fn visit(&mut self, block: &[u8], context: BlockVisitContext) -> io::Result<bool>;
}

/// 描述历史数据块在物理结构边界上的访问上下文。
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct BlockVisitContext {
    /// 当前数据块是否是所属物理结构中的第一个数据块。
    ///
    /// 单 block 物理结构中,该字段与 `is_last_in_structure` 同时为 `true`。
    pub is_first_in_structure: bool,

    /// 当前数据块是否是所属物理结构中的最后一个数据块。
    ///
    /// 该字段描述 block 在物理结构中的事实位置,不随 Forward 或 Backward 读取顺序改变。
    pub is_last_in_structure: bool,
}

/// 追加日志物理结构的命名与发现策略。
///
/// 此 trait 只负责结构身份与活动、已封闭、归档资源名称之间的映射,以及从资源名称
/// 解析结构身份。它不负责列举资源、创建资源、写入数据、轮转或决定归档时机,因此
/// 文件、网络和对象存储后端都可以使用同一抽象并提供各自的名称类型与命名规则。
pub trait Layout: Send + Sync {
    /// 后端用于区分物理结构的稳定身份类型。
    type StructureId: Clone + Ord + Send + Sync + 'static;

    /// 后端资源名称类型,例如文件路径、对象键或远端流名称。
    type Name: Clone + Send + Sync + 'static;

    /// 返回指定身份的活动结构资源名称。
    fn active_name(&self, structure_id: &Self::StructureId) -> Self::Name;

    /// 返回指定身份的已封闭但尚未归档结构资源名称。
    fn closed_name(&self, structure_id: &Self::StructureId) -> Self::Name;

    /// 返回指定身份的归档结构资源名称。
    fn archive_name(&self, structure_id: &Self::StructureId) -> Self::Name;

    /// 尝试从活动结构资源名称解析稳定身份。
    ///
    /// 名称不属于当前布局,或名称格式不合法时返回 None。
    fn parse_active_name(&self, name: &Self::Name) -> Option<Self::StructureId>;

    /// 尝试从已封闭结构资源名称解析稳定身份。
    ///
    /// 名称不属于当前布局,或名称格式不合法时返回 None。
    fn parse_closed_name(&self, name: &Self::Name) -> Option<Self::StructureId>;

    /// 尝试从归档结构资源名称解析稳定身份。
    ///
    /// 名称不属于当前布局,或名称格式不合法时返回 None。
    fn parse_archive_name(&self, name: &Self::Name) -> Option<Self::StructureId>;
}

/// 已完成初始化的只追加数据块日志。
///
/// 实现可以安全地在线程间移动或共享;追加、轮转和归档的并发协调由实现内部负责。
/// 调用方不需要额外使用外部互斥锁才能获得基本并发安全。历史读取仅在 Builder 初始化阶段可用。
pub trait AppendLog: Send + Sync {
    /// 此追加日志接受,并在初始化期间交给访问器的完整数据块类型。
    type Block: AsRef<[u8]> + Clone + Send + Sync + 'static;

    /// 已通过 `rotate` 封闭、可在业务条件满足后交给 `archive` 的结构句柄。
    ///
    /// 上层只保存并回传该句柄,不应依赖其内部表示、文件名、路径或对象标识。句柄将
    /// 哪一个结构可以归档与何时归档分离,便于协调多个追加日志实例。
    type Closed: Clone + Send + Sync + 'static;

    /// 追加一个完整数据块。
    ///
    /// 返回 `Ok(active_size)` 表示整个数据块已按照 `options` 成功接受,返回值是
    /// 该次追加完成时接收此数据块的活动物理目标大小。追加操作本身绝不自动轮转,
    /// 上层根据该大小及自身策略决定是否以及何时调用 `rotate`。
    ///
    /// 该大小是追加在线性化完成时的瞬时值;返回后,并发追加或轮转可以立即改变当前
    /// 活动物理目标及其大小。
    ///
    /// 返回 `Err(_)` 时,上层不可确认该数据块是否已经提交,必须把结果视为不确定,
    /// 不能据此盲目重试。成功调用的持久化边界由 `AppendOptions::durable` 定义。
    async fn append(&self, block: Self::Block, options: AppendOptions) -> io::Result<u64>;

    /// 结束当前活动物理追加目标,并创建或选择新的活动目标。
    ///
    /// 当前活动结构非空时,返回的 `Some(Closed)` 句柄精确指向刚刚封闭的结构。
    /// 当前活动结构为空时,返回 `Ok(None)`,并保持当前空活动结构,避免产生空结构链。
    /// 此操作不归档已封闭结构;归档是独立的显式步骤。返回错误时,调用方不能假定轮转
    /// 一定已完成或一定未发生。
    async fn rotate(&self) -> io::Result<Option<Self::Closed>>;

    /// 归档指定的已封闭物理结构。
    ///
    /// 该结构必须由本实例的 `rotate` 返回,且不能是当前活动结构。归档时机由上层业务
    /// 决定,例如多个日志都完成索引落地、B+ 树根快照发布或 LSM manifest 提交之后。
    /// 返回错误时,调用方不能假定归档是否完成。
    async fn archive(&self, closed: Self::Closed) -> io::Result<()>;
}

/// 一次性 Builder 成功恢复后的结果。
pub struct BuildResult<S>
where
    S: AppendLog,
{
    /// 已完成恢复并可继续追加的存储实例。
    pub storage: S,

    /// 重启时发现的已封闭但尚未归档的结构句柄。
    ///
    /// 上层根据自身 checkpoint、快照或 manifest 状态决定何时逐个调用 archive。
    pub recovered_closed: Vec<S::Closed>,
}

/// 恢复历史并返回已初始化追加日志的一次性 Builder。
///
/// 消耗 `self` 可确保历史恢复只发生在初始化阶段。实现可直接使用原生异步 I/O,
/// 无需装箱 Future。
pub trait AppendLogBuilder: Send {
    /// 此 Builder 初始化后产生的追加日志存储类型。
    type Storage: AppendLog;

    /// 使用 `decoder` 按照 `order` 读取已提交历史、调用 `visitor`,并完成存储初始化。
    ///
    /// Builder 必须使用严格 decoder 校验所有被访问的完整 block。未归档已封闭结构参与
    /// 恢复;归档结构不参与正常恢复。活动结构仅在文件尾部存在连续半写或垃圾字节时,
    /// 才可以由 Builder 截断到最后一个完整、连续且校验通过的 block 边界。中间损坏或
    /// 已封闭结构的任何损坏都必须返回错误。截断及必要持久化完成后才能返回 storage。
    /// Visitor 返回 `Ok(true)` 时停止继续交付历史,但不会取消剩余初始化。成功结果包含
    /// storage 和重启时恢复的已封闭结构句柄。
    async fn build<D, V>(
        self,
        decoder: &D,
        order: ReadOrder,
        visitor: &mut V,
    ) -> io::Result<BuildResult<Self::Storage>>
    where
        D: BlockDecoder<Block = <Self::Storage as AppendLog>::Block> + Send + Sync,
        V: AppendLogVisitor + Send;
}