사전 bcache
구현체

bcache

gabury1고친 사람 github-actions[bot]

bcache 는 느린 하드디스크 앞에 빠른 저장 장치를 세워 디스크를 빠르게 만듭니다. 자주 읽는 데이터는 빠른 쪽에서 꺼내 주고 나머지는 하드디스크에 둡니다. 이 일은 리눅스 커널 안에서 돕니다. 그래서 그 위의 프로그램은 손댈 것이 없습니다.

쉽고 빠른 이해
  • 무슨 일을 하나 — 크고 느린 하드디스크와 작고 빠른 디스크를 묶어 디스크 한 장처럼 보이게 합니다. 큰 하드디스크 여러 개 앞에 빠른 디스크 하나를 세우는 식으로 씁니다.
  • 왜 있나 — 하드디스크는 싸고 큽니다. 대신 흩어진 데이터를 읽을 때 느립니다. 빠른 디스크는 같은 돈으로 살 수 있는 용량이 작습니다. 자주 읽는 일부만 빠른 쪽에 두면 둘의 장점을 함께 얻습니다.
  • 어떻게 도나
    1. 읽기 요청이 오면 빠른 디스크에 사본이 있는지 봅니다
    2. 있으면 거기서 내줍니다. 없으면 하드디스크에서 읽고 빠른 쪽에 사본을 남깁니다
    3. 길게 이어 읽는 요청은 하드디스크도 잘하므로 빠른 쪽을 건너뜁니다
  • 대가 — 쓰기까지 빠른 쪽이 먼저 받게 하면 쓰기가 빨라집니다. 대신 그 디스크가 고장 나면 하드디스크로 아직 안 내려간 데이터를 잃습니다. 그래서 이 방식은 처음에 꺼져 있습니다.
  • 언제 쓰나 — 자주 읽는 부분이 빠른 디스크에 들어올 만큼 작을 때 씁니다. 자주 읽는 부분이 빠른 디스크보다 크거나 데이터가 이미 전부 빠른 디스크에 있으면 얻을 것이 없습니다.

상세

bcache 는 리눅스 커널에 들어 있는 캐시입니다. 빠른 SSD(Solid State Drive, 반도체 저장 장치)를 느린 HDD(Hard Disk Drive, 하드디스크)의 캐시로 씁니다. 캐시는 원본보다 빠른 곳에 데이터 사본을 두고 다음 요청을 거기서 받는 장치입니다.

아래 소절은 여덟입니다. 앞의 둘은 밑바탕입니다. 두 디스크를 왜 섞는지, bcache 가 커널의 어느 계층에 끼어드는지를 봅니다.

가운데 넷은 돌아가는 모습입니다. 장치를 묶는 법, 읽기가 도는 순서, 캐시 모드 넷, 그중 가장 빠른 모드가 치르는 대가를 봅니다. 마지막 둘은 SSD 에 맞춘 저장 방식과 고르는 때입니다.

하드디스크와 SSD 를 섞는 까닭

HDD 는 도는 원판 위로 헤드가 움직이며 읽습니다. 흩어진 데이터를 읽으려면 헤드가 이리저리 옮겨 가야 합니다. 그동안은 기다립니다. 이렇게 여기저기 떨어진 곳을 차례없이 읽는 것을 랜덤 접근이라고 부릅니다. HDD 가 가장 느린 경우가 이것입니다.

반대로 이어 붙은 데이터를 앞에서부터 읽으면 헤드가 거의 안 움직입니다. 이것을 순차 접근이라고 부릅니다. HDD 도 순차 접근은 꽤 잘합니다.

SSD 는 움직이는 부품이 없어 랜덤 접근도 빠릅니다. 대신 같은 돈이면 HDD 보다 용량이 작습니다.

서버는 디스크 전체를 고르게 읽지 않습니다. 자주 찾는 일부를 되풀이해 읽습니다. 이 일부를 작업 집합이라고 부릅니다. 작업 집합만 SSD 에 두면 용량은 HDD 로, 속도는 SSD 로 얻습니다. bcache 가 하는 일이 이것입니다.

블록 계층에 끼어든다는 것

커널은 디스크를 블록 장치로 봅니다. 블록 장치는 정해진 크기의 덩어리, 곧 블록 단위로 읽고 쓰는 장치입니다. 디스크의 몇 번째 블록을 읽어라, 이런 요청만 받습니다.

파일 시스템은 파일 이름과 위치를 블록 번호로 바꿔 아래로 내려보냅니다. 이 블록 요청이 지나가는 커널의 계층을 블록 계층이라고 부릅니다. bcache 는 이 계층에 끼어듭니다. 블록 계층 캐시(block layer cache)라는 다른 이름도 여기서 왔습니다.

블록 계층에 있으니 bcache 는 파일을 모릅니다. 블록 번호만 봅니다. 그 덕에 어떤 파일 시스템이든 bcache 위에 올라갈 수 있습니다.

bcache 는 HDD 와 SSD 를 묶어 새 블록 장치 하나를 내놓습니다. 이름은 /dev/bcache0 꼴입니다. 파일 시스템은 이 장치 위에 만듭니다.

flowchart TD
    A["프로그램"] --> F["파일 시스템"]
    F -->|"블록 번호로 바꾼 요청"| B["/dev/bcache0 · bcache 가 내놓은 블록 장치"]
    subgraph L["블록 계층"]
        B --> C{"SSD 에 사본이 있나"}
    end
    C -->|"있다"| S["SSD"]
    C -->|"없다"| H["HDD"]

프로그램과 파일 시스템은 평범한 디스크 하나를 쓰는 줄 압니다. 어느 디스크에서 읽을지는 블록 계층 안에서 bcache 가 정합니다.

백킹 장치와 캐시 장치

bcache 는 두 장치에 이름을 따로 붙입니다. 원본을 끝까지 들고 있는 느린 장치가 백킹 장치입니다. 사본을 담는 빠른 장치가 캐시 장치입니다. 보통 백킹 장치는 HDD 입니다. 캐시 장치는 SSD 입니다.

캐시 장치는 캐시 셋이라는 묶음에 들어갑니다. 캐시 셋은 캐시 장치를 담는 그릇입니다. 보통은 SSD 하나가 캐시 셋 하나를 이룹니다. 백킹 장치는 캐시 장치가 아니라 이 캐시 셋에 붙습니다.

캐시 셋 하나에 백킹 장치 여럿이 붙을 수 있습니다. SSD 하나로 HDD 여럿을 받치는 구성이 이렇게 나옵니다.

flowchart TD
    subgraph CS["캐시 셋"]
        S["캐시 장치 · SSD"]
    end
    H1["백킹 장치 · HDD 하나"] --> D1["/dev/bcache0"]
    H2["백킹 장치 · HDD 둘"] --> D2["/dev/bcache1"]
    S --> D1
    S --> D2

백킹 장치마다 /dev/bcache 뒤에 번호가 붙은 장치가 하나씩 생깁니다. SSD 는 두 장치가 나눠 씁니다.

쓰기 전에 두 장치를 bcache 용으로 포맷합니다. 장치 앞머리에 bcache 가 알아볼 표시를 적는 일입니다. 아래 두 줄이 그 포맷입니다.

터미널
bcache make -B /dev/sdb   # 느린 쪽
bcache make -C /dev/sdc   # 빠른 쪽

-B 는 백킹 장치로, -C 는 캐시 장치로 포맷하라는 뜻입니다. 이다음 백킹 장치를 캐시 셋에 붙입니다.

붙이는 일은 sysfs 로 합니다. sysfs 는 커널이 설정과 상태를 /sys 아래 파일 모양으로 내놓는 통로입니다. 그 파일에 값을 쓰면 커널 설정이 바뀝니다.

캐시 셋 번호는 캐시 장치를 포맷할 때 정해지는 고유 번호입니다. 포맷하고 나면 /sys/fs/bcache/ 아래에 이 번호를 이름으로 단 폴더가 생겨 거기서 읽을 수 있습니다. 아래 줄은 이 번호를 백킹 장치의 attach 파일에 써서 둘을 잇습니다.

터미널
echo <캐시 셋 번호> > /sys/block/bcache0/bcache/attach

읽기가 도는 순서

읽기 요청이 오면 bcache 는 먼저 SSD 에 그 블록의 사본이 있는지 봅니다. 있으면 캐시 적중입니다. SSD 에서 바로 내줍니다.

없으면 캐시 미스입니다. HDD 에서 읽어 내줍니다. 읽은 블록은 SSD 에도 한 벌 남깁니다. 다음에 같은 블록을 찾으면 적중이 됩니다.

순차 읽기는 따로 다룹니다. bcache 는 요청이 이어 붙은 블록을 길게 읽는지 지켜봅니다. 일정 길이를 넘으면 그 요청은 SSD 를 건너뛰고 HDD 로 곧장 갑니다. 이 길이는 sequential_cutoff 설정으로 정합니다.

건너뛰는 까닭은 둘입니다. HDD 도 순차 읽기는 잘하므로 SSD 가 벌어 줄 시간이 적습니다. 그리고 큰 파일을 SSD 에 실으면 작은 SSD 가 금세 차서 자주 읽던 블록이 밀려납니다. 백업이나 큰 파일 복사가 그런 요청입니다.

flowchart TD
    R["읽기 요청"] --> Q{"길게 이어 읽는 요청인가"}
    Q -->|"그렇다"| H["HDD 에서 곧장 읽는다"]
    Q -->|"아니다"| C{"SSD 에 사본이 있나"}
    C -->|"있다 · 적중"| S["SSD 에서 내준다"]
    C -->|"없다 · 미스"| M["HDD 에서 읽고 SSD 에 사본을 남긴다"]

캐시 모드 넷

캐시 모드는 백킹 장치가 SSD 를 어떻게 쓸지 정합니다. 백킹 장치마다 따로 고릅니다. 앞의 셋은 읽기를 똑같이 SSD 에 싣고 쓰기를 받는 법만 다릅니다. 마지막 none 은 읽기까지 SSD 를 안 씁니다.

모드 쓰기가 가는 곳 완료로 치는 때 읽은 블록을 SSD 에 싣나
writethrough SSD 와 HDD 둘 다 HDD 까지 썼을 때 ✓
writeback SSD 먼저, HDD 는 나중에 SSD 에 썼을 때 ✓
writearound HDD 만 HDD 에 썼을 때 ✓
none HDD 만 HDD 에 썼을 때 ✗

writethrough 가 처음 켤 때의 모드입니다. 쓰기는 HDD 속도로 끝납니다. 대신 SSD 가 고장 나도 잃는 데이터가 없습니다. 캐시 일반에서 이 방식을 write-through라고 부릅니다.

writeback 은 쓰기를 SSD 가 먼저 받고 곧장 완료로 돌려줍니다. 쓰기가 SSD 속도로 끝납니다. 캐시 일반에서는 write-behind라고도 부르는 방식입니다. 이 모드는 처음에 꺼져 있습니다.

writearound 는 쓰기를 SSD 에 싣지 않습니다. 한 번 쓰고 다시 안 읽을 데이터가 SSD 를 차지하지 않게 합니다. 캐시 일반에서 write-around라고 부르는 방식입니다. 읽기는 여전히 SSD 에 싣습니다.

none 은 쓰기도 읽기도 SSD 를 거치지 않습니다. 모든 요청이 HDD 로 곧장 갑니다.

모드는 켜 둔 채로 바꿀 수 있습니다. 백킹 장치의 cache_mode 파일에 모드 이름을 쓰면 됩니다.

터미널
echo writeback > /sys/block/bcache0/bcache/cache_mode

writeback 의 대가

writeback 모드에서는 SSD 에만 있고 HDD 에는 아직 없는 블록이 생깁니다. 이런 데이터를 dirty 데이터라고 부릅니다. HDD 쪽 사본이 낡았다는 뜻입니다.

bcache 는 dirty 데이터를 모아 두었다가 나중에 HDD 로 내려보냅니다. 내려보낼 때는 블록 번호 순서로 훑으며 씁니다. 여기저기 흩어져 들어온 쓰기가 HDD 에서는 순차 쓰기가 됩니다.

stateDiagram-v2
    state "깨끗함 · SSD 와 HDD 가 같다" as C
    state "dirty · SSD 에만 새 값이 있다" as D
    state "잃음 · 새 값이 SSD 와 함께 사라진다" as L
    [*] --> C
    C --> D: writeback 모드로 쓴다
    D --> C: HDD 로 내려보낸다
    D --> L: 내려보내기 전에 SSD 가 고장 난다

대가는 SSD 가 고장 날 때 드러납니다. dirty 데이터는 SSD 에만 있으므로 함께 사라집니다. HDD 에는 일부만 반영된 낡은 블록이 남습니다. 그 위의 파일 시스템은 깨진 채로 남을 수 있습니다.

이 때문에 bcache 는 dirty 데이터가 걸린 백킹 장치를 캐시 없이 띄우지 않습니다. 캐시 장치가 나타날 때까지 /dev/bcache0 을 만들지 않습니다. 사용자가 억지로 띄울 수는 있습니다. 그러면 낡은 데이터를 보게 됩니다.

반면 전원이 갑자기 나가도 데이터는 남습니다. bcache 는 쓰기가 SSD 에 실제로 적힌 뒤에야 완료로 돌려줍니다. SSD 에 적힌 쓰기는 다시 켜도 남아 있습니다. 끄기 전에 따로 밟을 정리 절차가 없는 것도 이 덕분입니다.

캐시 장치를 떼어 낼 때도 이 대가가 보입니다. 백킹 장치의 detach 파일에 쓰면 캐시 셋에서 뗍니다. dirty 데이터가 남아 있으면 먼저 HDD 로 다 내려보낸 뒤 뗍니다.

SSD 에 맞춘 저장 방식

SSD 는 이미 쓴 칸을 제자리에서 고쳐 쓰지 못합니다. 큰 덩어리를 한꺼번에 지운 뒤에야 그 안에 다시 씁니다. 이 지우는 단위를 소거 블록(erase block)이라고 부릅니다.

작은 조각을 여기저기 고쳐 쓰면 SSD 안에서 데이터를 옮기고 지우는 일이 늘어납니다. 그만큼 느려지고 빨리 닳습니다.

bcache 는 캐시 공간을 소거 블록 크기의 버킷으로 나눕니다. 공간은 버킷 단위로만 받아 씁니다. SSD 가 한꺼번에 지우는 단위와 bcache 가 쓰고 비우는 단위가 맞아떨어집니다.

어떤 블록의 사본이 SSD 어디에 있는지는 색인이 기억합니다. 색인은 HDD 의 블록 번호를 SSD 안의 위치로 바꿔 주는 표입니다. 읽기가 올 때마다 bcache 는 이 색인을 찾아 적중인지 가립니다.

bcache 는 이 색인을 B+tree로 둡니다. B+tree 는 키를 정렬해 두고 빨리 찾게 하는 트리입니다. 여기서 키는 블록 번호입니다.

색인을 고칠 때는 트리의 칸을 제자리에서 고쳐 쓰지 않습니다. 바뀐 내용을 칸 뒤에 이어 붙입니다. 이렇게 뒤에 이어 붙이기만 하는 기록을 로그라고 합니다. 두 방식을 섞은 덕에 색인을 고치는 일도 SSD 가 못하는 제자리 고쳐 쓰기를 피합니다.

고르는 때와 피하는 때

bcache 가 맞는 때는 데이터가 SSD 에 다 안 들어갈 만큼 클 때입니다. 그러면서도 작업 집합은 SSD 안에 들어와야 합니다. 가상 머신 디스크 여럿을 HDD 에 두고 돌리는 서버가 그런 예입니다. 각 가상 머신이 자주 읽는 부분만 SSD 에 올라옵니다.

작업 집합이 SSD 보다 크면 적중이 드뭅니다. 미스마다 HDD 에서 읽고 SSD 에 옮겨 싣는 일만 늘어납니다. 데이터 대부분을 길게 이어 읽는 서버도 얻을 것이 적습니다. 그런 읽기는 어차피 SSD 를 건너뜁니다.

데이터가 이미 전부 SSD 에 있으면 bcache 를 둘 까닭이 없습니다. 앞에 세울 더 빠른 장치가 없기 때문입니다.

같은 일을 하는 다른 수단도 있습니다. 디바이스 매퍼는 블록 장치를 겹치거나 이어 붙여 새 블록 장치를 만드는 커널 틀입니다. 그 위에서 도는 dm-cache 가 bcache 와 같은 일을 합니다. ZFS(Zettabyte File System, 제타바이트 파일 시스템) 처럼 파일 시스템이 스스로 SSD 캐시를 두는 방식도 있습니다. bcache 는 블록 계층에 끼어들므로 파일 시스템을 고르지 않는다는 점이 다릅니다.

관련 항목

bcache 가 끼어드는 리눅스 커널 계층

커널 · 블록 계층 · 블록 장치 · 장치 파일 · sysfs · 파일 시스템 · 입출력 스케줄러

bcache 가 묶는 저장 장치

SSD · HDD · NVMe · 디스크 · RAID · 소거 블록

bcache 가 고르는 쓰기 방식

write-through · write-behind · write-around · passthrough · dirty 데이터 · flush

bcache 가 캐시에 실을지 가르는 기준

캐시 적중 · 캐시 미스 · 적중률 · 작업 집합 · 순차 접근 · 랜덤 접근 · 캐싱

bcache 안에서 캐시를 관리하는 구조

버킷 · B+tree · 로그 · 익스텐트 · 슈퍼블록 · 메타데이터

bcache 대신 쓸 수 있는 캐시 수단

dm-cache · lvmcache · 디바이스 매퍼 · LVM · ZFS · L2ARC

bcache 와 헷갈리는 이웃

bcachefs · 페이지 캐시 · 블록 캐시

다른 이름: block layer cache · 블록 계층 캐시