数据存储后端【免费下载链接】opendalApache OpenDAL: One Layer, All Storage.项目地址https://gitcode.com/gh_mirrors/op/opendal点击查看免费下载本文基于 Apache OpenDAL 的核心 RFC 提案 7818_rename_if_not_exists.md 撰写。OpenDAL 将rename定义为覆盖语义的操作而很多应用还需要一个原子发布原语仅当目标路径尚不存在时才把 staging 文件移动到正式位置。读完后你将掌握rename_with(...).if_not_exists(true)/rename_options的完整用法、ConditionNotMatch与Unsupported的错误契约、Capability::rename_with_if_not_exists的能力探测方式以及各后端服务HDFS、FS、WebDAV、Azure 等对该条件的支持现状与实现要点。背景与动机stat rename 之间的竞态窗口OpenDAL 中rename的默认语义是覆盖overwrite如果目标文件已存在它会被替换。但原子发布场景需要的是另一种语义——把完成的 staging 文件放入正式位置前提是没有其他写者已经发布了该目标。如果按直觉用statrename两步实现stat_result stat(destination) if !stat_result { rename(source, destination) }就会落入经典的 time-of-check to time-of-useTOCTOU竞态stat判断目标不存在之后、rename执行之前另一个写者可能已经创建了目标文件最终导致覆盖。另一个思路是在服务构造时加一个配置开关来切换行为但这也并不合适——它会让同一个rename调用的含义取决于后端如何构造而不是调用处的显式条件。因此该提案选择与write和copy相同的 options 建模方式op.write_with(path, content) .if_not_exists(true) .await?; op.copy_with(source, target) .if_not_exists(true) .await?;rename遵循同样的公共 API 与错误模型新增rename_with(...).if_not_exists(true)。指南级 API现有行为与新 API 并存现有行为当需要在目标文件存在时覆盖它继续使用renameuse opendal::{Operator, Result}; async fn replace(op: Operator) - Result() { op.rename(staging/file, published/file).await?; Ok(()) }新 API当需要在目标文件存在时保护它不被覆盖使用rename_with(...).if_not_exists(true)use opendal::{ErrorKind, Operator, Result}; async fn publish(op: Operator) - Result() { match op .rename_with(staging/file, published/file) .if_not_exists(true) .await { Ok(()) Ok(()), Err(err) if err.kind() ErrorKind::ConditionNotMatch Err(err), Err(err) Err(err), } }条件操作的完整结果集目标不存在source 被重命名到目标目标已存在返回ConditionNotMatch两个路径都保持不变服务无法原子地强制目标条件返回Unsupportedsource 与目标为同一路径返回IsSameFile与常规 rename 一致。用户可以在启用该选项前检查Capability::rename_with_if_not_exists。阻塞式用户通过RenameOptions配置同样的条件use opendal::blocking; use opendal::options::RenameOptions; use opendal::Result; fn publish(op: blocking::Operator) - Result() { let mut options RenameOptions::default(); options.if_not_exists true; op.rename_options(staging/file, published/file, options)?; Ok(()) }公共 API 定义新增的选项结构体#[derive(Debug, Clone, Default, Eq, PartialEq)] pub struct RenameOptions { pub if_not_exists: bool, }异步Operator暴露三个方法当前仓库中已落地于 operator.rsimpl Operator { pub async fn rename(self, from: str, to: str) - Result(); pub fn rename_with( self, from: str, to: str, ) - FutureRenameimpl FutureOutput Result(); pub async fn rename_options( self, from: str, to: str, options: impl IntoRenameOptions, ) - Result(); }rename只是以默认选项委托给rename_options见 operator.rs#L1580-L1583直接调用rename_options(from, to, options::RenameOptions::default())。FutureRename提供链式配置方法implF: FutureOutput Result() FutureRenameF { pub fn if_not_exists(self, value: bool) - Self; }其实现位于 operator_futures.rs#L1878-L1905内部是OperatorFuture(options::RenameOptions, String), (), Fif_not_exists方法只是修改未来参数中的RenameOptions。阻塞式Operator沿用既有的 blocking options 风格见 blocking/operator.rs#L626-L655impl blocking::Operator { pub fn rename_options( self, from: str, to: str, options: RenameOptions, ) - Result(); }提案明确不新增独立的rename_if_not_exists方法条件选项放在RenameOptions中与 write/copy 的 API 形态一致也为未来可组合的重命名条件留出空间。与 copy API 的分层对齐rename API 在每一层都镜像 copy API层CopyRename默认操作copyrename流式选项copy_with(...).if_not_exists(true)rename_with(...).if_not_exists(true)选项结构体CopyOptionsRenameOptions显式选项调用copy_optionsrename_options原始参数OpCopy::if_not_exists()OpRename::if_not_exists()能力位copy_with_if_not_existsrename_with_if_not_exists同样的理由也不新增copy_if_not_exists这类独立方法——copy 本身就通过 options API 暴露条件。服务层 APIOpRename、Capability 与正确性检查RenameOptions会转换为服务层的原始参数OpRename当前实现在 raw/ops.rs#L1409-L1458pub struct OpRename { if_not_exists: bool, }Service::rename的签名保持不变服务通过检查OpRename::if_not_exists()来选择原生的 overwrite 或 no-overwrite 操作。OpRename还提供is_conditional()便捷判断且实现了Fromoptions::RenameOptions for OpRename的自动转换。能力位新增capability.rs#L230-L233pub struct Capability { pub rename: bool, pub rename_with_if_not_exists: bool, }rename_with_if_not_exists只有在rename同样受支持时才有意义。当选项被启用但服务未声明该能力时正确性检查层会在分发之前返回Unsupported——当前实现见 layers/correctness_check.rs#L434-L452if args.if_not_exists() !capability.rename_with_if_not_exists { return Err(new_unsupported_error( scheme, Operation::Rename, if_not_exists, )); }这与 write/copy 的既有模式一致RenameOptions永远可以构造由运行时检查强制底层服务的支持情况。错误语义if_not_exists是一个目标前置条件因此目标冲突统一映射为ConditionNotMatch与既有 write 和 copy 行为一致。两个要点由原生 no-replace 条件导致的后端错误必须被翻译为ConditionNotMatch即使后端或操作系统报告的是AlreadyExists这类原生错误其他错误保留原有含义source 缺失返回NotFound目录被当作文件传入选型时返回相应目录错误不支持的条件返回Unsupported。RenameOptions的文档注释types/options.rs#L1045-L1066还明确了条件只检查目标文件、从不检查 sourcesource 缺失会以NotFound失败。原子性要求能力声明的门槛服务只有在目标不存在检查与重命名本身由同一个原生操作完成时才允许声明rename_with_if_not_exists。先做一次独立的 stat 再做覆盖式 rename 会留下 TOCTOU 竞态不满足该能力。条件失败目标已存在时的路径状态契约source 保留在原始路径目标保持不变。rename 成功时source 不再存在于原始路径目标包含 source 的数据。RFC 不要求 rename 在崩溃语义上原子超出底层服务自身保证的部分只要求 no-replace 决策相对并发的目标创建是原子的。同时RFC 也不要求覆盖式rename必须原子某个服务可以把覆盖式 rename 实现为 delete-then-rename 多步流程依然可以声明rename: true——因为覆盖没有对外部条件需要强制。原子性门槛只针对rename_with_if_not_exists。一个重要细节即使覆盖式 rename 是 delete-then-rename 实现的只要原生 move 能原子地拒绝已存在目标该后端仍可能支持rename_with_if_not_exists——它只需在if_not_exists置位时跳过预删除步骤。服务审计哪些后端能支持 if_not_existsRFC 对当时声明了Capability::rename的 19 个服务逐一审计并以 S3 作为没有原生 rename 的对象存储代表。注意评估列只是描述当前后端原语能否支持该条件并不启用能力——后端还必须证明目标检查与 move 是同一个原子服务操作且能把目标冲突映射为ConditionNotMatch。服务当前 rename 实现if_not_exists评估aliyun_drive删除目标、按文件 ID 移动、再更新名称按现状不适合覆盖是多请求工作流azdls发送一个 Data Lake Storage rename 请求强候选API 支持对目标设置If-None-Match: *azfile发送一个 Azure Files rename 请求带x-ms-file-rename-replace-if-exists: true强候选把原生 replace 头设为falsecompfs调用compio::fs::rename与fs相同的可移植性限制dbfs发送一个 DBFS move 请求候选但原子性须验证DBFS 拒绝已存在目标dropbox发送一个files/move_v2请求候选但目标冲突与原子性保证须验证fs调用tokio::fs::rename当前实现没有可移植的 no-replace 原语gdrive回收站目标、再 patch source 元数据不适合覆盖是多请求工作流且 Drive 允许重名goosefsstat 并删除目标再调用原生 rename RPC若原生冲突是原子的则候选当前覆盖路径是多步hdfs_native修改目标后以覆盖标志调用 client rename API强候选走原生overwrite false路径去掉目标预处理hdfs常规 rename 先删目标再调用原生 no-overwrite rename由该 RFC 的初始实现支持koofr移除目标后发送一个 move 请求仅当 move 端点原子拒绝冲突时是候选当前覆盖是多步monoiofs调用monoio::fs::rename与fs相同的可移植性限制onedrive发送 conflict behavior 为replace的 PATCH move 请求候选可用 conflict behaviorfail取决于 OneDrive 变体兼容性pcloud发送一个renamefile或renamefolder请求需要服务专属的冲突与原子性验证sftp调用 SFTP client 的 rename 操作需要协议/服务器专属验证覆盖行为在服务器间不可移植upyun使用x-upyun-move-source发送一个 move 请求需要服务专属的冲突与原子性验证webdav发送一个带Overwrite: T的MOVE请求强候选RFC 4918 定义了Overwrite: F与失败前置条件yandex_disk发送一个带overwritetrue的 move 请求候选可用overwritefalse取决于原子性与错误映射验证s3不声明 renamecopy 与 delete 分别支持不适合条件 copy 加 delete 不是原子 rename这次审计还揭示多个现有后端通过移动前删除目标来实现 OpenDAL 的覆盖契约。该行为天然是多步的但并不妨碍同一后端在原生 move 能原子拒绝已存在目标时支持条件 rename。条件能力必须基于原生操作而不能基于 stat 或 delete 预检。HDFS初始实现的参考样例HDFS 通过Options.Rename暴露 rename 选项libhdfs 的hdfsRename使用Options.Rename.NONE会拒绝已存在目标。因此 HDFS 服务可以直接声明rename_with_if_not_exists并调用该原生操作。当前仓库中这已经落地能力声明services/hdfs/src/backend.rs#L174-L175 中rename: true, rename_with_if_not_exists: true条件错误翻译services/hdfs/src/core.rs#L25-L36 的map_hdfs_rename_error在if_not_exists置位且原生错误为AlreadyExists时返回ConditionNotMatch否则按普通 IO 错误处理目标预处理分支services/hdfs/src/core.rs#L164-L203 中hdfs_rename先 stat 目标——若目标是已存在文件且if_not_exists为真则直接返回ConditionNotMatch快速路径若为假才删除目标以满足覆盖契约最后的原生rename_file冲突仍经map_hdfs_rename_error兜底保证最终原生操作具有权威性并发创建者不会被覆盖单元测试core.rs#L259-L273 断言AlreadyExists被映射为ConditionNotMatch。FS / compfs / monoiofs可移植性约束FS 服务当前使用tokio::fs::rename不同操作系统上默认 rename 的行为与目标替换语义不同且该 API 没有可移植的 no-replace 选项。因此 FS 服务不得用metadata-then-rename实现来声明rename_with_if_not_exists。未来实现可以基于 Linuxrenameat2(RENAME_NOREPLACE)等原生原语并定义清晰的跨平台策略。compfs与monoiofs通过不同运行时封装同一操作系统 rename 模型受相同约束SFTP 同样需要谨慎因为实际 rename 行为取决于协议操作与服务器实现。原生条件 move 服务若干服务在同一个服务端 move 请求中暴露目标策略它们是后续实现的候选本 RFC 的初始 HDFS 变更并不启用它们每个后续实现都须包含行为覆盖与服务专属错误翻译Azure Data Lake Storagerename 接受If-None-Match: *Azure Files暴露x-ms-file-rename-replace-if-existsWebDAVMOVE的Overwrite头F要求服务器拒绝已存在目标RFC 4918Yandex Diskoverwrite查询参数OneDriveconflict behavior但不同服务变体支持度不同须先验证。S3 等无 rename 的对象存储S3 没有原生 rename也不声明rename。条件 copy delete不是原子 rename理由有三copy 成功后 delete source 可能失败两个操作之间其他客户端可能同时观察到 source 和 destination部分成功后重试的语义与原生 rename 不同。因此copy_with(...).if_not_exists(true) delete 不能被用来声明任何 rename 能力。采用相同操作模型的其他对象存储包括rename只是不支持占位符的服务遵循同一规则。仓库中的实现证据与行为测试行为测试框架按能力门控条件用例tests/behavior/async_rename.rs#L22-L45 中基础 rename 用例test_rename_file、test_rename_overwrite、test_rename_self等在cap.rename为真时运行而test_rename_with_if_not_exists、test_rename_with_if_not_exists_nested、test_rename_with_if_not_exists_returns_condition_not_match三个条件用例额外要求cap.rename_with_if_not_exists。这印证了先查能力、再执行条件操作的调用模式。goosefs 是第二个已声明该能力的服务services/goosefs/src/backend.rs#L404 声明rename_with_if_not_exists: true其rename实现core.rs在if_not_exists置位时不删除目标、把原生AlreadyExists错误翻译为ConditionNotMatch且注释明确该 rename 是write_with_if_not_exists发布路径的权威性步骤destination is never deleted on the Create path。services/goosefs/src/docs.md 也把rename (if_not_exists)列为已覆盖能力。正确性层单测layers/correctness_check.rs#L1241-L1255 验证了能力缺失时rename_with(...).if_not_exists(true)返回错误、能力启用后通过的对照行为。权衡、备选方案与开放问题缺点Drawbacks公共 options 与 capability 面新增了字段和FutureRename别名初始只有部分支持 rename 的服务支持该选项跨任意服务调用的调用方必须检查能力或处理Unsupported通用错误类型会掩盖后端专属的冲突错误——这对可移植的条件操作处理是有意为之的但确实从主错误类型中移除了一些原生细节。被否决的备选方案独立rename_if_not_exists方法容易发现但与 write/copy 的 options API 形态分道扬镳改变默认 rename 行为为拒绝已存在目标会破坏既有覆盖契约与依赖替换语义的调用方HDFS 配置开关让标准操作的含义取决于服务构造方式而目标条件是每次调用的需求应当在调用处显式表达stat-then-rename 模拟无法在并发目标创建下强制条件不得暴露为原生能力条件 copy-then-delete 模拟可用于应用专属工作流但不是原子 rename 且存在部分成功状态OpenDAL 不应在相同契约下暴露它。先例Prior artOpenDAL 已为 write 和 copy 提供if_not_exists选项用ConditionNotMatch表达失败的目标条件HDFS 提供Options.Rename.NONE与Options.Rename.OVERWRITELinux 提供带RENAME_NOREPLACE的renameat2Rust 的object_storecrate 把条件 rename 作为独立方法暴露对比见 docs/comparisons/vs_object_store.mdOpenDAL 采用相同的语义能力但保留既有的 options API。未解决问题还有哪些文件系统平台拥有 OpenDAL 可以在不削弱契约的前提下支持的合适原生 no-replace 原语OpenDAL 是否最终为对象存储提供一个独立的、明确非原子的 copy-then-delete move 助手这两个问题都不阻塞 HDFS 实现与通用 API。后续可能性与调用方实践建议RenameOptions可以在服务暴露可移植原生支持时继续增加重命名条件FS 服务可以在存在原生实现的平台上声明rename_with_if_not_exists前提是项目定义好期望的跨平台能力行为接受非原子 move 语义的应用可以自行用 copy delete 组合助手而不改变 rename 的保证。结合以上契约一个稳健的调用方实践是let cap op.info().capability(); if !cap.rename_with_if_not_exists { // 该服务不能原子强制目标条件选择降级策略或提前报错 // 注意直接调用 rename_with(...).if_not_exists(true) 会得到 Unsupported } match op.rename_with(staging/file, published/file).if_not_exists(true).await { Ok(()) {} Err(err) if err.kind() ErrorKind::ConditionNotMatch { /* 目标已被其他写者发布 */ } Err(err) if err.kind() ErrorKind::Unsupported { /* 服务未声明能力正确性层已拦截 */ } Err(err) if err.kind() ErrorKind::NotFound { /* staging 文件缺失 */ } Err(err) if err.kind() ErrorKind::IsSameFile { /* from to */ } Err(err) return Err(err), }小结rename_if_not_exists提案为 OpenDAL 补齐了原子发布这一常见分布式写入模式rename保持覆盖语义rename_with(...).if_not_exists(true)/rename_options提供目标不存在的前置条件ConditionNotMatch/Unsupported/IsSameFile/NotFound构成清晰的可判定错误集Capability::rename_with_if_not_exists是能力探测与正确性层双重拦截的开关。核心设计纪律是该能力只能基于原子的原生 no-replace 操作声明stat/delete 预检一律不算数仓库中 HDFS 与 goosefs 已率先落地行为测试按能力门控运行WebDAV、Azure 系等强候选则等待各自的验证与实现。赞分享数据存储后端【免费下载链接】opendalApache OpenDAL: One Layer, All Storage.项目地址https://gitcode.com/gh_mirrors/op/opendal点击查看免费下载相关推荐crush 的 LSP 语义重命名工具 lsp_rename跨文件安全重命名符号的原理与实战crush 的 LSP 语义重命名工具 lsp_rename 跨文件安全重命名符号的原理与实战 在 crushcharmbracelet 推出的 GlamoAI 应用代码智能体交互助手CLIMCP Clients人工智能DiceDB RENAME 命令完全指南安全重命名键、原子覆盖语义与底层实现剖析DiceDB RENAME 命令完全指南安全重命名键、原子覆盖语义与底层实现剖析 RENAME 是 DiceDB 中用于修改既有键名的核心命令在不删除、不重数据库缓存后端xiaozhi-esp32 快速上手给 ESP32 接上麦克风4 步做出你的 AI 语音助手xiaozhi esp32 快速上手给 ESP32 接上麦克风4 步做出你的 AI 语音助手 xiaozhi esp32 是一个开源的 ESP32 AI 语数据存储后端上一篇modern-python 技能中的 PEP 723 内联脚本元数据实战指南下一篇RocksDB db_stress 崩溃恢复验证Expected-State Trace 前缀恢复机制深度解析创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
阅读完成 · 觉得有帮助?