Bleeding edge

저장장치가 빨라질수록, 소프트웨어는 왜 더 영리해져야 할까 본문

CS

저장장치가 빨라질수록, 소프트웨어는 왜 더 영리해져야 할까

codevil 2026. 8. 9. 23:08

컴퓨터 성능 이야기를 하다 보면 저장장치는 보통 이렇게 구분한다.

HDD는 느리고, SSD는 빠르고, NVMe는 더 빠르다.

그래서 자연스럽게 HDD → SSD → NVMe를 단순한 속도 발전으로 생각하기 쉽다.

하지만 실제로는 조금 다르다.

세 저장장치는 단순히 속도만 다른 것이 아니라, 데이터를 읽고 쓰는 방식 자체가 다르다.

그리고 이 차이를 알고 나면 Kafka 같은 고성능 시스템이 왜 지금과 같은 구조를 선택했는지도 꽤 재미있게 이해할 수 있다.


HDD는 데이터를 읽기 위해 실제로 움직인다

HDD는 우리가 생각하는 것보다 훨씬 기계적인 장치다.

안에는 회전하는 원판인 Platter가 있고, 데이터를 읽고 쓰는 Head가 있다.

어떤 데이터를 읽으려면 먼저 Head가 데이터가 있는 위치까지 이동해야 한다.

그리고 원하는 데이터가 Head 아래로 돌아올 때까지 원판의 회전을 기다려야 한다.

즉 HDD에서는 데이터를 한 번 읽는 과정에도 실제 물리적인 움직임이 들어간다.

이 때문에 HDD에서는 데이터의 위치가 굉장히 중요하다.

예를 들어 A, B, C, D, E 데이터가 순서대로 붙어 있다고 해보자.

A → B → C → D → E

이렇게 읽는다면 Head를 크게 움직일 필요가 없다.

이런 접근을 Sequential I/O, 즉 순차 I/O라고 한다.

반대로 다음처럼 읽는다면 어떨까?

A → E → B → D → C

Head가 계속 다른 위치로 움직여야 한다.

이런 접근이 Random I/O다.

HDD에서는 Sequential I/O와 Random I/O의 성능 차이가 매우 크다.

그래서 과거의 파일 시스템이나 데이터베이스는 가능하면 데이터를 연속적으로 읽고 쓰도록 설계하는 것이 중요했다.

그리고 이 특징은 나중에 Kafka의 구조와도 연결된다.


SSD는 무엇이 달라졌을까

SSD에서는 가장 먼저 기계적인 움직임이 사라졌다.

HDD처럼 원판이 돌아가지도 않고, Head가 움직이지도 않는다.

대신 NAND Flash Memory라는 반도체에 데이터를 저장한다.

그래서 Random Access 성능이 HDD보다 압도적으로 좋아졌다.

그렇다고 SSD가 단순히 "움직이지 않는 HDD"인 것은 아니다.

SSD에는 SSD만의 복잡한 문제가 있다.

NAND Flash는 대략 다음과 같은 특성을 가진다.

  • 읽기는 Page 단위
  • 쓰기도 Page 단위
  • 삭제는 더 큰 Block 단위

특히 이미 기록된 위치에 데이터를 HDD처럼 자유롭게 덮어쓰기 어렵다.

그래서 SSD 내부에는 Controller가 존재한다.

운영체제가 특정 주소에 데이터를 써 달라고 요청하더라도 실제 NAND Flash의 어느 위치에 기록할지는 SSD Controller가 결정한다.

이 과정에서 사용하는 것이 FTL, Flash Translation Layer다.

즉 운영체제가 보는 논리적인 주소와 실제 NAND Flash의 물리적인 위치 사이에 한 단계의 변환이 존재한다.

SSD Controller는 이 외에도 많은 작업을 한다.

  • Wear Leveling
  • Garbage Collection
  • TRIM 처리
  • Bad Block 관리

그래서 SSD는 단순한 저장장치라기보다, NAND Flash를 관리하는 작은 컴퓨터가 내부에 들어 있는 장치라고 생각하는 편이 이해하기 쉽다.


SATA SSD와 NVMe SSD는 NAND가 다른 걸까

여기서 많이 헷갈리는 부분이 하나 있다.

NVMe는 Flash Memory의 종류가 아니다.

SATA SSD도 NAND Flash를 사용할 수 있고, NVMe SSD도 NAND Flash를 사용할 수 있다.

둘의 중요한 차이는 저장 매체가 아니라 컴퓨터와 SSD가 통신하는 방법이다.

SATA SSD는 SATA 인터페이스와 AHCI라는 방식을 주로 사용한다.

문제는 AHCI가 원래 HDD를 중심으로 설계된 기술이라는 점이다.

저장장치는 SSD가 되면서 엄청나게 빨라졌는데, SSD와 컴퓨터가 통신하는 방식에는 과거 HDD 시대의 흔적이 남아 있었던 것이다.

비유하면 이런 느낌이다.

자동차는 스포츠카로 바뀌었는데 도로는 예전 그대로인 상황이다.


NVMe는 SSD를 위해 도로부터 다시 만들었다

NVMe는 Non-Volatile Memory Express의 약자다.

Flash 기반의 빠른 저장장치를 제대로 활용하기 위해 설계되었다.

NVMe SSD는 PCIe를 통해 CPU와 훨씬 직접적으로 통신할 수 있다.

하지만 NVMe의 장점은 단순히 최대 읽기 속도가 높다는 것만은 아니다.

더 중요한 특징 중 하나는 많은 I/O 요청을 동시에 처리하기 좋은 구조라는 것이다.

현대 CPU에는 여러 개의 Core가 있다.

NVMe에서는 여러 CPU Core가 각각 Queue를 사용해 동시에 I/O 요청을 전달할 수 있다.

그래서 서버나 데이터 처리 시스템에서는 단순히 "초당 몇 GB를 읽을 수 있는가?"만큼 다음과 같은 것들이 중요해진다.

  • IOPS
  • Latency
  • 동시에 처리할 수 있는 I/O 요청 수

대략적인 특성을 비교하면 다음과 같다.

구분HDDSATA SSDNVMe SSD

물리적인 움직임 있음 없음 없음
Random I/O 매우 느림 빠름 매우 빠름
Sequential I/O 괜찮음 빠름 매우 빠름
병렬 I/O 약함 중간 강함
Latency 높음 낮음 매우 낮음

물론 실제 성능은 제품과 사용 환경에 따라 크게 달라질 수 있다.

하지만 중요한 것은 속도 숫자가 아니라 각 저장장치가 데이터를 처리하는 방식이 서로 다르다는 점이다.


저장장치는 빨라졌는데 새로운 문제가 생겼다

HDD 시대에는 가장 느린 것이 명확했다.

바로 디스크였다.

HDD 접근에는 ms 단위의 시간이 걸릴 수 있기 때문에 CPU에서 발생하는 작은 비용들은 상대적으로 크게 중요하지 않았다.

그런데 SSD와 NVMe가 등장하면서 상황이 조금 달라졌다.

저장장치 자체의 Latency가 크게 줄어들자 그동안 잘 보이지 않던 비용들이 눈에 들어오기 시작한 것이다.

예를 들면 다음과 같다.

  • System Call
  • Context Switch
  • Memory Copy
  • Lock
  • Serialization
  • Network Stack
  • CPU Scheduling

저장장치가 느릴 때는 디스크 하나가 압도적인 병목이었다.

저장장치가 빨라진 뒤에는 이런 작은 비용들이 모여 전체 시스템 성능을 결정하게 된다.

그리고 바로 이 지점에서 Kafka의 구조를 보면 꽤 재미있다.


Kafka는 저장장치와 싸우지 않는다

Kafka는 데이터를 디스크에 저장한다.

그런데 저장장치가 싫어하는 방식으로 데이터를 저장하지 않는다.

Kafka의 로그는 기본적으로 기존 데이터를 계속 수정하는 것보다 로그의 끝에 새로운 데이터를 추가하는 Append 방식을 사용한다.

예를 들어 데이터가 다음과 같이 있다면,

1 → 2 → 3 → 4 → 5 → 6 → ...

새로운 데이터는 대부분 맨 뒤에 붙는다.

이 방식은 Sequential Write에 굉장히 친화적이다.

특히 Random I/O가 느렸던 HDD에서는 굉장히 중요한 특징이었다.

SSD 시대가 되었다고 해서 Sequential Access의 장점이 사라지는 것도 아니다.

연속적인 I/O는 여전히 저장장치와 운영체제가 효율적으로 처리하기 좋은 패턴이다.


작은 데이터를 계속 쓰는 것도 비용이다

Kafka가 사용하는 또 하나의 중요한 방법은 Batching이다.

예를 들어 1KB짜리 데이터를 천 번 처리하는 것과, 데이터를 어느 정도 모아서 1MB 정도의 큰 덩어리로 처리하는 것은 총 데이터의 크기가 비슷하더라도 시스템에 주는 부담이 다르다.

작은 작업을 반복하면 그때마다 여러 고정 비용이 발생할 수 있다.

  • System Call
  • Queue 관리
  • Network 처리
  • 각종 Bookkeeping

그래서 Kafka는 작은 메시지를 어느 정도 묶어서 처리한다.

즉,

A
B
C
D
E

를 각각 따로 처리하기보다,

A + B + C + D + E

처럼 모아서 처리하는 것이다.

이렇게 하면 저장장치뿐 아니라 네트워크와 CPU 측면에서도 효율이 좋아진다.


파일에 쓴다고 해서 항상 디스크까지 기다리는 것은 아니다

여기서 또 하나 중요한 것이 운영체제의 Page Cache다.

애플리케이션에서 파일에 데이터를 쓴다고 해서 모든 경우에 그 순간 HDD나 SSD까지 데이터를 기록한 뒤 기다리는 것은 아니다.

Linux를 비롯한 운영체제는 RAM의 일부를 Page Cache로 사용한다.

단순화하면 흐름은 다음과 같다.

Kafka → Linux Page Cache(RAM) → HDD / SSD

Kafka가 데이터를 기록하면 우선 RAM에 있는 Page Cache를 거칠 수 있다.

실제 물리적인 저장장치에 데이터를 내려보내는 작업은 운영체제가 적절한 시점에 수행한다.

그래서 Kafka가 "디스크 기반 시스템"이라는 말과 "메모리를 적극적으로 사용한다"는 말은 서로 모순되지 않는다.


방금 들어온 데이터를 바로 읽으면 어떻게 될까

Producer가 Kafka에 데이터를 넣었다고 해보자.

데이터가 Page Cache에 들어간 상태에서 Consumer가 거의 즉시 같은 데이터를 요청한다면 어떻게 될까?

데이터가 아직 RAM에 남아 있다면 굳이 SSD나 HDD까지 다시 내려가서 읽어올 필요가 없다.

실제 흐름은 상당 부분 다음과 비슷해질 수 있다.

Producer → Kafka → Page Cache → Consumer

영속성은 디스크가 담당하지만, 최신 데이터를 처리하는 Hot Path에서는 RAM이 적극적으로 활용되는 것이다.

그래서 Kafka의 특징을 단순히

"디스크인데도 메모리처럼 빠르다"

라고 표현하면 조금 과장될 수 있다.

좀 더 정확하게 표현한다면,

Kafka는 느린 디스크 작업이 실제 요청 경로에 최대한 나타나지 않도록 설계되어 있다.

에 가깝다.


데이터 복사도 공짜가 아니다

저장장치가 빨라질수록 또 하나 중요해지는 것이 Memory Copy다.

파일에 있는 데이터를 네트워크로 보낼 때 일반적인 구조에서는 데이터가 여러 영역을 거칠 수 있다.

예를 들면,

Disk → Kernel Page Cache → Application Memory → Socket → Network

같은 경로다.

여기에서 데이터를 Application 영역으로 복사했다가 다시 Kernel 쪽으로 전달하는 과정 자체에도 CPU와 메모리 대역폭이 사용된다.

Kafka는 가능한 경우 sendfile() 같은 방식을 활용해 이런 불필요한 복사를 줄인다.

이를 흔히 Zero-copy라고 부른다.

Zero-copy라고 해서 데이터 복사가 물리적으로 완전히 0번 발생한다는 의미로 받아들이기보다는,

애플리케이션 영역을 오가는 불필요한 데이터 복사를 줄이는 방식

정도로 이해하면 좋다.


NVMe가 빨라지면 이런 최적화는 필요 없어지지 않을까

여기서 한 가지 재미있는 질문이 생긴다.

NVMe가 이렇게 빠르다면 Sequential I/O, Batching, Zero-copy 같은 최적화가 예전만큼 중요하지 않은 것 아닐까?

오히려 반대로 볼 수도 있다.

저장장치가 느릴 때는 병목이 명확했다.

디스크가 너무 느렸기 때문이다.

하지만 저장장치가 빨라지면서 다른 비용의 비중이 상대적으로 커진다.

NVMe가 데이터를 매우 빠르게 가져오더라도 그 데이터를 처리하는 과정에서

  • 여러 번 복사하고
  • 여러 번 System Call을 호출하고
  • Lock을 잡고
  • Serialization을 하고
  • Network Stack을 지나간다면

저장장치 이외의 부분이 전체 성능을 제한할 수 있다.

즉 저장장치가 빨라질수록 시스템 전체를 빠르게 만들기 위해서는 오히려 데이터가 이동하는 전체 경로를 살펴봐야 한다.


HDD, SSD, NVMe 그리고 Kafka를 함께 보면

처음에는 전혀 다른 이야기처럼 보인다.

HDD는 회전판의 이야기이고,

SSD는 NAND Flash의 이야기이며,

NVMe는 PCIe와 Queue에 대한 이야기다.

Kafka는 메시징 시스템이다.

하지만 이들을 같이 보면 하나의 공통점이 보인다.

HDD에서는 Sequential Access를 활용하는 것이 중요했다.

SSD에서는 NAND Flash의 특성을 Controller가 관리하기 시작했다.

NVMe에서는 저장장치의 병렬성을 더 적극적으로 활용하게 되었다.

그리고 Kafka는 그 위에서

  • Sequential Append
  • Batching
  • OS Page Cache
  • Zero-copy
  • Partition 기반 병렬화

같은 방식을 조합한다.

결국 좋은 시스템은 하드웨어가 가진 특성을 무시하지 않는다.

오히려 하드웨어가 잘하는 일을 최대한 활용하고, 잘하지 못하는 작업을 피하는 방향으로 설계된다.


빠른 시스템은 무엇을 더 빠르게 했을까

성능이 좋은 시스템을 보면 보통 이런 질문부터 하게 된다.

"무엇을 사용했길래 이렇게 빠른 걸까?"

더 빠른 SSD를 사용했나?

더 좋은 CPU를 사용했나?

메모리를 많이 사용했나?

물론 그것들도 중요하다.

하지만 Kafka 같은 시스템을 자세히 보면 조금 다른 답이 보인다.

빠른 시스템은 항상 어떤 작업을 엄청나게 빠르게 만든 것이 아니다.

오히려 많은 경우,

느린 작업을 하지 않는 방법을 찾아낸 시스템이다.

Random Write 대신 Sequential Append를 사용하고,

작은 요청을 계속 보내는 대신 Batching을 사용하고,

이미 운영체제가 잘하는 Cache는 운영체제에 맡기고,

불필요한 Memory Copy를 줄인다.

그래서 성능 문제를 만났을 때 이런 질문을 던져보는 것도 꽤 유용하다.

"어떻게 하면 이것을 더 빨리 할 수 있을까?"

보다,

"지금 컴퓨터에게 불필요하게 시키고 있는 일이 무엇일까?"

HDD에서 NVMe까지 저장장치는 크게 변했지만, 이 질문은 여전히 유효하다.