2P by GN⁺ | ★ favorite | 댓글 1개
  • Amazon S3는 2006년에 등장한 초기 클라우드 기술로 파일 저장에는 강하지만, Unix 파일 API를 그대로 대체하는 파일 시스템은 아님
  • Unix 파일 API는 open, read, write, seek, close 같은 좁은 인터페이스 뒤에 버퍼링, 페이지 캐시, 권한, IO 스케줄링을 감춘 깊은 모듈에 가까움
  • S3는 GetObjectPutObject 중심으로 단순해 보이지만, Range를 통한 부분 읽기만 가능하고 부분 덮어쓰기는 지원하지 않음
  • Postgres, SQLite, MySQL, MongoDB, Elasticsearch 같은 데이터베이스는 페이지 단위 덮어쓰기에 의존하므로, SQLite나 DuckDB 파일을 S3에 그대로 올리는 방식은 작은 데이터셋 외에는 맞기 어려움
  • 높은 읽기·쓰기 대역폭과 낮은 운영 부담은 S3의 강점이지만, rename/move 부재, 느린 목록 조회, XML 전용 API, 로컬 테스트 환경 부재 같은 제약을 함께 고려해야 함

S3는 파일을 저장하지만 파일 시스템은 아님

  • S3는 2006년에 등장한 초기 클라우드 기술이며, 당시 유행하던 표현에 따라 “object store”로 불렸음
  • 실제로는 파일 저장소로 널리 쓰이지만, 이를 “Amazon Cloud Filesystem”처럼 이해하면 일부만 맞음
  • 파일 저장은 잘하지만, 기존 파일 시스템의 동작과 기대를 그대로 대신하지는 못함

Unix 파일 API와 깊은 모듈

  • Unix 파일 API의 핵심은 다음 다섯 가지 호출로 요약됨
    • open(filepath): 파일을 엶
    • file.read(size=100): 현재 위치에서 읽고 위치를 앞으로 이동함
    • file.write("hello, world"): 현재 위치에 쓰고 위치를 앞으로 이동함
    • file.seek(94): 위치를 특정 바이트로 이동함
    • file.close(): 파일을 닫음
  • 이 호출들은 실제 시스템 호출 전체 중 핵심에 해당하며, 파일 읽기와 쓰기에 필요한 최소 기능에 가까움
  • 좁은 인터페이스 뒤에서 많은 처리가 이뤄지기 때문에 Unix 파일 API는 깊은 모듈(deep module) 로 볼 수 있음
    • 버퍼링과 페이지 캐시
    • 단편화 처리
    • 권한 관리
    • IO 스케줄링
    • SD 카드의 wear-levelling 같은 기능도 사용자가 직접 신경 쓰지 않아도 혜택을 얻음

얕은 모듈과 YAML, ORM

  • 얕은 모듈은 처리해 주는 기능에 비해 API 표면이 상대적으로 큼
  • 오늘날 얕은 모듈을 알아보는 단서 중 하나는 인터페이스가 YAML인 경우임
    • YAML은 마크업 언어처럼 보이지만, 실제로는 거의 어떤 의미론도 얹을 수 있는 재사용 문법처럼 쓰임
    • DevOps 영역에서는 YAML이 “프로그래밍 언어”처럼 동작하는 경우가 많음
    • YAML 미니 언어가 반복 구조를 제공하면 튜링 완전할 가능성이 있음
  • 얕은 모듈이 항상 나쁜 것은 아님
    • SQL ORM은 본질적으로 새는 추상화이며 SQL 이해 없이 쓰기 어려움
    • 어떤 경우에는 얕은 모듈이 가능한 최선의 형태일 수 있음
  • 같은 조건이라면 더 깊은 모듈이 더 나음

S3 API는 단순하지만 파일 API와 다름

  • Unix 파일 API는 1970년대 초반에 자리 잡았고, 호환성을 위해 인터페이스는 유지된 채 내부 구현은 여러 번 바뀌었음
  • Amazon S3는 Unix 파일 시스템 API를 다시 구현하지 않음
  • S3의 기본 조작은 Unix 파일 API와 일부만 대응됨
    • GetObject(Bucket, Key, Range=None): 객체 전체 또는 일부를 읽음
    • PutObject(Bucket, Key): 객체 전체를 씀
  • 버킷이라는 추가 개념은 있지만, 기능 대비 인터페이스 비율만 보면 S3가 Unix 파일 API보다 더 단순하다고 볼 수 있음
  • 결정적인 차이는 부분 덮어쓰기의 부재임
    • GetObjectRange 인자로 객체 일부 읽기는 가능함
    • 객체 일부만 덮어쓰는 것은 불가능함
    • 덮어쓰기는 파일 전체 단위로 해야 함
  • 이 차이 때문에 S3는 기존 파일 사용 사례 중 일부에만 잘 맞음

데이터베이스는 S3에 그대로 이식되기 어려움

  • 여러 데이터베이스는 데이터를 파일 시스템 위의 파일에 저장함
    • Postgres는 테이블마다 2~3개 파일과 여러 관리용 파일을 유지함
    • SQLite는 모든 데이터를 단일 파일에 저장하는 것으로 알려져 있음
    • MySQL, MongoDB, Elasticsearch도 데이터를 파일에 저장함
  • 문제는 데이터베이스가 대체로 페이지 단위 부분 덮어쓰기에 의존한다는 점임
    • 데이터는 보통 4KB 또는 8KB 같은 페이지로 저장됨
    • heap 파일 안에는 수천 개의 페이지가 있을 수 있음
    • 필요한 데이터 저장을 위해 페이지가 부분적으로 덮어써짐
  • S3에 SQLite 데이터베이스를 두면 쓰기마다 전체 데이터베이스 파일을 다시 써야 함
  • S3는 큰 쓰기를 빠르게 처리할 수 있지만, 가장 작은 데이터셋을 제외하면 매번 전체 파일을 덮어쓰는 전략은 감당하기 어려움
  • 매번 데이터베이스 파일을 다시 쓰면 데이터베이스 구현자가 만든 트랜잭션 무결성도 활용하기 어려워짐
  • S3에서는 마지막 쓰기가 이김

S3가 잘하는 것과 못하는 것

  • S3의 강점은 읽기와 쓰기의 대역폭이 매우 높다는 점임
    • 온라인에서 S3에 초당 10GB 이상 쓰거나 읽은 사례를 찾기 어렵지 않음
    • S3 쓰기 작업으로 금융 고객사의 사무실 네트워크를 포화시킨 경험도 있음
  • 부분 덮어쓰기 부재 외에도 파일 시스템과 다른 제약이 있음
  • S3에는 rename 또는 move 연산이 없음
    • 이름 변경은 CopyObjectDeleteObject로 처리됨
    • CopyObject는 파일 크기에 비례하는 선형 시간이 걸림
    • 파일을 잘못된 위치에 많이 쓴 뒤 되돌리는 작업은 매우 느림
  • 파일 목록 조회는 느림
    • 읽기·쓰기 대역폭은 매우 높지만, 저장된 항목을 나열하는 작업은 훨씬 느림
    • 느린 로컬 파일 시스템보다도 느릴 수 있음
  • 대신 파일 시스템보다 운영 부담은 낮음
    • 버킷과 키 이름만 지정하면 나머지는 클라우드가 처리함
    • 백업, 오프사이트 복제, 프로비저닝 같은 반복 작업 부담을 줄임
    • 프로비저닝은 용량뿐 아니라 IO 작업에도 해당함

조직 간 인터페이스에서는 깊은 모듈이 더 중요함

  • S3가 첫 번째 인기 클라우드 API였다는 점은 깊은 API의 장점과 연결됨
  • 깊은 API는 단일 시스템 내부 모듈 간 복잡성을 감추는 데 유용하며, 두 기업 간 상호작용처럼 비용이 높은 관계에서는 더 중요함
  • 기업 간 컴퓨터 시스템 연결은 전통적으로 integration이라고 불렸고, 고통의 대명사처럼 다뤄짐
  • SAP 같은 대형 엔터프라이즈 소프트웨어는 깊은 모듈이 아님
    • 조직 거의 전체가 SAP를 이해해야 함
    • 기존 업무 방식과 계속 조정해야 함
    • SAP 통합 프로젝트는 비싸고 거대하며 실패 사례도 반복됨
  • S3 내부 복잡성이 SAP 설치보다 훨씬 적은 것은 아님
    • Amazon은 S3를 “Simple Storage Service”라고 불렀지만 실제 S3의 복잡성은 큼
    • 큐잉 이론, IO 경합, 샤딩, 파일 시스템이 처리하는 여러 문제를 포함함
  • S3의 “simple”은 실제 단순함보다 깊은 인터페이스에 가까움

S3에 맞는 예외와 남는 제약

  • S3가 사용 사례에 비해 비싸다는 문제를 배제하는 것은 아님
  • 깊은 모듈과 얕은 모듈 개념은 John Ousterhout의 A Philosophy of Software Design에서 온 것임
  • S3 API를 저장 계층으로 쓰도록 처음부터 설계된 데이터베이스도 있음
    • Snowflake는 그런 사례임
    • 다만 투명한 이식이 아니라 초기 설계 결정이 필요함
    • Snowflake는 적어도 2016년까지는 이 결정을 매우 일찍 내린 사례임
  • 데이터베이스만 S3에서 어려움을 겪는 것은 아님
    • 많은 파일 형식은 저렴한 seek를 가정함
    • Zip 파일은 S3보다 디스크에서 더 성능이 좋은 대표 사례임

S3에서 아쉬운 점

  • S3 API는 XML 전용
    • JSON은 2006년에도 존재했지만 당시에는 XML이 우세했음
    • Amazon이 SOAP에서 REST로 전환할 때 JSON 버전을 내지 않은 점은 아쉬움
  • Amazon은 XSD schema 유지도 중단함
    • XML API의 핵심 장점 중 하나가 스키마인데, 현재 표준 문서는 웹사이트임
  • Amazon은 로컬 테스트 환경을 제공하지 않음
    • Python에서는 성실한 테스트를 위해 moto 라이브러리를 쓰는 경우가 있음
    • moto는 상용 서비스 테스트 도구임에도 자원봉사자들이 유지함
  • Amazon S3는 checksum을 지원하지만 기본값으로 켜져 있지 않음
    • Amazon은 내구성에 대해 여러 주장을 함
    • 실제 문제가 있었다는 이야기는 듣지 못했지만, 그런 주장이 테스트된 사례도 보지 못함
  • 과거 S3에는 eventual consistency 함정이 있었음
    • 파일을 읽고 덮어쓴 뒤 다시 읽으면 아직 바뀌지 않은 내용을 볼 수 있었음
    • 짧은 시간 동안 가끔 발생해 혼란을 만들었음
    • 다른 S3 구현체들은 이 특성을 복제하지 않았고, Amazon도 몇 년 전 strong read-after-write consistency로 수정함

댓글과 토론

Hacker News 의견들
  • S3 내구성은 과장처럼 보여도 믿을 만하며, 전통적인 파일 시스템과 비교하기 어렵다고 봄
    소프트웨어만이 아니라 물리 인프라와 안전 문화까지 포함된 차이이고, AWS의 가용 영역 격리는 다른 클라우드보다 낫다고 느낌
    S3에서 일할 때 GCP Blob Storage와 가격 비교를 많이 당했지만, Google은 같은 건물이나 같은 건물의 다른 방에 데이터를 둘 수 있어 AWS식 분리와는 공정한 비교가 아니었음
    조직 전체가 데이터 무결성에 극도로 집착했고, 모든 것에 체크섬을 붙였으며 자연재해 같은 큰 사건도 대비했음
    S3 규모에서는 감마선이 하드디스크 플래터를 때려 생기는 무작위 비트 반전 같은 비트 로트도 감지할 수 있었고, 디스크 제조사와 생산 시기별 실패율까지 측정해 특정 배치가 망가져도 데이터 손실 가능성을 줄였음
    중요한 데이터는 다른 곳에 저장하지 않겠다고 할 정도이며, S3 배치 시스템을 직접 만들었음

    • 다른 스토리지 업체에서의 경험이 궁금함
      이 설명은 Cinnabon이 직접 반죽을 만든다고 칭찬하는 것처럼 들리는데, 언급한 것들은 스토리지 회사라면 보통 하는 일임
      모든 것에 체크섬을 붙이는 건 많은 파일 시스템의 기본 기능이고, 집 컴퓨터에서도 비트 로트를 감지하고 알림을 받을 수 있다면 대형 스토리지 업체는 당연히 함
      디스크 제조사별 실패율 추적도 흔하고, 스토리지 회사들은 보고서를 공개하기도 하며, 6명짜리 IT 조직에서도 스프레드시트로 관리했음
      AWS 밖에도, AWS가 생기기 훨씬 전부터 스토리지에 똑똑한 사람들이 많이 일해 왔음
    • AWS의 가용 영역 격리가 모든 업체보다 나은 건 아님
      rsync.net의 지리 중복 계정은 예를 들어 Fremont의 주 저장소와 Denver의 보조 저장소처럼 서로 다른 주나 국가에 존재함
      S3가 규모 때문에 비트 로트를 감지할 수 있다는 것도 맞지 않으며, 개인 서버에서 ZFS를 돌려도 작은 규모로 비트 로트를 잘 감지함
      [1] he.net 본사
    • 데이터에 체크섬을 붙이는 건 편집증 때문이라기보다, Reed-Solomon 알고리즘을 돌리기 위해 어떤 블록이 못 쓰는지 알아야 해서 자연스럽게 필요한 일임
      이런 손상 이벤트가 충분히 많으면 개별 데이터 블록을 다른 머신으로 옮겨 시스템을 “치유”하는 신호로도 쓰일 것 같음
      전반적으로 언급한 것들은 스토리지 시스템에서 꽤 전형적인 일이고, S3만의 특징은 아님
    • Google이 같은 건물에 데이터를 저장한다는 건 사실 같지 않음
      Google Cloud Storage 문서에 따르면 데이터는 여러 영역에 복제되고, 각 영역은 서로 다른 클러스터에 매핑됨
      https://cloud.google.com/compute/docs/regions-zones/zone-vir...
    • “믿으라”보다는 테스트 결과를 믿고 싶음
      S3의 내구성, 무결성, 일관성을 Jepsen만큼 엄격하게 검증한 중립적인 제3자가 있는지 궁금함
      누군가 S3 호환 클라우드 스토리지들을 엄밀하게 비교하면 무섭고 큰 문제들이 드러날 수도 있고, 이미 그런 비교가 있을지도 모름
  • S3에서 진짜 유용한 건 읽기/쓰기 속도보다 목록 조회라고 봄
    버전 없는 버킷이나 삭제 마커가 없는 버킷에서는 특정 접두어 목록 조회가 사실상 상수 시간처럼 동작해서, 1천억 개 객체가 있는 버킷에서도 임의 문자열 뒤에 오는 알파벳순 키 1000개를 요청할 수 있음
    /를 구분자로 쓰는 건 기본값일 뿐이고 어떤 문자든 써서 공통 접두어 집합을 얻을 수 있으며, 디렉터리는 실제로 존재하지 않고 필요할 때 만들어진 것처럼 보일 뿐임
    이 특성 덕분에 성능 걱정 없이 필요한 식별자 기준으로 데이터를 여러 방식으로 나눌 수 있음
    만약 목록 조회가 그냥 느리고 파일 접두어 기반 조회도 못 하며 키 수에 비례해 느려지는 전통적인 Unix 파일 시스템 같았다면 S3는 전혀 유용하지 않았을 것임

    • 별로 설득되지는 않음
      접두어 앞뒤의 키를 뽑아오는 능력은 1970년대부터 있던 데이터베이스 인덱스의 기본이어서 특별히 인상적이지 않음
      사용 사례가 다를 수는 있지만, 버킷 목록 조회가 느려서 방해되는 일이 많았고, 버킷이 조금만 커져도 키를 나열하는 시간이 읽는 시간보다 길어짐
      기억상 목록 조회는 1Mbps 미만 수준이었지만, 지금 바로 테스트할 큰 버킷은 없음
    • 평평한 사전식 키 계층과 디렉터리로 중첩된 파일 시스템 계층의 차이는 예시를 보면 분명함
      dir1/a/000000부터 dir1/a/999999까지 있고 dir1/b가 있을 때, 진짜 계층형 파일 시스템에서 ls dir1/"a""b" 두 항목만 순회하고 반환하면 됨
      반면 구분자 처리가 없는 평평한 문자열 인덱스 키-값 저장소는 "b"에 도달하기 전에 "a/00000"부터 "a/999999"까지 100만 개 디렉터리 항목을 지나야 함
      그래서 단순한 평평한 계층은 한 디렉터리 내용 나열이 진짜 파일 시스템의 O(직접 자식)이 아니라 O(모든 재귀 자식)이 되어 훨씬 느림
      다만 목록 조회 알고리즘에 / 같은 구분자 문자를 알려주면, 사전식 접두어 트리가 다음 /에서 하위 트리를 효율적으로 건너뛸 수 있음
      Amazon S3 문서도 CommonPrefixes 필드에서 더 깊은 수준에 중첩된 수백만 키를 건너뛰고 요약한다고 명시함
      https://docs.aws.amazon.com/AmazonS3/latest/userguide/using-...
      실제 구현이 정말 순회를 절약하는지, 아니면 순회한 뒤 결과만 줄이는지는 테스트해 보지 않았지만 절약하길 기대함
    • 1993년 XFS부터 HPFS의 영향을 받은 좋은 UNIX 파일 시스템들은 디렉터리를 어떤 형태의 B-트리로 구현해 왔음
      그래서 항목 수에 비례해 느려지지 않고, 파일 접두어 기반 목록 조회도 매우 빠름
    • S3 사용의 99%는 이미 아는 키로 객체를 가져오는 일일 것 같음
      접두어 목록 조회를 핵심 기능으로 보는 건 이상하게 느껴짐
    • 여기서 “상수 시간”의 정의가 같은지 모르겠음
      네트워크 호출 한 번에 1000개 키를 받는다고 해서 백엔드의 복잡도에 대해 아무것도 보장하지는 않음
  • 최근 S3 자산 관리 스크립트를 손보다가 목록 조회 속도에 놀랐음
    동료가 파일 목록 캐시가 필요하다며 미리 채운 캐시를 보내줬고, 처음엔 정말 필요할 리 없다고 생각했지만 직접 확인해 보니 달랐음
    개별 자산용 루트 디렉터리가 약 10만 개 있고, 각각에 5~6개 디렉터리와 소수 파일이 있으며, 전체 파일은 아마 100만 개 미만이고 깊이는 최대 3단계 정도임
    이 파일들을 재귀적으로 나열하는 데 문자 그대로 15분이 걸림
    Stack Overflow와 ChatGPT의 속도 개선 제안을 이것저것 시도했지만 의미 있는 결과가 없었고, 왜 이렇게 느린지 이해가 안 됨
    Amazon이 왜 이걸 고치지 않았는지 모르겠고, 밖에서 보기엔 개별 버킷에 B-트리 몇 개 붙이면 끝날 것처럼 보임
    어려운 문제라면 그 이유가 흥미로울 것 같아서 듣고 싶음

    • S3는 근본적으로 키-값 저장소
      객체를 “디렉터리”처럼 볼 수 있는 건 접두어 필터일 뿐이고, 파일 시스템이 아니며 디렉터리 개념도 없음
    • “재귀적으로”라는 표현과 “디렉터리”, “단계”를 길게 설명한 부분이 걱정됨
      S3에서 가장 빠르게 객체를 나열하는 방법에는 재귀가 전혀 필요 없고, 그냥 접두어 아래의 모든 객체를 나열하면 됨
      경로 구분자를 써서 S3 키를 폴더 구조처럼 가장하고 “폴더별로” 돌면 훨씬 느려짐
      ListObjectsV2를 호출할 때 delimiter를 넘기지 말아야 하며, 구분자 기능을 쓰지 않으면 “디렉터리”와 “단계”는 성능에 영향을 주지 않음
      원하는 총 시간을 맞추려면 하나의 목록 작업을 여러 접두어에 대한 병렬 목록 조회로 나누면 됨
    • 이 문제의 재미있는 귀결은 S3 버킷 삭제가 간단하지 않다는 점임
      객체가 들어 있는 버킷은 삭제할 수 없고, S3에 모든 객체를 지워 달라고 한 번에 말할 수도 없음
      각 객체에 대해 개별 삭제 API 요청을 보내야 하며, 그러려면 객체를 1000개씩 나열하는 요청도 보내야 하고, 이 목록 호출은 시간도 걸리고 비용도 듦
      상황 정리는 이 글이 잘함: https://cloudcasts.io/article/deleting-an-s3-bucket-costs-mo...
      S3 버킷을 빨리 치우는 가장 빠른 방법은 결국 그 버킷이 속한 AWS 계정을 삭제하는 것임
    • 이유는 훨씬 평범할 수 있음
      요청 한 번에 1만 개 객체를 나열할 수 있고 다음 1만 개를 얻으려면 이전 요청 결과가 필요하니 전부 직렬임
      100만 파일을 나열하려면 연속 요청 100번이 필요하고, 왕복 시간이 50ms만 되어도 왕복만 5초이며, 평평한 반복에서 목록 자체를 만드는 비용은 별도임
      1만 항목 목록 조회 비용은 쓰기 비용과 비슷한데 이 자체가 꽤 느린 편이고, 각 목록 조회가 강한 일관성 스냅샷일 가능성도 있어 비용이 더 붙음
      B-트리는 디렉터리 순회를 하는 경우가 아니면 별 도움이 안 될 것 같고, 그 경우에도 병목은 네트워크 작업과 외부로 노출된 API일 가능성이 큼
      결국 파일 목록 조회는 그렇게 중요한 사용 사례가 아니며, 보통은 객체 수명 주기 같은 기능으로 S3에 원하는 작업을 맡기고 내부 파일 시스템 계층에서 효율적으로 처리하게 함
    • S3 버킷 안에 디렉터리가 있다고 생각하는 건 좋은 모델이 아님
      전부 객체이고, 웹 인터페이스가 슬래시로 나뉜 접두어를 보기 좋게 표현할 뿐임
      각 객체에는 키가 있고 그 키에 슬래시가 들어갈 수 있으며, 마음 편하게 각 구간을 디렉터리라고 생각할 수는 있음
      하지만 보통 디렉터리에 대해 하던 작업을 시도하면 그 환상은 깨짐
  • S3를 만든 사람들은 이게 파일 시스템이 아니라는 걸 알고 있었고, 객체 저장소라는 이름은 글에서 지적한 차이들을 설명하려는 표현이었다고 봄
    “객체가 인기 있었다”는 건 실행 코드와 로컬 상태를 묶는 소프트웨어 구성요소로서의 객체를 말하지만, S3 초기 예시는 “라이브 객체를 직렬화했다가 다른 프로세스에서 역직렬화”하는 식이 아니었음
    예시는 전부 웹사이트 정적 자산 같은 것들이었고, 당시 데이터베이스 쪽에서도 “binary large object” 또는 “blob”이라는 말에서 객체라는 의미가 쓰였음
    S3는 데이터베이스에 넣기 애매한 것들을 저장하는 곳에 가까웠고, 처음 설계 당시 출시 사용 사례들은 콘텐츠 인덱스가 어딘가 다른 곳에 있다고 가정했기 때문에 목록 조회가 느린 것도 바로 그런 성격을 설명함

    • 글쓴이는 “객체 저장소”가 객체 지향 프로그래밍과 무관한 스토리지 시스템 용어라는 점을 모르는 듯함
      https://en.wikipedia.org/wiki/Object_storage
    • 글쓴이가 객체 지향 프로그래밍과 객체 저장소를 혼동하는 것 같아 걱정됨
      GCP 설명에 따르면 객체 저장소는 비정형 데이터를 객체 단위로 나눠 구조적으로 평평한 데이터 환경에 저장하는 아키텍처임
      https://cloud.google.com/learn/what-is-object-storage
      즉 비정형, 평평한 구성, 전체 항목 단위 작업인 읽기와 쓰기가 핵심임
  • S3는 파일도 아니고 파일 시스템은 더더욱 아님
    파일 추상화에서 기대하는 건 변경 가능성으로, 파일 일부를 편집하고, 늘리고, 줄이고, 임의 오프셋에서 읽고 쓸 수 있어야 함
    파일을 잡은 뒤 다시 루트나 상위 개념으로 올라갈 필요가 없어야 하는데, S3는 변경 가능한 목록 위에 변경 불가능한 객체를 제공할 뿐이고, 변경하려면 복사해서 다시 올려야 함
    원래의 파일 추상화는 디스크의 섹터들을 찾아 클라이언트에 연속 버퍼처럼 보여주는 것이며, S3는 다른 문제를 풂
    많은 사람이 UNIX의 “모든 것은 파일”이라는 좋은 아이디어를 모든 것이 연속 가상 버퍼처럼 보여야 한다는 뜻으로 오해함
    진짜 핵심은 파일이든 시스템이 프로세스에 보여주고 싶은 다른 객체든 기본 잎 노드가 있고, 디렉터리를 포함한 모든 것이 디렉터리에 나열될 수 있으며, 재귀적인 트리가 있다는 점임
    파일 시스템을 만드는 건 특정 잎 노드의 타입이 아니라 디렉터리
    소켓이나 프레임버퍼 같은 새 잎 타입을 추가하는 건 거의 사소하고 이 아이디어를 해치지 않지만, 리스트 같은 다른 종류의 컨테이너를 추가하면 파일 시스템 구조가 복잡해져 개념적 일관성이 깨짐
    S3는 이런 것들을 하지 않지만 괜찮음
    데이터베이스에 맞지 않는 것들을 넣어 두고 보지 않는 동안 비트 로트가 생기지 않길 바랄 뿐임
    S3를 파일 시스템처럼 만들고 싶어 하는 욕구는 S3가 잘하는 일을 고객이 오해하고, 제품 관리가 그 오해를 막지 않고 받아준 데서 온다고 봄

    • S3는 파일 시스템이 아니라는 데 동의함
      더 맞는 비유는 블록 저장 장치이고, 다만 블록 크기가 임의이며 키가 붙을 수 있는 아주 이상한 블록 장치에 가까움
      파일 시스템은 블록 저장 장치 위에 얹히는 추상화이므로, “S3 파일 시스템”도 S3를 기반 블록 저장소처럼 두고 그 위에 올라가는 추상화여야 함
    • 읽기 전용 파일 시스템은 이 정의와 어떻게 맞는지 궁금함
  • 파일 시스템은 블록 장치 위에 만들어진 추상화임
    블록 장치는 거대한 바이트 배열을 제공하고, 예를 들어 “273041 위치에 이 300바이트를 쓰라”처럼 블록 단위 읽기/쓰기를 하게 해줌
    블록 장치 자체도 실제 하드웨어 위에 만들어진 추상화라서, “이 300바이트를 쓰라”는 말은 실제로는 “2번 플래터의 바늘을 6번 위치로 움직여라” 같은 동작으로 이어짐
    S3는 원시 저장소 위에 만들어진 다른 추상화일 뿐이고, 엄격히 평평한 키-객체 저장소
    파일 시스템 기능이 필요하면 앱에서 구현하거나 파일 시스템을 쓰면 됨
    추가만 필요하면 데이터베이스로 추가 체인을 추적하고 청크는 S3에 저장하면 되고, 안 맞으면 다른 걸 쓰면 됨
    복사가 필요하면 데이터베이스에서 같은 객체에 대한 새 참조를 만들면 되고, 안 맞으면 다른 걸 쓰면 됨
    S3는 많은 사람에게 잘 맞으니 다른 것으로 만들려고 하지 말아야 함
    이미 확립된 분야 용어의 의미를 바꾸려는 것도 그만해야 하며, 파일 시스템은 교과서에 설명된 개념이고 S3는 파일 시스템이라고 주장한 적이 없음
    운영체제 설계도 조금 공부하면 정말 도움이 되고 재미도 있음

  • Apache Arrow의 object_storeApache OpenDAL API를 비교하는 논의가 https://github.com/apache/arrow-rs/issues/3888에서 있었음
    Apache OpenDAL은 S3와 여러 클라우드 저장소를 포함한 여러 백엔드 위에 파일 시스템 같은 API를 제공하는 라이브러리임
    GreptimeDB와 Databend 같은 일부 데이터베이스 시스템은 클라우드 저장소의 데이터에 접근하기 위한 더 나은 S3 SDK처럼 OpenDAL을 사용함
    S3 위에 파일 시스템 같은 인터페이스를 관리하는 다른 해법으로 Alluxio와 JuiceFS도 있지만, Apache OpenDAL과 달리 별도 배포와 전용 내부 메타데이터 서비스가 필요함

    • TrinoDB용 로컬 캐시 계층으로 Alluxio를 OpenDAL로 대체할 수 있을지는 잘 모르겠음
  • S3를 이야기할 때 Backblaze B2도 언급할 만함
    가격이 S3보다 3배 낮아서 매우 마음에 들며, Backblaze 관계자는 아님

    • Backblaze B2는 싸지만 운영 환경에서 쓴다면 비용에 포함해야 할 것들이 있음
      매주 PST 11:30~13:30의 2시간 유지보수 창이 있고, 보통 다운타임은 없지만 가끔 미국 업무시간 한가운데에 전체 장애가 나기도 함
      오류율이 사용 불가능한 수준으로 올라가면 지원 티켓을 넣어야 했고, 지난 몇 년간 대략 1년에 한 번 정도 겪었음
      지원은 자기 쪽 오류 로그나 가시성이 없는 것처럼 수많은 질문만 하고 문제를 제대로 들여다보지 않음
      업로드 성공이라고 응답했는데 실제로는 B2 시스템에 0바이트로 저장되는 거짓 성공도 있어, 성공 코드에도 반드시 업로드를 검증해야 함
      Log4j2 CVE처럼 심각도 높은 취약점이 나오면 10시간 중단처럼 긴 장애도 생길 수 있음
      가격은 최고지만, 더 성숙한 클라우드 저장소 서비스와 직접 비교 가능한 제품은 아님
    • 대안 서비스에서는 데이터가 그 회사를 믿을 수 있는 만큼만 안전하다는 점이 항상 핵심임
      다만 두 겹의 외부 백업으로 어느 정도 보완할 수 있다고 봄
    • B2는 좋았지만 IPv4 주소 비용까지 내면서 쓸 만큼은 아니었음
      멀티클라우드 솔루션이라고 광고하면서도 NAT 게이트웨이와 IPv4 요금이 어디에나 붙는 상황에서는 도입 가능성을 사실상 없애는 게 황당함
      읽기가 많고 쓰기가 적은 사용량이라 B2 대역폭 요금을 내도 돈을 절약할 수 있었지만, NAT64 게이트웨이를 거치거나 B2에 접근하기 위해 시간당 요금을 내야 하면 그렇지 않음
  • 좋은 글이고, rclone mount로 클라우드 저장소를 FUSE로 마운트하는 여정을 시작하기 전에 읽었으면 유용했을 것임
    여러 번 반복한 끝에 rclone에는 S3나 Google Cloud Storage, Azure Blob, OpenStack Swift, Oracle Object Storage 같은 저장소를 POSIX 비슷한 파일 시스템 계층으로 맞춰 주는 VFS 계층이 생겼고, 실제 rclone mount 코드는 그 위의 얇은 계층임
    VFS 계층은 호환성 수준이 여러 단계인데, off에서는 디렉터리 캐싱만 함
    이 모드에서는 글에서 말한 것처럼 같은 파일을 동시에 읽고 쓸 수 없고, 파일 중간에 쓸 수 없으며, 파일은 순차적으로만 쓸 수 있음
    놀랍게도 이런 제약에서도 꽤 많은 것이 잘 동작함
    다음 단계인 writes는 같은 파일을 동시에 읽고 쓰기, 파일 중간에 쓰기 등 앱이 원하는 대부분의 POSIX 기능을 지원하지만, 파일의 로컬 복사본을 만들고 닫힐 때 비동기로 업로드하는 비용이 듦
    VFS 캐싱 모드 문서는 글의 제약을 잘 반영함: https://rclone.org/commands/rclone_mount/#vfs-file-caching
    기본적으로 S3에는 진짜 디렉터리도 없어서 파일이 없는 디렉터리를 가질 수 없고, 디렉터리에 수정 시간 같은 유효한 메타데이터도 없음
    /로 끝나는 0바이트 파일인 디렉터리 마커를 만들 수 있고 rclone을 포함한 많은 도구가 이를 지원함
    빈 디렉터리가 없어도 보통은 큰 문제가 아닌데, VFS 계층이 이를 가짜로 만들어 주고 대부분 앱은 곧 그 안에 뭔가를 쓰기 때문임
    결국 S3처럼 보이는 것을 POSIX 파일 시스템처럼 보이게 바꾸는 일은 상당히 많은 작업이고, 열린 파일 이름 바꾸기 같은 동작과 까다로운 예외 상황 뒤에는 많은 눈속임이 있음
    rclone의 저수준 move/sync/copy 명령은 그런 처리를 하지 않고 S3 API를 거의 그대로 사용함
    S3 API에서 하나를 바꿀 수 있다면 목록 조회 때 메타데이터도 읽는 옵션이 있었으면 함
    rclone은 파일 수정 시간을 객체 메타데이터에 저장하는데, 이를 대량으로 읽을 방법이 없어 객체마다 HEAD를 해야 함
    또는 업로드할 때 객체의 Last-Modified를 설정할 수 있어도 좋겠음

    • 메타데이터를 키 이름 자체에 저장하는 식으로 우회할 수 있지 않을까 싶음
      키 길이 제한이 1024라 저장할 수 있는 메타데이터 양은 제한되지만, 파일 경로를 감안해도 꽤 넉넉함
      정규화된 경로에서 유효하지 않은 // 같은 구분자를 써서 /path/to/file.txt//mtime=1710066090처럼 둘 수 있음
      여전히 접두어로 “디렉터리”를 가져오고, //를 접두어처럼 써서 직접 파일을 가져올 수 있음
      다만 이런 형식은 다른 소프트웨어와의 호환성을 크게 해칠 것 같음
    • 목록 조회에 메타데이터를 포함하는 옵션이 있으면 좋다는 데 동의함
      MinIO에서는 적절한 권한이 있을 때 목록 조회에 메타데이터와 태그를 포함하는 metadata=true라는 “비밀” 파라미터를 추가했음
      확장이기 때문에 안정적으로 사용할 수 있는 건 아니지만, rclone은 항상 시도해 보고 가능하면 쓸 수 있음
      /로 끝나는 0바이트 파일도 가능하지만, 목록 조회의 공유 접두어 자체를 디렉터리로 볼 수도 있음
      그러면 디렉터리는 상태가 없고 그 안에 객체가 없으면 존재할 수 없다는 장단점이 생김
      업로드할 때 Last-Modified를 설정할 수 있으면 클라이언트 제약이 줄지만, 서버 시간이 기준이 되는 이점도 있음
      클라이언트 쪽 복제나 미러링에서도 같은 제약을 처리해야 함
      개인적으로 가장 큰 불만은 단일 객체의 버전 정보를 반환하는 HeadObjectVersions가 없다는 점임
      ListObjectVersions는 주어진 접두어가 실제 접두어인지 객체 키인지 알 수 없어서 항상 클러스터 전체 작업이 될 수밖에 없음
      AWS가 최근 GetObjectAttributes를 추가했지만, 거기에 잘 어울렸을 버전 정보는 들어가지 않았음
  • S3의 “Simple”은 “깊지 않다”가 아니라, 요구사항을 달성하는 데 필요한 부품이 가장 적다는 뜻임
    분산형, 중앙집중형, 복제형, 고가용성, 고내구성, 고대역폭, 저지연, 강한 일관성, 동기식, 확장 가능한 객체 저장소에 HTTP REST API까지 필요하다면 S3보다 단순하게 만들기 어렵다고 봄
    AWS S3에는 오랜 기간 많은 기능이 추가됐지만 기본 동작은 그대로 유지됨

    • 글에서 쓰는 용어로는 “필요한 최소 부품”이 바로 깊다는 뜻임
      Ousterhout의 『A Philosophy of Software Design』 기준에서 단순하다는 건 복잡하지 않다는 뜻이고, Rich Hickey의 “Simple Made Easy”도 같은 맥락임
      반면 “깊다”는 작은 인터페이스로 내부적으로 복잡한 많은 기능을 제공한다는 뜻이라, S3에는 “단순하다”보다 이 표현이 더 잘 맞음
      S3에 별것 없다는 뜻의 단순함과는 다름
      https://www.infoq.com/presentations/Simple-Made-Easy/
    • 글의 표현을 따르면, 단순함은 거의 깊음과 좁은 인터페이스를 함축한다고 볼 수도 있음