Python의 ossfs 라이브러리를 사용하여 파일에 데이터를 추가(append)하는 과정에서 오류가 발생했습니다. 이 문제는 ossfs가 파일을 덮어쓰려고 할 때 내부적으로 발생하는 동작 방식과 관련이 있습니다. 문제 해결 과정을 공유하여 비슷한 상황에 놓인 다른 사용자들에게 도움을 드리고자 합니다.
문제 상황 재현
오류 로그는 다음과 같습니다:
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 "*"))
오류 메시지 PositionNotEqualToLength는 OSS(Object Storage Service)에서 파일의 현재 길이와 요청된 쓰기 위치가 일치하지 않아 발생하는 409 Conflict 오류입니다. 이는 ossfs 라이브러리가 파일을 덮어쓸 때 (overwrite=True) 내부적으로 append_object 메서드를 호출하려다 발생하는 문제입니다. 만약 파일이 이미 존재하면, ossfs는 파일의 현재 크기를 기준으로 쓰기 위치를 지정하려 하는데, 이 위치가 실제 파일 크기와 다를 경우 위와 같은 오류가 발생합니다.
사용자의 코드에서 문제가 발생하는 부분은 다음과 같습니다:
# ... (이전 코드) ...
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)
# ... (이후 코드) ...
fs.write_text(..., overwrite=True)는 파일이 존재하면 append_object를 시도합니다. 만약 이전에 실행된 쓰기 작업이 완료되기 전에 다음 쓰기 작업이 시도되면, OSS가 인식하는 파일 크기와 ossfs가 기록한 크기 사이에 불일치가 발생하여 409 오류가 발생할 수 있습니다.
해결 방안
이 문제를 해결하기 위한 가장 확실한 방법은 append_object 동작을 피하는 것입니다. 즉, 파일을 추가하는 대신 완전히 새로 쓰는 방식을 사용하는 것입니다.
방법 1: 기존 파일 삭제 후 새로 작성
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가 기본값이므로 새로 작성됨
방법 2: overwrite=False 사용 (최신 버전 ossfs)
최신 버전의 ossfs 라이브러리를 사용한다면, overwrite=False를 명시하여 append_object 동작을 강제로 비활성화할 수 있습니다.
fs.write_text(args.task_status_path,
json.dumps(task_status, ensure_ascii=False),
encoding='utf-8',
overwrite=False) # append_object 동작 방지
추가 문제: fs.exists()의 부정확성
위의 해결 방법을 적용했음에도 불구하고 fs.exists()가 False를 반환하지만 실제로는 파일이 존재하고 쓰기 시 오류가 발생하는 경우가 있습니다. 이는 ossfs가 로컬 캐시에 파일 목록을 저장하고 있으며, 이 캐시가 실시간으로 갱신되지 않아 발생하는 현상입니다. 특히 분산 환경이나 OSS의 최종 일관성(eventual consistency) 모델 때문에 이런 문제가 두드러질 수 있습니다.
증상
- 첫 번째 루프:
write_text(..., overwrite=True)실행 시append_object시도, OSS에서 409 오류 발생. (이 오류가 외부try-except블록에 의해 처리되어 눈에 띄지 않을 수 있음) - 두 번째 루프:
fs.exists()는 로컬 캐시를 확인하므로 파일이 없다고 판단 (False 반환).write_text(..., overwrite=True)다시 실행 시append_object시도, 또 다시 409 오류 발생하며 최종적으로 예외가 외부로 던져짐.
검증 방법
오류 발생 지점에서 다음과 같은 로그를 추가하여 확인할 수 있습니다.
print("fs.exists() 캐시 결과:", fs.exists(args.task_status_path))
print("실제 listdir 결과:", fs.listdir(os.path.dirname(args.task_status_path)))
fs.exists()는 False를 반환하지만, fs.listdir() 결과에는 해당 파일이 존재하는 것을 확인할 수 있습니다.
근본적인 해결책
- 캐시 강제 갱신:
ossfs의invalidate_cache()메서드를 사용하여 캐시를 강제로 갱신합니다. append_object동작 회피: 방법 1 (삭제 후 재작성) 또는 방법 2 (`overwrite=False`)를 사용합니다.overwrite=True사용 시 주의: 최신 버전의ossfs는overwrite=True시 기본적으로PutObject를 사용하지만, 여전히 캐시 문제의 영향을 받을 수 있습니다.
fs.invalidate_cache(args.task_status_path) # 특정 파일 또는 디렉토리의 캐시 갱신
# 또는 fs.invalidate_cache() # 전체 캐시 갱신
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')
요약
fs.exists()가False를 반환하는 것은 로컬 캐시를 읽기 때문이며, 실제 OSS에는 파일이 존재할 수 있습니다.- 409
PositionNotEqualToLength오류는ossfs의overwrite=True옵션이 내부적으로append_object를 시도할 때 파일 크기 불일치로 인해 발생합니다. - 해결책은
append_object동작을 피하는 것입니다. 즉,invalidate_cache()를 사용하거나, 파일을 먼저 삭제하고 새로 작성하는 방식을 사용해야 합니다.