Zig에서 Redis 클론 구축—파트 4
Source: Dev.to
여정: 웹 개발에서 시스템 프로그래밍까지
저는 Zedis라는 Redis와 호환되는 인‑메모리 데이터베이스를 만들면서 많은 것을 배웠으며, 여러분도 직접 시도해 보길 권합니다. 저처럼 웹 개발 배경을 가지고 있다면 데이터베이스를 구축하는 것이 위협적으로 느껴질 수 있지만, 실제로는 엄청난 보람을 주며 여러분이 상상도 못했던 컴퓨팅 기본 개념들을 발견하게 될 것입니다.
Benchmark Overview
Zedis를 1 000 000 요청에 동시 클라이언트 50명을 사용해 벤치마크했습니다. 아래 플레임 그래프는 즉시 문제를 드러냈습니다:
| Function | % of Execution Time |
|---|---|
Parse.parse | 75 % |
Client.executeCommand | 13 % |
Command.deinit | 8 % |
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 000 | 4.25 | 50 | 3 bytes | 235,294.12 | 0.115 |
| 1 000 000 | 4.60 | 50 | 3 bytes | 217,344.06 | 0.121 |
GET
| 요청 수 | 시간 (초) | 병렬 클라이언트 | 페이로드 | 처리량 (요청/초) | 평균 지연시간 (ms) |
|---|---|---|---|---|---|
| 1 000 000 | 4.29 | 50 | 3 bytes | 233,045.92 | 0.113 |
| 1 000 000 | 4.53 | 50 | 3 bytes | 220,799.30 | 0.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