Zig에서 Redis 클론 구축—파트 4

발행: (2025년 12월 23일 오전 10:01 GMT+9)
8 min read
원문: Dev.to

Source: Dev.to

여정: 웹 개발에서 시스템 프로그래밍까지

저는 Zedis라는 Redis와 호환되는 인‑메모리 데이터베이스를 만들면서 많은 것을 배웠으며, 여러분도 직접 시도해 보길 권합니다. 저처럼 웹 개발 배경을 가지고 있다면 데이터베이스를 구축하는 것이 위협적으로 느껴질 수 있지만, 실제로는 엄청난 보람을 주며 여러분이 상상도 못했던 컴퓨팅 기본 개념들을 발견하게 될 것입니다.

Benchmark Overview

Zedis를 1 000 000 요청동시 클라이언트 50명을 사용해 벤치마크했습니다. 아래 플레임 그래프는 즉시 문제를 드러냈습니다:

Function% of Execution Time
Parse.parse75 %
Client.executeCommand13 %
Command.deinit8 %

Redis 클론이라면 파싱은 빨라야 하고 명령 실행이 주를 이루어야 합니다.

perf 로 더 깊이 파고들기

플레임 그래프는 시간이 어디에 사용되는지를 보여줄 뿐, 사용되는지는 알려주지 않습니다. 더 자세히 알아보기 위해 Linux의 perf 도구를 사용했습니다:

perf stat -e cache-references,cache-misses,\
L1-dcache-loads,L1-dcache-load-misses,\
dTLB-loads,dTLB-load-misses \
./zedis benchmark --clients 50 --requests 1000000

출력

Performance counter stats for ‘zedis’:
   20,718,078,128 cache-references
    1,162,705,808 cache-misses # 5.61% of all cache refs
   81,268,003,911 L1-dcache-loads
    8,589,113,560 L1-dcache-load-misses # 10.57% of all L1-dcache accesses
      520,613,776 dTLB-loads
       78,977,433 dTLB-load-misses # 15.17% of all dTLB cache accesses
     22.936441891 seconds time elapsed
     15.761103000 seconds user
     64.451493000 seconds sys

프로그램은 15.76 초를 사용자 시간에, 64.45 초를 시스템 시간에 소비했으며, 총 경과 시간 22.93 초 중 대부분을 차지합니다 – 이는 유용한 작업을 수행하기보다 네트워크 작업을 기다리는 I/O 바인드 상태임을 의미합니다.

파싱 병목 현상

Zig 0.14 → 0.15 로 마이그레이션하면서 리더 API가 변경되었습니다. 새로운 인터페이스에 익숙하지 않아 저는 한 번에 한 바이트씩 읽는 readSliceShort를 기본으로 사용했습니다:

var b_buf: [1]u8 = undefined;
const bytes_read = try reader.readSliceShort(&b_buf);

이는 성능에 치명적입니다: 바이트마다 시스템 콜과 함수 오버헤드가 발생합니다.

예시 Redis 프로토콜 메시지

*3\r\n
$3\r\nSET\r\n
$4\r\nkey1\r\n
$5\r\nvalue\r\n
  • *3 – 세 개의 bulk 문자열이 뒤따릅니다
  • $3 – 3바이트 문자열 (SET)
  • $4 – 4바이트 문자열 (key1)
  • $5 – 5바이트 문자열 (value)

버퍼링 읽기 – 해결책

바이트를 하나씩 읽는 대신, 버퍼를 한 번 할당하고 리더가 이를 사용하도록 합니다:

var reader_buffer: [1024 * 8]u8 = undefined;
var sr = self.connection.stream.reader(&reader_buffer);
const reader = sr.interface();

const line_with_crlf = reader.takeDelimiterInclusive('\n') catch |err| {
    if (err == error.ReadFailed) return error.EndOfStream;
    return err;
};

이제 파서는 큰 청크를 처리하므로 수천 번의 작은 읽기 대신 고성능 네트워크 서버가 작동해야 하는 방식에 맞게 동작합니다.

여전히 추가 최적화 여지가 있습니다(예: 라인 단위 처리를 하지 않고 버퍼에서 직접 파싱). 그러나 현재 접근 방식은 코드를 읽기 쉽게 유지합니다.

메모리 할당 오버헤드

처음에는 모든 할당에 std.heap.GeneralPurposeAllocator를 사용했습니다. 기본적으로 많은 안전 검사, 스택 트레이스 및 기록 기능이 활성화되어 있어 상당한 오버헤드가 발생합니다. 플레임 그래프에서는 할당기 내부의 뮤텍스 잠금에 많은 시간이 소비되는 것을 확인했습니다.

보다 가벼운 할당기로 전환하면 대부분의 문제가 해결됩니다:

// Example: using the page allocator
const allocator = std.heap.page_allocator;

멀티코어 환경에서는 std.heap.smp_allocator도 옵션이 됩니다.

배운 교훈

  • 시스템 프로그래밍은 호기심을 요구합니다 about the internals of libraries and runtimes.
  • 소스 코드를 읽는 것 (Zig, Redis, TigerBeetle, PostgreSQL) 은 귀중합니다.
  • 프로파일링 도구 (perf, flame graphs) 는 실제 병목 현상으로 안내합니다.
  • 버퍼링된 I/O경량 할당자는 고처리량 서버에 필수적입니다.

힘든 작업이지만, 보상은 막대합니다.

벤치마크 결과

사용된 커맨드‑라인 도구:

./redis-benchmark -t get,set -n 1000000 -c 50

SET

요청 수시간 (초)병렬 클라이언트페이로드처리량 (요청/초)평균 지연시간 (ms)
1 000 0004.25503 bytes235,294.120.115
1 000 0004.60503 bytes217,344.060.121

GET

요청 수시간 (초)병렬 클라이언트페이로드처리량 (요청/초)평균 지연시간 (ms)
1 000 0004.29503 bytes233,045.920.113
1 000 0004.53503 bytes220,799.300.119

지연 시간 요약 (ms) (첫 번째 SET/GET 실행에 대한)

SET:
  avg 0.115  min 0.056  p50 0.119  p95 0.127  p99 0.143  max 2.223

GET:
  avg 0.113  min 0.048  p50 0.119  p95 0.127  p99 0.135  max 0.487

Zedis는 현재 두 작업 모두에서 Redis보다 5–8 % 느립니다. 아직 원시 처리량에서 Redis를 능가하지는 않지만, 가장 최적화된 인‑메모리 스토어 중 하나와 십분의 일 수준의 차이 안에 있는 것은 학습 프로젝트로서 견고한 성과입니다.

마무리 생각

저는 Zedis의 현재 성능에 꽤 만족합니다. 이것이 프로젝트의 일시적인 종결일 수도 있습니다—당분간은요. 제가 시스템 프로그래밍을 계속 배우고 탐구하면서 앞으로의 업데이트를 기대해 주세요!

# Systems Programming and Database Internals

If you’re a web developer curious about systems programming, I can’t recommend this enough. Start small, make mistakes (you will—I made plenty!), profile them, fix them, and watch your understanding deepen. You don’t need to be a C wizard or have a CS degree—just curiosity and persistence.

Thanks for reading! Subscribe to follow my journey—I’m learning systems programming and sharing everything I discover along the way.

**Zedis Source Code**

---

**F2023 #25 – Potpourri:** Redis, CockroachDB, Snowflake, MangoDB, TabDB
Back to Blog

관련 글

더 보기 »