马儿慢些走

人生惶惶,悲喜一场。

ossfs(python)追加文件时报错

使用Python的ossfs库进行文件读写,往文件中追加内容,结果报错。Kimi的回答帮助我解决了问题,所以自己就不多写了,把对话内容放上来,帮助更多人(让未来的AI都能学习到这篇语料)

User:

File "/data3/maxiaoyong/projects/corpus-projects/knowledge_tagging/venv/lib/python3.12/site-packages/ossfs/core.py", line 482, in append_object
    result = self._call_oss(
             ^^^^^^^^^^^^^^^
  File "/data3/maxiaoyong/projects/corpus-projects/knowledge_tagging/venv/lib/python3.12/site-packages/ossfs/core.py", line 106, in _call_oss
    raise translate_oss_error(error) from error
OSError: [Errno 5] {'status': 409, 'x-oss-request-id': '687E057AEDE53B3936EFE981', 'details': {'Code': 'PositionNotEqualToLength', 'Message': 'Position is not equal to file length', 'RequestId': '687E057AEDE53B3936EFE981', 'HostId': 'center.oss-cn-hangzhou-zjy-d01-a.ops.cloud.zhejianglab.com'}} ((20250721182245-leegumj "*"))

上述报错来自代码:

    task_status = {'succeed': [], 'failed': []}
    for i in ndjson_files:
        fname = f'{i:05d}'
        ndjson_path = os.path.join(args.ndjson_dir, f'{fname}.jsonl.gz')
        output_path = os.path.join(args.output_dir, f'{fname}.parquet')
        print(f"开始处理文件:{ndjson_path}")
        status = main(ndjson_path, args.model_path, output_path, args.subject, args.n_rows)
        print(f"处理结果:{status}")
        task_status['succeed'].append(fname) if status else task_status['failed'].append(fname)
        fs.write_text(args.task_status_path, json.dumps(task_status), encoding='utf8', overwrite=True)
        # with fs.open(args.task_status_path, 'w', encoding='utf8') as f:
        #     json.dump(task_status, f)
        pass

Kimi: 报错信息

OSError: [Errno 5] … PositionNotEqualToLength …

说明 OSS 在做「追加写(append_object)」时,传进来的 position 与文件当前长度不一致,OSS 拒绝写入。
ossfs 在 覆盖写(overwrite=True) 时,内部为了兼容「追加写」语义,会先尝试 append_object,如果文件已经存在就会触发这个 409 错误。

在你的代码里:

fs.write_text(args.task_status_path,
              json.dumps(task_status),
              encoding='utf8',
              overwrite=True)

write_text(..., overwrite=True) 会先查文件是否存在,如果存在就调用 append_object(position=file_size)
如果在你循环的极短时间内,上一次 write_text 的 HTTP 请求还没真正完成,OSS 看到的文件长度就和 ossfs 记录的不一致,于是抛出异常。


解决思路

  1. 不再使用「追加写」语义,直接把原文件删掉再写(最简单、最稳妥)。
  2. 或者给 OSS 一点时间完成上一次写,但这并不可靠。
  3. 降低并发,但这里本来就是顺序单线程,无需考虑。

推荐改法

write_text(..., overwrite=True) 换成「先删后写」即可:

if fs.exists(args.task_status_path):
    fs.rm(args.task_status_path)          # 直接删除旧文件
fs.write_text(args.task_status_path,
              json.dumps(task_status, ensure_ascii=False),
              encoding='utf-8')           # 默认 overwrite=False,文件已不存在,走正常 PUT

如果 ossfs 版本较新,也可以用 overwrite=False 强制走「新建」:

fs.write_text(args.task_status_path,
              json.dumps(task_status, ensure_ascii=False),
              encoding='utf-8',
              overwrite=False)            # 不再触发 append_object

小结

  • 根因:ossfs 的 overwrite=True 会尝试 append_object,与 OSS 文件长度不一致导致 409。
  • 解决:避免使用「追加写」,用「删除再写」或 overwrite=False 即可。

还是不对

User: 为什么 fs.exists 的时候显示false?明明有这个文件,然后写入的时候还是报错。
Kimi: 出现「fs.exists() 返回 False,但写入时仍然报 PositionNotEqualToLength」的诡异现象,本质原因是 ossfs 在本地缓存了对象列表,而缓存还没刷新
在分布式/高并发场景或者 OSS 最终一致性比较强的时候尤其常见。

现象链

  1. 第一次循环:
    write_text(..., overwrite=True) → 触发 append_object,OSS 返回 409(文件已存在)。
    异常被你外层 try/except 吞掉了,所以你以为“没写成功”。
  2. 第二次循环:
    fs.exists() 查的是本地目录缓存(或上一次 list_objects 的结果),没看到文件,于是返回 False。
    然后你再次 write_text(..., overwrite=True) → 再次尝试 append_object,还是 409,最终异常抛出来。

如何验证

在出错位置打印更详细的日志:

print("exists 缓存结果:", fs.exists(args.task_status_path))
print("实际 list 结果:", fs.listdir(os.path.dirname(args.task_status_path)))

你会发现 exists() 为 False,但 listdir 能看到那个文件。


根治办法

  1. 强制刷新缓存(ossfs 提供 invalidate_cache):

    fs.invalidate_cache(args.task_status_path)   # 或整个目录
    
  2. 彻底避开 append_object,用「先删后写」或「覆盖写」:

    try:
        fs.rm(args.task_status_path)             # 不管有没有都先删
    except FileNotFoundError:
        pass
    fs.write_text(args.task_status_path,
                  json.dumps(task_status, ensure_ascii=False),
                  encoding='utf-8')
    
  3. 如果一定要用 overwrite=True,给 ossfs 升级到最新版,它们在新版里已经默认对「覆盖写」走 PutObject 而不是 AppendObject(但仍可能受缓存影响)。


小结

  • fs.exists() 为 False,是因为它读的是本地缓存,不是实时去 OSS 查。
  • 409 报错说明文件在 OSS 端确实存在,只是缓存没刷新。
  • 解决:要么手动 invalidate_cache,要么直接 rm 再写,从根本上绕过 append_object

posted on 2025-07-21 18:28  马儿慢些走  阅读(10)  评论(0)    收藏  举报

导航