Redis/Valkey는 Command를 어떻게 실행할까? - Valkey 커스텀 명령어를 만들어보자

2026. 5. 21. 04:36·CS STUDY

🔴 들어가며

OSSCA Valkey 4주차에서는 Valkey에 직접 command를 추가해보는 실습을 진행했다.

처음에는 단순히 함수 하나를 추가하면 되는 과제처럼 보였다. 하지만 실제로 따라가 보니 Valkey 내부에서 command가 어떻게 정의되고, 서버 시작 시 어떻게 등록되며, client 요청이 어떤 흐름을 거쳐 실제 C 함수까지 도달하는지 이해할 수 있는 실습이었다.

 

이번 글에서는 수업에서 다룬 Valkey command 등록 및 실행 흐름과, 내가 ECHOPIXXLIX command를 추가하고 PR을 보낸 과정을 정리한다.


🔴 Valkey command는 어디에 정의될까?

Valkey에서 command를 추가하려면 단순히 C 함수 하나만 작성하면 되는 것이 아니다.

command의 이름, 설명, 인자 수, 실행할 함수, command 성격 등이 함께 정의되어야 한다. 이번 실습에서 수정한 주요 파일은 다음과 같다.

파일                                                                               역할
src/commands/echo-pixxlix.json ECHOPIXXLIX command metadata 정의
src/commands.def ECHOPIXXLIX command 항목 추가
src/server.h echoPixxlixCommand 함수 선언
src/server.c echoPixxlixCommand 함수 구현
tests/unit/other.tcl ECHOPIXXLIX unit test 추가

Valkey command는 “어떤 이름의 command가 들어왔을 때 어떤 함수를 실행할 것인지”를 미리 등록해두고, client 요청이 들어오면 해당 command를 찾아 실행하는 구조로 동작한다.


🔴 Command 등록 흐름 4단계

수업에서 가장 먼저 본 부분은 command 등록 흐름이었다.

Valkey command는 요청이 들어올 때마다 새로 만들어지는 것이 아니라, 서버가 시작될 때 command table에 등록된다.

큰 흐름은 다음과 같다.

commands.def
↓
serverCommandTable[]
↓
populateCommandTable()
↓
server.commands
 

commands.def에는 command 이름, 설명, 실행 함수, 인자 수, flag, ACL category 같은 정보가 들어간다.

이후 서버가 시작되면 populateCommandTable()을 통해 command들이 command table에 등록된다. client가 command를 보내면 Valkey는 이 command table에서 해당 command를 찾는다.

이번에 추가한 ECHOPIXXLIX도 이 흐름에 포함되어야 한다. 즉, command metadata와 실행 함수가 제대로 연결되어 있어야 client가 ECHOPIXXLIX를 보냈을 때 정상적으로 실행된다.


🔴 Client 요청 이후 command 실행 흐름 7단계

클라이언트가 다음과 같이 command를 보냈다고 해보자.

 
ECHO abc
 

겉으로 보기에는 서버가 곧바로 echoCommand()를 호출하는 것처럼 보인다. 이 장면만 보면 “소켓에서 문자열을 읽고 바로 함수 하나를 실행하는 단순한 구조”라고 생각하기 쉽다.

 

하지만 실제 Valkey 내부에서는 command 함수가 호출되기 전에 여러 단계를 거친다. 네트워크에서 데이터를 읽고, 프로토콜을 parsing하고, command를 lookup하고, 인증과 ACL, 인자 수 검사 등을 통과한 뒤에야 실제 command 함수가 실행된다.

 

수업에서 정리한 Valkey command 실행 흐름은 다음과 같다.

client 요청
↓
readQueryFromClient()
↓
processInputBuffer()
↓
parseMultibulkBuffer() / parseInlineBuffer()
↓
lookupCommand()
↓
processCommand()
↓
call()
↓
c->cmd->proc(c)
↓
addReply()
↓
client 응답
 

이 흐름을 역할별로 나누면 다음 7단계로 정리할 수 있다.

 

단계                    함수                                                        설명
1. 연결 수락 acceptCommonHandler() → createClient() client 객체를 생성하고 read 이벤트 핸들러를 등록한다.
2. 데이터 수신 readQueryFromClient() → readToQueryBuf() 소켓에서 데이터를 읽어 client query buffer에 저장한다.
3. 프로토콜 파싱 parseMultibulkBuffer() / parseInlineBuffer() RESP 또는 inline command를 parsing하여 c->argc, c->argv[]를 채운다.
4. 명령어 lookup lookupCommand() → hashtableFind() 등록된 server.commands hashtable에서 command를 찾고 c->cmd에 연결한다.
5. 실행 전 검증 processCommand() 인증, ACL, arity, cluster redirection, 서버 상태 등을 검사한다.
6. 명령어 실행 call() → c->cmd->proc(c) 실제 command 함수가 호출된다.
7. 응답 전송 addReply() → sendReplyToClient() 응답을 client buffer에 쌓고 소켓으로 전송한다.

 

이 중 가장 중요한 지점은 6단계다.

c->cmd->proc(c)
 

c->cmd에는 lookup된 command 정보가 들어 있고, proc에는 실제로 실행할 C 함수가 연결되어 있다.

즉, 커스텀 command를 추가한다는 것은 단순히 함수 하나를 작성하는 일이 아니다. 새 command 이름이 lookup될 수 있도록 등록하고, 그 command의 proc에 연결될 실제 함수를 작성하는 과정이다.


🔴 과제 목표

이번 과제는 Valkey에 새로운 command를 하나 추가하는 것이었다.

추가할 command는 기존 ECHO와 같은 방식으로 동작한다. 사용자가 전달한 문자열을 그대로 다시 반환하는 command를 구현하면 된다.


기존 ECHO command 분석

이번 과제는 기존 ECHO command를 참고하면 이해하기 쉽다.

ECHO command의 핵심 구현은 다음과 같은 구조다.

 
void echoCommand(client *c) {
    addReplyBulk(c, c->argv[1]);
}
 

client가 다음과 같이 입력했다고 해보자.

 
ECHO abc
 

Valkey 내부에서는 parsing 이후 argument가 다음과 같이 정리된다.

c->argv[0] = "ECHO"
c->argv[1] = "abc"
c->argc = 2
 

여기서 c->argv[0]은 command 이름이고, c->argv[1]은 사용자가 전달한 첫 번째 인자다.

따라서 echoCommand()에서 c->argv[1]을 응답으로 반환하면 사용자가 입력한 abc가 그대로 출력된다.

addReplyBulk(c, c->argv[1]);
 

이 부분을 통해 Valkey command 함수는 parsing된 argv 배열을 기반으로 동작한다는 것을 확인할 수 있었다.


🔴 ECHOPIXXLIX command 구현

이제 기존 ECHO command를 참고해 ECHOPIXXLIX command를 추가했다.

1. command metadata 추가

먼저 src/commands/echo-pixxlix.json 파일을 추가했다.

이 파일에는 ECHOPIXXLIX command의 기본 정보가 들어간다.

{
    "ECHOPIXXLIX": {
        "summary": "Returns the given string.",
        "complexity": "O(1)",
        "group": "connection",
        "since": "8.1.0",
        "arity": 2,
        "function": "echoPixxlixCommand",
        "command_flags": [
            "LOADING",
            "STALE",
            "FAST"
        ],
        "acl_categories": [
            "CONNECTION",
            "FAST"
        ],
        "reply_schema": {
            "description": "The given string",
            "type": "string"
        },
        "arguments": [
            {
                "name": "message",
                "type": "string"
            }
        ]
    }
}
 

여기서 중요한 필드는 다음과 같다.

필드                                                              의미
summary command 설명
complexity 시간복잡도
group command 분류
arity command 이름을 포함한 인자 수
function 실제 실행될 C 함수 이름
command_flags command 실행 성격
acl_categories ACL category
arguments command가 받는 인자 정보

ECHOPIXXLIX abc는 내부적으로 command 이름과 message를 포함해 총 2개의 argument로 처리된다.

argv[0] = "ECHOPIXXLIX"
argv[1] = "abc"
argc = 2
 

따라서 arity는 2로 설정했다.


❓명령어 처리 속도는 왜 O(1)일까?

ECHOPIXXLIX command의 metadata에는 시간복잡도를 다음과 같이 작성했다.

 
"complexity": "O(1)"
 

처음에는 문자열을 응답으로 돌려주는 command인데 왜 O(1)인지 헷갈릴 수 있다. 입력 문자열의 길이가 길어지면 실제로 전송해야 하는 byte 수도 늘어나기 때문이다.

여기서 O(1)은 Valkey 내부 command 처리 로직 기준으로 이해하는 것이 좋다.

ECHOPIXXLIX는 key를 조회하지 않고, hash table이나 skip list 같은 자료구조를 탐색하지도 않는다. 단순히 parsing이 끝난 뒤 c->argv[1]에 들어 있는 값을 응답으로 추가한다.

 
void echoPixxlixCommand(client *c) {
    addReplyBulk(c, c->argv[1]);
}
 

즉, command 함수 입장에서 하는 일은 다음 한 가지다.

사용자가 전달한 첫 번째 인자 c->argv[1]을 응답으로 등록한다.
 

GET, HGET, ZRANGE처럼 특정 key나 자료구조 내부를 찾아가는 과정이 없기 때문에 command 자체의 논리적 처리 비용은 상수 시간으로 볼 수 있다.

 

다만 실제 네트워크 응답을 만들고 전송하는 비용은 문자열 길이에 영향을 받을 수 있다. 예를 들어 "abc"를 돌려주는 것과 매우 긴 문자열을 돌려주는 것은 전송해야 하는 byte 수가 다르다. 따라서 응답 크기에 따른 I/O 비용은 별도로 존재한다.

 

정리하면 다음과 같다.

command 로직 기준: O(1)
응답 데이터 전송 비용: 문자열 길이에 영향받을 수 있음
 

이번 command는 DB 조회나 자료구조 순회 없이 인자를 그대로 응답하는 echo 계열 command이므로 metadata의 complexity를 O(1)로 작성했다.


2. commands.def에 command 항목 추가

다음으로 src/commands.def에 ECHOPIXXLIX command 항목을 추가했다.

{MAKE_CMD("echopixxlix","Returns the given string.","O(1)","8.1.0",CMD_DOC_NONE,NULL,NULL,"connection",COMMAND_GROUP_CONNECTION,ECHOPIXXLIX_History,0,ECHOPIXXLIX_Tips,0,echoPixxlixCommand,2,CMD_LOADING|CMD_STALE|CMD_FAST,ACL_CATEGORY_CONNECTION|ACL_CATEGORY_FAST,NULL,ECHOPIXXLIX_Keyspecs,0,NULL,1),.args=ECHOPIXXLIX_Args},
 

3. server.h에 함수 선언 추가

commands.def에서 echoPixxlixCommand를 참조하므로, server.h에 함수 선언을 추가했다.

 
void echoPixxlixCommand(client *c);
 

이 선언을 통해 다른 파일에서도 echoPixxlixCommand 함수의 존재를 알 수 있다.


4. server.c에 함수 구현 추가

실제 동작은 server.c에 구현했다.

void echoPixxlixCommand(client *c) {
    addReplyBulk(c, c->argv[1]);
}
 

기존 echoCommand와 동일하게 사용자가 전달한 첫 번째 인자인 c->argv[1]을 그대로 응답으로 반환하도록 구현했다.

예를 들어 다음 명령을 실행하면,

ECHOPIXXLIX abc
 

내부적으로는 다음과 같이 처리된다.

c->argv[0] = "ECHOPIXXLIX"
c->argv[1] = "abc"
 

그리고 addReplyBulk(c, c->argv[1])에 의해 abc가 응답으로 반환된다.


🔴테스트 코드 추가

command가 정상적으로 동작하는지 확인하기 위해 tests/unit/other.tcl에 unit test를 추가했다.

test {Coverage: ECHOPIXXLIX} {
    assert_equal bang [r ECHOPIXXLIX bang]
}
 

 

만약 command가 제대로 등록되지 않았다면 command를 찾지 못해 실패할 것이고, 함수 구현에서 잘못된 argument를 반환했다면 기대값과 실제값이 달라져 테스트가 실패할 것이다.

즉, 이 테스트는 단순히 문자열 하나가 잘 출력되는지만 확인하는 것이 아니라, 다음 흐름이 정상적으로 이어졌는지 확인하는 역할도 한다.

1. ECHOPIXXLIX command가 등록되어 있다.
2. lookupCommand()가 command를 찾을 수 있다.
3. c->cmd에 command 정보가 연결된다.
4. call() 내부에서 echoPixxlixCommand()가 호출된다.
5. c->argv[1] 값이 응답으로 반환된다.
 

빌드 및 테스트

구현 후 먼저 빌드를 진행했다.

make -j$(nproc)
 

이후 unit test를 실행했다.

./runtest --single unit/other
 

결과는 다음과 같이 정상적으로 통과했다.

[ok]: Coverage: ECHOPIXXLIX (0 ms)
 

valkey-cli 수동 검증

unit test 외에도 valkey-cli를 통해 직접 command를 실행해보았다.

127.0.0.1:6379> ECHOPIXXLIX abc
"abc"

127.0.0.1:6379> ECHOPIXXLIX "hello world"
"hello world"
 

입력한 문자열이 그대로 반환되는 것을 확인할 수 있었다.


🔴PR 과정 정리

이번 구현 과정은 다음 순서로 진행했다.

1. 기존 ECHO command 구조 확인
2. ECHOPIXXLIX command metadata 추가
3. commands.def에 command 항목 추가
4. server.h에 함수 선언 추가
5. server.c에 echoPixxlixCommand 구현
6. tests/unit/other.tcl에 unit test 추가
7. make로 빌드
8. unit test 실행
9. valkey-cli로 수동 검증
10. commit 후 PR 생성
 

작은 command 하나를 추가하는 과정이었지만, 실제로는 command metadata, command table 등록 정보, 함수 선언, 함수 구현, 테스트까지 여러 지점이 함께 맞아야 했다.


🔴배운 점

이번 실습을 통해 Valkey command가 단순히 C 함수 하나로만 이루어진 것이 아니라는 점을 알 수 있었다.

처음에는 ECHOPIXXLIX abc를 입력하면 곧바로 echoPixxlixCommand()가 호출될 것처럼 생각했다. 하지만 실제로는 client 연결 수락, 데이터 수신, 프로토콜 parsing, command lookup, 실행 전 검증, 실제 함수 호출, 응답 전송이라는 여러 단계를 거친다. 

 

학기 초 운영체제 tiny shell 과제에서 명령어 만들기를 했었는데, 그때와 유사한 C 코드를 작성하여 기억이 새록새록 나고 재밌었다. 역시 전공수업 과제를 딸깍 안하고 열심히 하면 언젠가는 도움이 된다.

 

command 등록 흐름과 실행 흐름이 server.commands hashtable에서 만난다는 점을 이해한 것도 큰 수확이었다.

 

서버 시작 시 command가 table에 등록되고, client 요청이 들어오면 lookupCommand()가 이 table에서 command를 찾는다. command를 찾은 뒤에는 c->cmd에 command 정보가 연결되고, 최종적으로 call() 함수 내부에서 다음 코드가 실행된다.

c->cmd->proc(c)
 

즉, 커스텀 command를 추가한다는 것은 단순히 새로운 C 함수를 작성하는 일이 아니라, “이 command 이름이 들어왔을 때 어떤 함수가 실행되어야 하는가”를 Valkey의 command 등록 구조에 연결하는 일이다.

 

마지막으로, 아주 작은 echo command를 추가하는 실습이었지만 Valkey 코드를 읽는 방법을 익힐 수 있었다. 그동안 한 파일에 16000줄이 넘어가는 코드들을 보며 대체 어디에 PR을 시도해야할지, 구조를 어떻게 이해할지 어려웠는데 해당 실습을 통해 코드를 어떻게 읽어야 할지 감이 잡혔다.

 

앞으로 다른 command를 분석할 때도 먼저 command 정의를 찾고, 연결된 함수 이름을 확인한 뒤, 실제 구현으로 따라 들어가면 된다. 예를 들어 GET, HGET, ZRANGE 같은 command도 같은 방식으로 command 정의에서 출발해 내부 구현을 추적할 수 있다.

 

저작자표시 비영리 변경금지 (새창열림)

'CS STUDY' 카테고리의 다른 글

Valkey 커스텀 명령어 응답 가공하기: echo2_ prefix 붙이기  (0) 2026.05.24
Redis, Valkey는 왜 빠를까? - 자료구조, 메모리 최적화, RESP 프로토콜 실습  (0) 2026.05.10
Git-X-Ray : 깃 구조 살펴보기  (0) 2026.05.10
io_uring 개념과 epoll과의 비교 + 실제 코드 테스트  (0) 2025.11.08
'CS STUDY' 카테고리의 다른 글
  • Valkey 커스텀 명령어 응답 가공하기: echo2_ prefix 붙이기
  • Redis, Valkey는 왜 빠를까? - 자료구조, 메모리 최적화, RESP 프로토콜 실습
  • Git-X-Ray : 깃 구조 살펴보기
  • io_uring 개념과 epoll과의 비교 + 실제 코드 테스트
데구르르..
데구르르..
내가 보려고 기록하는
  • 데구르르..
    데굴데굴
    데구르르..
  • 전체
    오늘
    어제
    • 분류 전체보기 (27)
      • CS STUDY (5)
        • DB (8)
      • 프로그래머스 (5)
      • 백준 (5)
      • PROJECT (2)
      • ETC (1)
      • 회고 (1)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    그리디 알고리즘
    외래키
    Valkey
    ossca
    데이터베이스정리
    SQL기초
    데이터베이스설계
    redis
    백준
    관계형데이터베이스
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.6
데구르르..
Redis/Valkey는 Command를 어떻게 실행할까? - Valkey 커스텀 명령어를 만들어보자
상단으로

티스토리툴바