An update about Redis developments in 2019

Salvatore Sanfilippo

2019년 Redis 개발 현황 업데이트

어제 한 Redis 사용자가 Hacker News에 다음과 같은 글을 남겼습니다:

https://news.ycombinator.com/item?id=19204436

저는 Redis를 정말 좋아하지만, 현재 개발 중인 몇 가지 변경 사항에 대해서는 다소 회의적입니다. RESP3 프로토콜에는 흥미로워 보이는 기능들이 있지만, 클라이언트 라이브러리 코드를 상당히 복잡하게 만들 수도 있을 것 같습니다. 또한 세분화된 ACL 작업에도 많은 공이 들어가고 있는데, 왜 이런 기능이 필요한지, 그리고 멀티스레드 지원이나 더 나은 영속성 모델, 데이터 타입 같은 다른 변경보다 우선순위가 높은지 잘 이해가 되지 않습니다.

— 사용자 댓글 끝 —

그분(성별을 잘 모르겠습니다)이 ACL을 마치 Redis Labs의 목표, 그러니까 ‘엔터프라이즈 사용자’ 같은 것 때문에 강요된 기능처럼 바라보는 시각이 그분만의 것은 아닐 것 같다는 느낌이 들었습니다. 댓글의 다른 지적들도 흥미롭고, 앞으로의 방향에 대해 Redis 커뮤니티와 명확하게 소통하기 위해 모든 내용을 짚어볼 가치가 충분히 있다고 생각합니다.

설명을 쉽게 하기 위해 이 글에서는 원래 댓글에서 언급된 각 기능을 섹션별로 나누어 다루겠습니다.

RESP3

이미 이 블로그에서 말씀드렸듯이 RESP3의 목표는 오히려 클라이언트 생태계를 단순화하는 것입니다. 이상적으로 모든 클라이언트는 독자적인 고수준 인터페이스를 다시 만들려 하지 않고 redis.call(“get”,”foo”)와 같은 낮은 수준의 계층을 갖게 되기를 바랍니다. 이제 프로토콜 자체가 충분히 의미론적이어서 호출자에게 전달될 응답이 어떤 모습이어야 하는지 클라이언트에 알려주기 때문에 더 이상 변환을 일일이 조율할 필요가 없고, 대부분의 명령어에 대해 사전에 명령어의 특성을 알 필요도 없습니다. 사용자가 언급한 부분은 아마도 RESP3의 대역 외(out-of-band) 통신 지원, 즉 응답의 ‘어트리뷰트(attributes)’일 것이라고 생각합니다.

저는 앞으로 Redis에서 “클라이언트 사이드 캐싱(client side caching)”이 매우 중요한 기능이 될 것이라고 확신합니다. 확장 가능한 시스템이라면 당연히 밟게 되는 논리적인 다음 단계이기 때문입니다. 하지만 서버의 도움 없이는 클라이언트 측 캐시 무효화가 악몽이 됩니다. RESP3가 응답에 어트리뷰트를 지원하는 가장 큰 이유가 바로 여기에 있습니다. 다만 아마도 Redis 6에서는 *그 어떤 기능도 구현되지 않을 가능성이 높습니다*. Redis unstable 버전, 즉 앞으로 Redis 6이 될 버전에는 이미 거의 완성된 RESP3 구현이 들어 있지만 어트리뷰트는 없습니다. RESP3를 구현하는 클라이언트는 정말로 미래를 대비하고 싶다면 어트리뷰트를 그냥 버리도록 결정하면 되고, 어차피 사용자가 어떤 특수 기능을 활성화하지 않는 한 향후 Redis 버전에서도 어트리뷰트가 전송되지 않을 가능성이 큽니다. 예를 들어 클라이언트 사이드 캐싱을 위해서는 연결을 특수한 모드로 전환해야 할 것입니다. 게다가 아시다시피 Redis 6은 RESP2와 완전히 하위 호환될 것입니다. 사실 저는 RESP2 지원이 앞으로도 절대 제거되지 않을 것 같다는 생각이 들기 시작했습니다. 유지 비용이 거의 들지 않고, RESP2와 RESP3 사이의 추상화 계층을 구현하는 노력을 이미 들인 마당에 하위 호환성을 깰 이유가 없기 때문입니다.

저는 보통 타당한 이유 없이 무언가를 바꾸는 것을 좋아하지 않지만, RESP2의 한계는 클라이언트 생태계에 큰 영향을 미치고 있었습니다. 사용자들이 어떤 클라이언트에서 다른 클라이언트로 옮겨가도 낯설지 않고, API가 클라이언트 작성자가 만든 계층이 아니라 Redis 자체의 API가 되는 클라이언트 환경을 만들고 싶습니다. 참고로 낮은 수준의 API에 더해 *더 높은 수준의* API가 존재하는 것 자체를 반대하는 것은 아닙니다. 다만 공통 기반이 있어야 하고, 클라이언트는 해당 명령어에 대해 아무것도 모르더라도 명령어를 보낼 수 있어야 합니다.

ACLs

ACL 명세는 4년 전에 제가 직접 작성했습니다. 정말로 지금이 구현할 때라는 확신이 들 때까지 오랜 시간을 기다렸습니다. 지금까지는 주로 명령어 이름 변경 같은 편법으로 ACL 없이도 꽤 멀리 왔습니다. 하지만 ACL의 주된 동기가 보안을 필요로 하는 엔터프라이즈 고객이라고 생각하지는 마십시오. 부수적인 효과로 ACL은 보안 목적의 사용자 인증도 가능하게 하지만, 이 기능의 주된 목표는 *운영*에 있습니다.

예를 하나 들어 보겠습니다. Redis 인스턴스 하나가 있고, 여기에 지연 작업 처리를 위한 새로운 용도를 추가하려고 합니다. 인터넷에서 라이브러리 하나를 가져왔는데 잘 동작하는 것 같습니다. 그런데 한 줄 한 줄을 속속들이 알지 못하는 그런 라이브러리가 왜 “FLUSHALL”을 호출해서 데이터베이스를 순식간에 날려버릴 수 있어야 할까요? 어쩌면 라이브러리 테스트 코드 안에 그런 명령어가 들어 있어서 너무 늦게야 알게 될지도 모릅니다. 혹은 이제 막 채용한 주니어 개발자가 회사 Redis 정책이 “KEYS 명령어 사용 금지”임에도 계속 Redis 인스턴스에 “KEYS *”를 호출하고 있을지도 모릅니다.

다른 시나리오로 클라우드 제공업체를 들 수 있습니다. 이들은 관리용 명령어를 신중하게 이름을 바꾸고, 심지어 어떤 이유로든 그런 명령어가 노출되지 않도록 숨겨야 합니다. 더 많은 편법이 동원됩니다. 예를 들어 MONITOR 출력에 명령어가 나타나지 않도록 하는 식입니다. ACL을 이용하면 기본 사용자가 별도의 인증 없이는 관리용이거나 위험한 어떤 명령어도 실행하지 못하도록 Redis를 설정할 수 있습니다. 이는 운영 측면에서 큰 개선이 될 것이라고 생각합니다.

게다가 ACL은 제가 Redis를 위해 작성한 코드 중에서도 제가 아는 한 가장 잘 만든 코드 중 하나입니다. 키 패턴을 사용하지 않는 한 CPU 비용이 거의 들지 않으며, 사용하더라도 비용은 미미합니다. 구현은 acl.c 파일 안에 완전히 독립적으로 들어 있고, 코어의 나머지 부분은 ACL API를 호출하는 몇 줄만 가지고 있습니다. 완전히 모듈식이기 때문에 시스템에 복잡성이 추가되지 않습니다. 오히려 ACL 코드 덕분에 AUTH 명령어 주변을 잘 리팩터링할 수 있었습니다.

Multi threading

Redis가 지원할 수 있는 멀티스레딩에는 두 가지 가능성이 있습니다. 사용자가 언급한 것은 “memcached와 유사한” 멀티스레딩, 즉 단일 Redis 인스턴스를 여러 스레드로 확장해 GET이나 SET 같은 단순한 명령어 등의 초당 처리량을 높이는 방식이라고 생각합니다. 이는 I/O, 명령어 파싱 등을 멀티스레드로 만드는 것을 의미합니다. 그래서 이 방식을 “I/O 스레딩”이라고 부르겠습니다.

또 다른 멀티스레드 접근 방식은 느린 명령어를 다른 스레드에서 실행하도록 하여 다른 클라이언트가 블로킹되지 않게 하는 것입니다. 이 스레딩 모델을 “느린 명령어 스레딩”이라고 부르겠습니다.

결론부터 말씀드리면, 제가 아는 한 I/O 스레딩은 Redis에서 일어나지 않을 것입니다. 깊이 고민한 끝에 타당한 이유 없이 복잡성만 크게 늘어난다고 생각하기 때문입니다. 실제로 많은 Redis 구성은 네트워크나 메모리가 병목입니다. 게다가 저는 share-nothing 구성을 강하게 신봉하기 때문에, Redis를 확장하는 방법은 같은 호스트에서 여러 Redis 인스턴스를 실행하는 것에 대한 지원을 개선하는 것, 특히 Redis Cluster를 통해서라고 생각합니다. 이와 관련해 2019년에 일어날 일은 두 가지입니다:

A) Redis Cluster의 여러 인스턴스가 로컬 인스턴스의 디스크를 현명하게 사용하도록 조율할 수 있게 됩니다. 즉, 동시에 AOF 재작성이 일어나지 않도록 하는 것입니다.

B) Redis 프로젝트의 일부로 Redis Cluster 프록시를 제공하여, 클라이언트 측에 Cluster 프로토콜을 제대로 구현하지 않아도 클러스터를 추상화할 수 있도록 할 예정입니다.

또 하나 짚고 넘어갈 점은 Redis는 Memcached가 아니지만, memcached처럼 인메모리 시스템이라는 것입니다. 데이터 모델이 매우 단순한 memcached 같은 인메모리 시스템을 멀티스레드로 만드는 것은 매우 타당합니다. 온디스크 저장소를 멀티스레드로 만드는 것은 필수입니다. 복잡한 인메모리 시스템의 멀티스레딩은 그 중간 어딘가에서 일이 복잡해지는 영역에 있습니다. Redis 클라이언트는 서로 격리되어 있지 않고, 자료구조도 복잡합니다. LPUSH를 수행하는 스레드가 LPOP을 수행하는 다른 스레드를 위해 서비스를 제공해야 합니다. 얻을 수 있는 이득은 적고, 추가되는 복잡성은 큽니다.

대신 제가 *정말로* 원하는 것은 느린 연산에 대한 스레딩이며, Redis 모듈 시스템 덕분에 이미 올바른 방향으로 가고 있습니다. 다만 앞으로( Redis 6이 될지 7이 될지는 모르겠지만) 모듈 시스템에 키-레벨 잠금이 도입되어 스레드가 느린 연산을 처리하기 위해 키에 대한 제어권을 완전히 획득할 수 있게 될 것입니다. 현재 모듈은 명령어를 구현하고 클라이언트를 위한 응답을 완전히 분리된 방식으로 만들 수 있지만, 공유 데이터셋에 접근하려면 여전히 전역 잠금이 필요합니다. 이 부분은 앞으로 사라질 것입니다.

Better persistence

최근 우리는 Redis의 이런 종류의 핵심 기능을 개선하기 위해 여러 차례 노력해 왔습니다. 최근에 구현된 것 중 가장 좋은 것 중 하나는 AOF 파일 안의 RDB 프리앰블입니다. 또한 Redis 4와 5에서 모두 복제(replication)에 대해 많은 작업이 이루어졌고, 이제는 예전과는 완전히 다른 수준에 이르렀습니다. 그리고 네, 이러한 부분을 개선하는 것은 여전히 저의 주요 관심사 중 하나입니다.

Data structures

이제 Redis는 Redis 5부터 Streams를 갖추게 되었습니다. Redis 6과 7에서는 우선 특정 구현을 변경해 현재 가지고 있는 것들을 훨씬 더 메모리 효율적으로 만드는 것이 계획되어 있습니다. 하지만 새로운 자료구조를 추가하는 데는 고려해야 할 점이 많습니다. 리스트, pub/sub, 정렬된 셋 사이의 간극을 시계열과 스트리밍이라는 맥락에서 Streams로 어떻게 메울 수 있을지 깨닫는 데만 수년이 걸렸습니다. 저는 Redis가 바로 쓸 수 있는 *도구*들의 집합이 아니라, 사용자가 조합해서 쓸 수 있는 직교적인(orthogonal) 자료구조들의 집합이 되기를 바랍니다. Streams는 추상적인 로그이므로 매우 가치 있는 추가라고 생각합니다. 하지만 다른 것들이 매우 오랜 고민 없이 코어 안에 들어갈 가치가 있는지는 아직 확신이 서지 않습니다. 어쨌든 최근 몇 년간 새로운 자료구조 추가에 대한 부담은 확실히 더 커졌습니다. HyperLogLog, 더 고도화된 비트 연산, Streams, 블로킹 정렬 집합 연산(ZPOP*와 BZPOP*), 그리고 Streams가 좋은 예입니다.

Conclusions

Redis 커뮤니티는 왜 어떤 일이 이루어지고 왜 어떤 일은 연기되는지 알고 있어야 한다고 생각합니다. 저는 마치 모두가 트위터에 있는 것처럼 트위터를 통해 소통하는 실수를 자주 하는데, 많은 분들은 삶이 바빠서 :-D 신경 쓰지 않기도 합니다. 블로그가 커뮤니티에 정보를 전달하는 훨씬 더 좋은 방법이며, 글을 쓰는 데 더 많은 시간을 할애해야 합니다. 마침 글 쓰는 것을 좋아하니 서로에게 좋은 일입니다. 중요한 점은 Redis에는 탄탄한 로드맵이 없다는 것을 알아두는 것입니다. 수년간 저는 로드맵을 갖는 것보다 기회주의적인 개발이 훨씬 더 큰 성과가 된다는 것을 알게 되었습니다. 뭔가 요구가 있는가? 필요성을 느끼는가? 코딩할 의욕이 있는가? 다른 큰 우선순위가 없어서 지금이 적기인가? 설계를 돕고 힌트와 아이디어를 주며 테스트를 도와주는 사용자 그룹이 있는가? 그렇다면 지금이 바로 실행할 때입니다. Redis에 탄탄한 로드맵을 갖는 것은 어리석은 일입니다. OSS 코어 팀의 규모가 작고, 때로는 알 수 없는 충돌 하나에 몇 주씩 붙잡혀 있기도 하기 때문입니다. 고정된 장기 계획은 통하지 않습니다. 게다가 Redis 커뮤니티의 피드백을 받으면 제 아이디어도 많이 바뀌기 때문에, 로드맵을 매달 다시 쓰게 될 것입니다. 그럼에도 블로그는 적어도 현재의 우선순위와 아이디어가 어떤 모습인지, 그리고 왜 다른 아이디어들이 버려졌는지를 보여주는 좋은 방법입니다.

마지막으로 한 가지 덧붙이자면, 오픈 소스 프로젝트 측면에 무엇을 넣을지에 대해 Redis Labs로부터 받는 자유도는 거의 무한합니다. 이것이 업계에서 일종의 기적이라고 생각하거나, 아니면 제가 함께 일하는 Redis Labs의 사람들이 오픈 소스 운동에서 비롯된 일을 이해하고 그 흐름을 계속 이어가는 것이 현명하다고 생각하는 멋진 분들이기 때문이라고 생각합니다. 하지만 흔한 일은 아닙니다. Redis 로드맵에서 실수를 한다면 그것은 분명히 저의 실수일 것입니다.

원문은 Salvatore Sanfilippo님이 에 게재했습니다.

이 글은 muse-spark-1.2-contributor 모델을 사용해 번역했습니다.