Computer >> 컴퓨터 >  >> 프로그래밍 >> Redis

Redis 프로토콜(RESP) 완벽 입문 가이드

Redis의 가장 뛰어난 숨은 기능 중 하나를 꼽으라면 단연 통신 프로토콜입니다. 이 프로토콜 덕분에 Redis는 고품질 클라이언트 라이브러리가 풍부한 대형 생태계를 자랑할 수 있게 되었습니다. Redis의 유선(wire) 프로토콜은 놀랄 만큼 단순해서, Redis의 주요 기능을 모두 구현하는 클라이언트를 손쉽게 만들 수 있습니다. 동시에 빠르고 효율적인 클라이언트 라이브러리를 작성하기 쉽도록 설계되어 있다는 점도 큰 장점입니다.

RESP란 무엇인가?

RESP(REdis Serialization Protocol)는 Redis 클라이언트와 서버가 TCP를 통해 서로 통신할 때 사용하는 텍스트 기반 프로토콜입니다. 서버와 클라이언트 간의 모든 통신은 다음 다섯 가지 기본 타입으로 이루어집니다.

  • 단순 문자열(Simple strings): "OK"(쓰기 명령 성공 후)나 "PONG"(PING 명령에 대한 정상 응답)처럼 일반적인 서버 응답에 사용됩니다.
  • 벌크 문자열(Bulk strings): GET, LPOP, HGET처럼 단일 값을 반환하는 거의 모든 읽기 명령의 응답으로 사용됩니다. 벌크 문자열은 개행 문자, 제어 문자, 심지어 유효한 RESP조차 이스케이프나 인코딩 없이 그대로 담을 수 있다는 점에서 단순 문자열과 다릅니다.
  • 정수(Integers): STRLEN, HLEN, BITCOUNT 같은 카운팅 계열 명령의 응답으로 사용됩니다.
  • 배열(Arrays): 다른 배열을 포함해 원하는 만큼의 RESP 객체를 담을 수 있습니다. 서버로 명령을 전송할 때와 HGETALL, LRANGE, MGET처럼 여러 요소를 반환하는 응답에 활용됩니다.
  • 에러(Errors): 잘못된 타입의 데이터가 저장된 키에 명령을 실행하는 경우처럼, Redis가 명령 처리 중 오류를 만났을 때 반환됩니다.

RESP를 읽고 쓸 때는 키나 값에 대해 별도의 이스케이프나 인코딩 작업이 필요 없습니다. 실제로 파싱해야 하는 부분은 각 RESP 객체에 포함된 간단한 메타데이터뿐입니다.

RESP는 어떻게 생겼을까?

모든 RESP 객체는 접두사 문자로 시작하고 줄 종결자(line terminator)로 끝납니다(배열은 예외적으로 자체 줄 종결자를 포함하지 않습니다). 가장 간단한 RESP 객체의 예는 OK 단순 문자열 응답입니다.

+OK\r\n

(RESP는 사람이 읽을 수 있는 프로토콜이지만, 설명의 명확성을 위해 모든 예제에서 줄바꿈(\r\n)을 명시적으로 표기하겠습니다.)

위 단순 문자열에서 +는 단순 문자열 접두사, OK는 본문, \r\n은 이 문자열의 끝을 표시하는 줄 종결자입니다.

단순 문자열을 읽을 때는 다음 \r\n 줄 종결자까지 읽고, 그 앞의 바이트들(+ 제외)을 응답 문자열로 반환하면 됩니다. RESP 단순 문자열에는 개행 문자가 포함될 수 없기 때문에 이 방식이 안전하게 동작합니다.

에러와 정수도 단순 문자열과 비슷한 형식을 따르지만 접두사가 다릅니다. 에러는 -로 시작합니다.

-ERR unknown command 'GETT'\r\n

정수는 :로 시작합니다.

:99\r\n

벌크 문자열은 길이 지정부와 본문이라는 두 부분으로 구성된다는 점에서 독특합니다.

$13\r\nHello, World!\r\n

$는 벌크 문자열 접두사이고, 13은 실제 문자열 본문의 바이트 수이며, 그 뒤의 \r\n이 길이 지정부를 종료합니다. Hello, World!는 13바이트 문자열 본문이며, 역시 \r\n으로 종료됩니다(이 \r\n은 본문에 포함되지 않습니다).

벌크 문자열은 본문의 정확한 길이를 미리 알려주기 때문에, 본문의 끝을 찾기 위해 문자열 내용을 일일이 파싱할 필요가 없습니다. 소켓에서 13바이트(마지막 줄 종결자 포함)를 추가로 읽기만 하면 됩니다. 즉, 내용을 인코딩하거나 이스케이프하지 않고도 문자열 본문에 어떤 데이터든 자유롭게 담을 수 있습니다.

배열 역시 길이 지정으로 시작하지만, 여기서 길이 필드는 배열 내용의 바이트 수가 아니라 배열에 포함된 객체의 개수를 나타냅니다.

*2\r\n$3\r\nfoo\r\n$3\r\nbar\r\n

위 예제는 두 개의 벌크 문자열(foobar)을 담고 있는 배열입니다. 배열의 길이 지정은 벌크 문자열을 읽을 때처럼 위치를 한 번에 건너뛰는 데에는 활용할 수 없지만, 클라이언트에서 배열을 구현하기는 오히려 쉬워집니다. 배열 크기를 읽고, 그만큼의 객체를 차례로 읽은 다음, 해당 객체들을 최종 배열로 반환하면 됩니다.

모든 Redis 명령은 벌크 문자열의 배열 형태로 전송됩니다. 예를 들어 "SET mykey 'my value'" 명령은 다음과 같이 작성되어 전송됩니다.

*3\r\n$3\r\nSET\r\n$5\r\nmykey\r\n$8\r\nmy value\r\n

RESP의 추가 세부 사항

RESP에서 사용하는 접두사 기반 길이 표기 덕분에 복잡한 상태 모델링이나 여러 번의 데이터 스캔 없이도 파서를 구현할 수 있으며, 이를 통해 매우 빠른 파서를 만들 수 있습니다.

여기서 다루지 않은 세부 사항도 몇 가지 있습니다. 대표적으로 null 벌크 문자열과 null 배열 등이 있습니다. 더 자세한 정보는 공식 문서에서 확인할 수 있는데, RESP에 대해 읽기 쉽고 포괄적인 문서 페이지를 제공하고 있으니 참고하시기 바랍니다.

프로토콜을 더 깊이 이해하는 가장 좋은 방법은 직접 간단한 클라이언트를 구현해 보는 것입니다. 다음으로 Go 언어로 Redis 프로토콜을 읽고 쓰는 가이드도 함께 확인해 보세요.