객체 저장소
고친 사람 github-actions[bot]
객체 저장소는 파일에 이름을 붙여 맡기고 그 이름으로 다시 꺼내 쓰게 해 주는 저장 방식입니다. 넣고 꺼낼 때는 웹 요청 하나면 됩니다. 로그와 백업과 사진처럼 한 번 쓰고 오래 쌓아 두는 데이터가 주로 여기에 놓입니다. Git 안에서는 같은 이름이 커밋과 파일을 쌓아 두는 내부 저장소를 가리킵니다.
쉽고 빠른 이해
객체 저장소는 파일에 이름을 붙여 맡기고 그 이름으로 다시 꺼내는 저장소입니다. 하루치 로그를 10/16/web.log 라는 이름으로 올려 두면 나중에 그 이름만 대고 받아 옵니다.
디스크 한 대에 파일을 쌓으면 디스크가 차거나 고장 날 때 곤란합니다. 폴더 나무는 여러 서버에 나눠 담기도 어렵습니다. 객체 저장소는 폴더를 없애고 이름 하나로만 찾게 해서 파일을 서버 여러 대에 흩어 둡니다.
어떻게 도나:
- 파일에 이름을 붙여 웹 요청으로 올립니다
- 저장소가 파일을 서버 여러 대에 사본으로 나눠 둡니다
- 같은 이름을 대면 파일 전체를 돌려줍니다
대가는 셋입니다.
- 파일 일부만 고칠 수 없어서 한 글자를 바꿔도 전체를 다시 올립니다
- 요청마다 네트워크를 건너므로 서버에 붙은 디스크보다 느립니다
- 폴더처럼 보이는 것은 이름 앞부분일 뿐입니다. 앞부분
10/16/을 바꾸려면 그렇게 시작하는 파일을 전부 새 이름으로 하나씩 복사해야 합니다
이런 대가가 있어서 로그·백업·사진처럼 한 번 쓰고 오래 두는 파일에 주로 씁니다.
상세
객체 저장소(object storage)는 데이터를 객체라는 덩어리 단위로 맡아 두었다가 돌려주는 방식입니다. 객체 하나는 파일 하나라고 보면 됩니다. 영어 이름을 살려 오브젝트 스토리지라고도 부릅니다.
이 절은 먼저 객체 저장소가 왜 생겼는지 봅니다. 그다음 객체와 버킷이 무엇인지, 넣고 꺼내는 요청이 어떻게 생겼는지, 여러 서버에 어떻게 흩어 두는지를 따라갑니다. 끝으로 다른 저장 방식과 비교합니다. 어떤 데이터에 맞는지도 봅니다.
창고에 짐을 맡기는 장면과 닮았습니다. 상자마다 이름표를 붙여 맡깁니다. 찾을 때는 이름표만 대면 창고 직원이 상자를 찾아 내줍니다. 상자가 몇 층 어느 선반에 있는지는 맡긴 사람이 몰라도 됩니다.
디스크와 디렉터리로 쌓을 때 막히는 점
파일 시스템은 디스크 위의 파일을 디렉터리 나무로 정리하는 방식입니다. 경로를 따라 내려가 파일을 찾습니다. 파일 가운데를 고쳐 쓸 수도 있습니다. 서버 한 대의 디스크를 쓸 때는 이 방식이 익숙합니다.
문제는 데이터가 디스크 한 대를 넘을 때입니다. 디스크가 차면 더 큰 디스크로 옮겨야 합니다. 디스크가 고장 나면 그 안의 파일을 다 잃습니다. 여러 서버가 같은 파일을 읽어야 할 때도 곤란합니다.
디렉터리 나무는 여러 서버에 나눠 담기 어렵습니다. 파일 하나를 만들거나 옮길 때마다 위 디렉터리의 목록도 함께 바뀌기 때문입니다. 서버가 여러 대면 모든 서버가 이 목록을 똑같이 알고 있어야 합니다.
객체 저장소는 디렉터리를 버려서 이 문제를 피합니다. 파일마다 이름 하나만 둡니다. 이름끼리는 서로 얽히지 않습니다. 그러면 파일을 서버 여러 대에 흩어 두기 쉽습니다. 용량이 모자라면 서버를 더 붙이면 됩니다.
객체를 이루는 데이터 · 키 · 메타데이터
객체는 객체 저장소가 한 번에 맡아 두고 돌려주는 단위입니다. 객체 하나에는 세 가지가 함께 담깁니다. 찾을 때 대는 이름, 파일 내용, 그 내용을 설명하는 정보입니다.
찾을 때 대는 이름을 키라고 합니다. 내용을 설명하는 정보는 메타데이터라고 합니다. 메타데이터는 데이터에 대한 데이터라는 뜻입니다. 아래 표가 셋을 나란히 놓습니다.
| 구성 | 무엇인가 | 예 |
|---|---|---|
| 키 | 객체를 찾는 이름 | 10/16/web.log |
| 데이터 | 파일 내용인 바이트 덩어리 | 로그 글자 · 사진 바이트 |
| 메타데이터 | 데이터를 설명하는 정보 | 크기 · 올린 시각 · 파일 종류 |
데이터는 저장소가 안을 들여다보지 않는 바이트 덩어리입니다. 글이든 사진이든 저장소에는 다 같은 바이트입니다.
메타데이터에는 저장소가 스스로 붙이는 것과 올리는 사람이 붙이는 것이 있습니다. 크기와 올린 시각은 저장소가 붙입니다. 「이 파일을 만든 서비스」 같은 꼬리표는 올리는 사람이 이름과 값의 쌍으로 붙입니다.
버킷과 평평한 이름 목록
버킷은 객체를 담는 가장 바깥 통입니다. 객체는 반드시 어느 버킷 하나에 들어갑니다. 누가 읽고 쓸 수 있는지 같은 규칙은 대개 버킷 단위로 정합니다.
버킷 안에는 디렉터리가 없습니다. 키 10/16/web.log 의 빗금(/)은 디렉터리 구분이 아니라 이름에 들어간 글자입니다. 버킷 안의 객체는 이름이 서로 다른 객체들이 한 줄로 늘어선 목록일 뿐입니다. 이런 구조를 평평한 이름 공간이라고 부릅니다.
flowchart TD
subgraph FS["파일 시스템 · 디렉터리 나무"]
L["logs"] --> D10["10"]
D10 --> D16["16"]
D16 --> W1["web.log"]
end
subgraph OS["객체 저장소 · 평평한 이름 목록"]
B["버킷 logs"] --> K1["10/16/web.log"]
B --> K2["10/16/api.log"]
B --> K3["10/17/web.log"]
end
그림 위쪽은 파일 시스템입니다. web.log 에 닿으려면 디렉터리 logs·10·16 을 차례로 지나야 합니다. 아래쪽 객체 저장소에서는 버킷 logs 아래에 키 10/16/web.log·10/16/api.log·10/17/web.log 셋이 나란히 붙어 있을 뿐입니다.
그래도 제품의 웹 관리 화면은 빗금을 기준으로 폴더처럼 보여 줍니다. 키가 10/16/ 으로 시작하는 객체만 골라 달라고 목록을 요청할 수 있어서입니다. 키의 앞부분을 접두사라고 부릅니다. 폴더처럼 보이는 것은 접두사로 거른 목록입니다.
한 덩어리로 쓰고 읽는 규칙
객체는 한 덩어리로 올리고 한 덩어리로 바꿉니다. 파일 가운데 몇 바이트만 고쳐 쓰는 요청은 없습니다. 끝에 덧붙이는 요청도 대개 없습니다.
내용을 바꾸려면 같은 키로 새 객체를 다시 올립니다. 그러면 옛 객체가 새 객체로 덮어써집니다. 한 글자를 바꿔도 파일 전체를 다시 올리는 셈입니다.
이 제약 덕분에 여러 서버에 흩어 두기가 쉬워집니다. 저장소는 서버 고장에 대비해 같은 객체를 서버 여러 대에 사본으로 둡니다. 객체가 중간에 조금씩 바뀌지 않으므로 이 사본끼리 어긋날 틈이 줄어듭니다. 사본을 맞출 때도 객체 하나를 통째로 옮기면 끝납니다.
읽기는 조금 더 너그럽습니다. 많은 객체 저장소는 객체의 일부 구간만 읽어 오는 범위 요청을 받습니다. 큰 동영상의 앞부분만 먼저 받아 재생할 때 씁니다.
덮어쓴 옛 객체를 지우지 않고 남겨 두는 기능을 갖춘 제품도 많습니다. 이를 객체 버전 관리라고 부릅니다. 실수로 덮어쓰거나 지운 파일을 되살릴 때 씁니다.
HTTP 요청으로 넣고 꺼내기
객체 저장소는 HTTP(HyperText Transfer Protocol) 요청으로 부릅니다. HTTP 는 웹 브라우저가 웹 서버와 주고받는 요청 방식입니다. 디스크를 서버에 붙이는 절차가 없으니 네트워크만 닿으면 어디서든 부를 수 있습니다.
아래는 버킷 logs 에 키 10/16/web.log 로 객체를 올리고, 받고, 지우는 요청입니다. 오른쪽 주석이 저장소가 돌려주는 것입니다.
PUT /logs/10/16/web.log // 성공 응답
GET /logs/10/16/web.log // 파일 전체
DELETE /logs/10/16/web.log // 성공 응답
GET /logs?prefix=10/16/ // 키 목록
요청의 첫 낱말이 곧 동작입니다. PUT 은 올리기, GET 은 받기, DELETE 는 지우기입니다. 마지막 줄은 접두사 10/16/ 으로 시작하는 키의 목록을 요청합니다. 주소의 꼴은 제품마다 조금씩 다릅니다.
실무에서는 이 요청을 손으로 짜지 않습니다. 제품이 내놓은 클라이언트 라이브러리를 불러 함수 하나로 올리고 받습니다. 라이브러리가 안에서 위와 같은 HTTP 요청을 만들어 보냅니다.
아주 큰 파일은 한 번에 올리지 않습니다. 여러 조각으로 나눠 올린 뒤 하나로 합쳐 달라고 요청합니다. 이를 멀티파트 업로드라고 합니다. 조각 하나가 실패하면 그 조각만 다시 올리면 됩니다.
여러 서버에 흩어 두는 구조
객체 저장소 하나는 서버 여러 대가 한 저장소처럼 움직이는 분산 시스템입니다. 이 소절은 객체 하나를 올릴 때 그 안에서 무슨 일이 일어나는지 따라갑니다.
요청은 먼저 앞단 서버가 받습니다. 앞단 서버는 키를 보고 그 객체를 어느 저장 서버들에 둘지 정합니다. 키를 해시 함수에 넣어 나온 값으로 서버를 고르는 방식이 흔합니다. 해시 함수는 같은 입력에 언제나 같은 값을 내므로 같은 키는 언제나 같은 서버들로 갑니다.
그다음 객체를 저장 서버 여러 대에 씁니다. 같은 객체의 사본을 여러 벌 두는 것을 복제라고 합니다. 서버 하나가 고장 나도 다른 사본에서 읽으면 되므로 파일을 잃지 않습니다.
sequenceDiagram
participant A as 앱
participant F as 앞단 서버
participant S1 as 저장 서버 1
participant S2 as 저장 서버 2
A->>F: PUT 10/16/web.log
Note over F: 키로 저장 서버를 고른다
F->>S1: 사본을 쓴다
F->>S2: 사본을 쓴다
S1-->>F: 다 썼다
S2-->>F: 다 썼다
F-->>A: 성공 응답
그림에서 앱은 앞단 서버하고만 이야기합니다. 객체가 어느 저장 서버에 몇 벌 놓였는지는 앱이 모릅니다. 성공 응답은 대개 사본이 다 써진 뒤에 돌아옵니다. 그래서 성공을 받은 객체는 서버 하나가 죽어도 남아 있습니다.
사본을 여러 벌 두면 공간도 그만큼 더 듭니다. 그래서 이레이저 코딩을 쓰기도 합니다. 객체를 조각으로 나누고 복구용 조각을 몇 개 더 만들어 서로 다른 서버에 흩어 둡니다. 조각 몇 개를 잃어도 남은 조각으로 원래 객체를 되살립니다.
맡긴 데이터를 잃지 않는 성질을 내구성이라고 합니다. 객체 저장소는 내구성을 지키려고 사본이 다 써질 때까지 기다렸다가 응답합니다. 이 기다림에 네트워크를 건너는 시간까지 더해져 서버에 붙은 디스크보다 응답이 느립니다.
블록 저장소 · 파일 저장소와 비교
서버가 쓰는 저장소는 흔히 세 갈래로 나눕니다. 객체 저장소 말고 둘이 더 있습니다. 이 소절은 그 둘을 먼저 풀고 셋을 표로 비교합니다.
블록 저장소는 저장 공간을 같은 크기의 칸으로 나누고 칸 번호로 읽고 쓰게 합니다. 이 칸이 블록입니다. 서버는 블록 저장소를 자기 디스크처럼 붙이고 그 위에 파일 시스템을 올려 씁니다.
파일 저장소는 파일 시스템 하나를 네트워크 너머 여러 서버가 같이 쓰게 합니다. 서버들은 같은 디렉터리를 보고 같은 경로로 파일을 엽니다. NFS(Network File System, 네트워크 파일 시스템)가 이런 공유에 쓰는 대표 프로토콜입니다.
| 블록 저장소 | 파일 저장소 | 객체 저장소 | |
|---|---|---|---|
| 다루는 단위 | 같은 크기의 블록 | 파일과 디렉터리 | 객체 |
| 찾는 법 | 블록 번호 | 디렉터리 경로 | 버킷과 키 |
| 부르는 법 | 서버가 디스크처럼 붙인다 | 공유 파일 시스템으로 연다 | HTTP 요청 |
| 일부 고쳐 쓰기 | 된다 | 된다 | 안 된다 |
| 함께 쓰는 서버 | 대개 한 대 | 여러 대 | 네트워크가 닿는 어디서나 |
표에서 셋을 가르는 줄은 「일부 고쳐 쓰기」입니다. 블록과 파일 저장소는 파일 가운데를 고칠 수 있습니다. 그래서 데이터베이스처럼 조금씩 자주 바꾸는 데이터를 받습니다. 객체 저장소는 일부 고쳐 쓰기를 포기하는 대신 서버를 붙이는 만큼 용량이 늘어나는 쪽을 골랐습니다.
객체 저장소에 맞는 데이터
객체 저장소에 맞는 데이터는 한 번 쓰고 오래 두거나, 한 번 쓰고 여러 번 읽는 데이터입니다. 파일이 커도 되고 개수가 많아도 됩니다. 아래 표는 흔한 쓰임과 그 까닭입니다.
| 쓰임 | 맞는 까닭 |
|---|---|
| 로그 보관 | 쌓이기만 하고 고치지 않는다 |
| 백업 | 한 번 쓰고 오래 두며 잃으면 안 된다 |
| 사진 · 동영상 같은 사용자 파일 | 크고 개수가 많다. HTTP 로 바로 내준다 |
| 데이터 레이크 | 분석할 원본 파일을 손대지 않고 쌓아 둔다 |
로그 수집기는 서버들의 로그를 모아 여러 목적지로 보내는 프로그램입니다. Fluent Bit 같은 수집기는 로그를 일정 시간이나 크기만큼 모았다가 객체 하나로 올립니다. 이미 올린 객체 끝에는 덧붙일 수 없으니 묶음마다 새 객체가 됩니다. 묶음끼리 이름이 겹치지 않도록 키에 날짜와 시각을 넣어 10/16/web-0900.log 처럼 짓습니다.
데이터 레이크는 분석에 쓸 원본 데이터를 가공하지 않고 한곳에 쌓아 두는 저장소입니다. 그 바닥에는 객체 저장소를 자주 깝니다. 용량을 서버 대수만큼 늘릴 수 있기 때문입니다. Apache Spark 같은 분석 엔진은 객체 저장소의 파일을 바로 읽어 계산합니다.
안 맞는 데이터와 치르는 비용
조금씩 자주 고치는 데이터에는 맞지 않습니다. 데이터베이스 파일이 그렇습니다. 행 하나를 바꿀 때마다 파일 전체를 다시 올릴 수는 없습니다.
응답이 느립니다. 요청마다 네트워크를 건너 HTTP 를 한 번 주고받기 때문입니다. 서버에 붙은 디스크를 읽을 때보다 한참 오래 걸립니다. 작은 파일을 여러 번 읽는 일에서 이 차이가 크게 쌓입니다.
이름 바꾸기가 비쌉니다. 디렉터리가 없으니 디렉터리 이름만 바꾸는 요청도 없습니다. 접두사 10/16/ 을 oct/16/ 으로 바꾸려면 그 접두사로 시작하는 객체를 하나씩 새 키로 복사하고 옛 객체를 지웁니다.
클라우드에서 파는 객체 저장소는 대개 저장한 양과 함께 요청 횟수로도 요금을 매깁니다. 작은 객체가 아주 많으면 요청이 늘어 요금과 시간이 함께 늘어납니다. 그래서 작은 기록은 모아서 큰 객체 하나로 올립니다.
객체 저장소를 파는 서비스와 제품
클라우드 업체마다 객체 저장소를 하나씩 팝니다. Amazon S3·Google Cloud Storage·Azure Blob Storage가 그렇습니다. 자기 서버에 깔아 굴리는 제품으로는 MinIO·Ceph가 있습니다.
S3 가 일찍 널리 퍼졌습니다. 그래서 그 요청 꼴이 사실상 표준으로 쓰입니다. 다른 제품들이 S3 와 같은 요청을 받아 주어 S3 용으로 짠 코드를 거의 고치지 않고 옮겨 붙일 수 있게 합니다. 이런 제품을 S3 호환 저장소라고 부릅니다.
Git 의 객체 저장소
Git 안에도 객체 저장소라는 이름이 나옵니다. Git 저장소의 .git/objects 디렉터리 아래에 파일 내용과 디렉터리 목록과 커밋을 객체로 쌓아 둔 곳입니다.
Git 의 객체는 내용에서 이름을 얻습니다. 내용을 해시 함수에 넣어 나온 값이 그 객체의 이름입니다. 그래서 내용이 같으면 이름도 같아 한 번만 저장됩니다. 이 방식을 내용 주소 지정이라고 부릅니다.
앞에서 본 객체 저장소와는 갈래가 다릅니다. 이름을 쓰는 사람이 짓지 않습니다. 네트워크 너머가 아니라 내 컴퓨터의 디렉터리에 있습니다.
관련 항목
객체 저장소를 이루는 구성 요소
오브젝트 · 버킷 · 키 · 메타데이터 · ETag · 접두사 · 객체 버전 관리 · 네임스페이스
객체 저장소와 맞세워지는 저장 방식
블록 스토리지 · 파일 스토리지 · 파일 시스템 · NFS · NAS · SAN · 블록 장치 · HDFS · 디스크
객체 저장소를 부르는 HTTP 요청과 호출 방식
HTTP · PUT · GET · DELETE · REST · 범위 요청 · 멀티파트 업로드 · 서명된 URL · S3 호환
객체 저장소가 데이터를 지키고 늘리는 기법
복제 · 이레이저 코딩 · 내구성 · 가용성 · 해시 함수 · 일관성 · 최종 일관성 · 강한 일관성
객체 저장소를 구현한 제품과 저장 등급
Amazon S3 · Google Cloud Storage · Azure Blob Storage · MinIO · Ceph · S3 Glacier · 스토리지 클래스
객체 저장소에 데이터를 쌓는 쓰임과 도구
데이터 레이크 · 데이터 웨어하우스 · 로그 수집기 · Fluent Bit · 백업 · CDN · 정적 웹사이트 · Parquet · Apache Spark
같은 이름을 쓰는 Git 내부 저장소의 원리
Git · 내용 주소 지정 · SHA-1 · 머클 트리
객체 저장소가 속하는 상위 분류
다른 이름: object storage · 오브젝트 스토리지 · 객체 스토리지