In-Memory DB 구현 8편 - TTL과 만료 전략, 최소힙으로 데이터 수명 관리하기

들어가며 지금까지 구현한 데이터베이스에서 저장된 모든 데이터는 명시적으로 삭제하지 않는 한 영구적으로 존재한다. 하지만 세션 토큰, 캐시 데이터, 인증 코드 같은 것들은 일정 시간이 지나면 자동으로 사라져야 한다. 이번 포스팅에서는 키에 만료 시간(TTL)을 설정하고 만료된 키를 자동으로 삭제하는 두 가지 전략을 구현한다. 그 과정에서 최소 힙 자료구조를 직접 구현하고 백그라운드 고루틴의 라이프사이클도 관리해보는 과정을 다뤄보겠다. TTL TTL(Time To Live)은 데이터가 유효한 시간을 의미한다. 네트워크 패킷에서는 홉 수를 제한하고 DNS 캐시에서는 레코드의 유효 기간을 정하고, CDN에서는 캐시 갱신 주기를 결정한다. 컴퓨터 시스템 전반에서 “이 데이터가 얼마나 오래 살아야 하는가"를 제어하는 수단으로 널리 쓰인다. ...

2026년 2월 19일 PM08:28 · PolarBear

In-Memory DB 구현 7편 - 이중 연결 리스트로 List 명령어 구현하기

들어가며 이번 포스팅에서는 이중 연결 리스트를 직접 구현하고, 저장소를 다중 타입으로 확장하여 LPUSH, RPUSH, LPOP, RPOP, LRANGE 명령어를 추가한다. 배열과 연결 리스트의 근본적인 차이부터 시작해서 왜 이중 연결 리스트가 이 상황에 적합한지, 그리고 포인터로 노드 간 연결을 어떻게 구현하는지를 다룬다. 배열과 연결 리스트 데이터를 순차적으로 저장하는 가장 기본적인 두 자료구조가 배열과 연결 리스트이다. 이 둘의 핵심 차이는 메모리 배치에 있다. 배열은 연속된 메모리 공간에 요소를 나란히 저장한다. 컴파일러가 시작 주소 + (인덱스 × 요소 크기)로 바로 계산할 수 있기 때문에 인덱스 접근이 O(1)이다. 하지만 앞쪽에 요소를 삽입하거나 삭제하면 나머지 요소를 전부 한 칸씩 밀거나 당겨야 하므로 O(n)이 된다. go의 슬라이스([]string)가 내부적으로 배열 기반이다. ...

2026년 2월 11일 PM07:50 · PolarBear

In-Memory DB 구현 6편 - Race Condition과 뮤텍스로 동시성 안전한 저장소 만들기

들어가며 이전 포스팅에서 go 키워드 하나로 서버를 동시성 서버로 바꿨다. 여러 클라이언트가 동시에 접속해서 PING을 보내면 모두 PONG을 정상적으로 받는다. 다수의 클라이언트가 동시에 연결하는 문제는 해결되었다. 하지만 PING은 저장소에 접근하지 않고 서버에서 특정 응답만 한다. 클라이언트들이 서버에 연결하는 순간 서로에게 공유되는 자원인 map 저장소에 동시에 SET과 GET을 수행하는 순간 현재 구조에서는 경쟁 상태가 발생한다. 이번 포스팅에서는 이 문제의 본질인 Race Condition을 이론적으로 파고들고, 세마포어부터 뮤텍스까지 동기화 기법의 계보를 살펴본 뒤 RWMutex로 저장소를 보호하는 과정을 알아보자. ...

2026년 2월 9일 PM06:46 · PolarBear

In-Memory DB 구현 5편 - 고루틴으로 동시성 서버 만들기(프로세스/스레드/컨텍스트스위칭/GMP)

들어가며 지금까지 만든 서버는 RESP 프로토콜을 파싱하고, SET/GET 명령어로 데이터를 저장하고 조회할 수 있다. 기능적으로는 꽤 그럴듯하지만 치명적인 한계가 하나 있다. 현재의 코드 구조로는 클라이언트 A가 연결 중이면, 클라이언트 B는 A의 처리가 끝날 때까지 기다려야 한다. 1편에서 살펴본 Accept Queue에 연결이 쌓이기만 할 뿐 서버가 꺼내가지 못하는 상황이다. 실제 데이터베이스에서 이런 구조는 쓸 수 없다. 이번 포스팅에서는 이 문제를 해결하기 위해 동시성의 핵심 개념을 살펴보고 go의 고루틴이 왜 경량 스레드라 불리는지 GMP 스케줄러 모델까지 파고든 뒤 코드 한 줄로 서버를 동시성 서버로 탈바꿈시키는 과정을 다루겠다. ...

2026년 2월 7일 PM05:43 · PolarBear

In-Memory DB 구현 4편 - 저장소 구현 (해시 테이블/해시충돌/리팩터/리해싱)

들어가며 이전 포스팅에서 RESP 프로토콜을 구현해 클라이언트와 서버가 구조화된 형식으로 통신할 수 있게 되었다. 이번 포스팅에서는 Key-Value 저장소를 구현하고 SET/GET 명령어를 추가해보자. go의 map을 감싸는 단순한 구조체를 만드는 것이 전부지만, 그 뒤에 숨어있는 해시 테이블의 원리를 깊이 있게 다뤄보려 한다. 해시 함수가 갖춰야 할 조건, 충돌을 해결하는 전략, 로드 팩터와 리해싱까지 살펴보면 map[string]string 한 줄이 왜 O(1)인지 납득할 수 있을 것이다. Key-Value 저장 모델 가장 단순한 데이터 모델 Key-Value 모델은 데이터를 저장하는 가장 단순한 방식이다. key로 값을 저장하고 같은 키로 값을 조회한다. ...

2026년 2월 4일 PM04:58 · PolarBear

In-Memory DB 구현 3편 - RESP 프로토콜

들어가며 이전 포스팅에서 TCP 스트림을 파싱해 PING/ECHO 명령어를 처리하는 서버를 만들었다. 하지만 PING\r\n 같은 단순한 텍스트 형식에는 몇 가지 한계가 있다. 데이터 안에 \r\n이 포함되면 구분자로 오인한다 문자열인지, 숫자인지, 에러인지 타입을 알 수 없다 악의적인 클라이언트가 무한히 긴 데이터를 보낼 수 있다 Redis는 이런 문제를 해결하기 위해 Redis Serialization Protocol 이라는 자체 프로토콜을 사용한다. 이번 포스팅에서는 RESP 프로토콜의 구조를 이해하고 직접 파서를 구현해본다. 단순히 어떻게 파싱하는지를 넘어서 프로토콜이 왜 이런 형태를 갖게 되었는지, 직렬화 포맷 사이의 트레이드오프는 무엇이고 바이너리 세이프라는 개념이 실제로 어떤 문제를 해결하는지까지 깊이 들여다본다. ...

2026년 2월 2일 PM04:19 · PolarBear

In-Memory DB 구현 2편 - TCP 스트림 파싱하기 (TCP 스트림/시스템콜/버퍼링)

들어가며 이전 포스팅에서 TCP 서버를 만들어 클라이언트 연결을 받고 +PONG\r\n을 응답하는 데까지 성공했다. 하지만 실제 데이터베이스라면 클라이언트가 보낸 명령어를 읽고 해석해야 한다. PING이면 PONG을, GET key면 해당 값을 응답해주듯 말이다. 문제는 클라이언트가 PING\r\n을 보냈다고 해서 서버가 정확히 PING\r\n을 한 번에 받는다는 보장이 없다. 이번 포스팅에서는 이 문제의 본질을 TCP 프로토콜의 구조까지 내려가서 이해하고 커널과 유저 공간 사이의 데이터 흐름을 살펴본 뒤, go의 bufio.Reader와 io.Reader 인터페이스를 활용해 스트림 파싱을 구현하는 과정을 다룬다. ...

2026년 1월 30일 PM01:05 · PolarBear

In-Memory DB 구현 1편 - HTTP 없이 TCP 소켓 직접 제어하기

들어가며 요즘 대부분의 개발자들이 바이브 코딩 또는 못해도 gpt나 클로드, 제미나이 같은 LLM을 사용해서 개발을 할 것이다. AI가 코드를 구현해주면 개발자의 검증이 반드시 필요한데 성능 문제, 비즈니스 및 도메인 지식, 소프트웨어 구조 설계 등이 AI 시대의 개발자로서 필요한 역량이라고 생각한다. 코드가 그냥 굴러만 가더라도, 병목현상이 벌어지는 구간이 어디인지 검증을 해봐야하고 비즈니스가 어떻게 흘러가는지와 도메인 지식을 결합하여 코드를 어떻게 처리해야 좋을지, 그리고 복합적으로 따져봤을 때 최적의 아키텍처나 구조 등을 직접 결정해야할 것이다. ...

2026년 1월 28일 PM03:29 · PolarBear