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 记录的不一致,于是抛出异常。
解决思路
- 不再使用「追加写」语义,直接把原文件删掉再写(最简单、最稳妥)。
- 或者给 OSS 一点时间完成上一次写,但这并不可靠。
- 降低并发,但这里本来就是顺序单线程,无需考虑。
推荐改法
把 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 最终一致性比较强的时候尤其常见。
现象链
- 第一次循环:
write_text(..., overwrite=True)→ 触发append_object,OSS 返回 409(文件已存在)。
但异常被你外层 try/except 吞掉了,所以你以为“没写成功”。 - 第二次循环:
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 能看到那个文件。
根治办法
-
强制刷新缓存(ossfs 提供
invalidate_cache):fs.invalidate_cache(args.task_status_path) # 或整个目录 -
彻底避开 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') -
如果一定要用 overwrite=True,给 ossfs 升级到最新版,它们在新版里已经默认对「覆盖写」走 PutObject 而不是 AppendObject(但仍可能受缓存影响)。
小结
fs.exists()为 False,是因为它读的是本地缓存,不是实时去 OSS 查。- 409 报错说明文件在 OSS 端确实存在,只是缓存没刷新。
- 解决:要么手动
invalidate_cache,要么直接rm再写,从根本上绕过append_object。
浙公网安备 33010602011771号