라즈베리 파이에서 Redis: 정렬되지 않은 세계에서의 모험
1,000만 대 판매를 넘어섰고, 센서와 디스플레이 같은 사실상 무궁무진한 응용 분야와 주변 장치들을 보면 라즈베리 파이는 단순한 성공작을 넘어 임베디드 분야에서 프로그래머들이 실험하기에 가장 선호하는 플랫폼 중 하나가 되었다고 해도 과언이 아니라고 생각합니다. Pi Zero 같은 제품 덕분에 이제는 수직적 전용 장치를 직접 설계하고 제작하며 소프트웨어까지 작성해야 하는 모든 위험과 비용을 감수하지 않고도 하드웨어 제품을 만들 수 있는 플랫폼이 되어가고 있는 듯합니다.
저는 Redis 역시 프로그래머들이 무언가를 해킹하고 실험하며 새로운 것을 만들 때 즐겨 쓰는 플랫폼이라고 생각하곤 합니다. 더구나 임베디드/IoT 용도로 쓰이는 장치들은 센서로부터 받은 데이터 같은 정보를 기기 안에 일시적 혹은 영구적으로 저장해야 하는 문제를 자주 겪습니다. 기기 내에서 연산을 수행하거나 원격 서버로 보내기 위해서죠. Redis는 바로 이런 데이터 스트림과 시계열 저장에 특화된 “Stream” 자료형을 추가하고 있습니다. 현재 명세는 거의 완성되었고 앞으로 몇 주 안에 구현 작업이 시작될 예정입니다. Redis의 기존 자료구조와 새로운 Stream, 그리고 작은 메모리 사용량, 소형 하드웨어에서도 낼 수 있는 준수한 성능(덕분에 전력 소모도 낮습니다)을 함께 고려하면 라즈베리 파이의 잠재적 활용 분야, 나아가 소형 ARM 장치 전반과 잘 어울려 보였습니다. 빠진 조각은 너무나 분명했습니다. 파이에서 제대로 동작하도록 만드는 일이었죠.
파이의 멋진 점 중 하나는 개발 환경이 몇 년 전의 임베디드 개발 환경과는 전혀 다르다는 것입니다. 그냥 리눅스가 돌아가고, 기대할 수 있는 데비안 계열 도구들이 모두 있습니다. 기본적으로 Redis가 파이에서 동작하도록 만드는 일은 그리 큰 작업이 아니었습니다. 리눅스 시스템 프로그램과 파이가 가질 수 있는 가장 근본적인 불일치는 성능/자원 사용량 문제겠지만, 이는 Redis 자체의 설계 덕분에 문제가 되지 않습니다. 빈 인스턴스의 상주 메모리(RSS)는 총 1MB에 불과하고, 질의를 메모리에서 처리하므로 충분히 빠르고 플래시 디스크에 부담도 거의 주지 않습니다. 영속성이 필요할 때는 추가 전용(append-only) 쓰기 패턴을 가진 AOF를 사용합니다. 다만 파이는 ARM 프로세서에서 동작하므로 정렬되지 않은 접근(unaligned access)을 다룰 때 주의가 필요합니다.
이 글에서는 Redis와 라즈베리 파이가 더 잘 어울리도록 제가 한 작업을 보여드리면서, x86 플랫폼처럼 정렬되지 않은 접근을 투명하게 처리하지 않는 아키텍처를 다루는 방법에 대해 전반적으로 설명해 보려 합니다.
ARM 프로세서에 대한 몇 가지 이야기
Redis를 ARM으로 포팅하면서 가장 흥미로운 점은 ARM 프로세서가, 정확히는 과거에는 정렬되지 않은 메모리 접근을 그다지 좋아하지 않았다는 사실입니다. 고급 언어로만 프로그래밍해 왔다면 모를 수도 있지만, 과거의 많은 프로세서 아키텍처는 워드 크기의 배수가 아닌 주소에서 메모리 워드를 읽거나 쓰는 것을 할 수 없었습니다. 예를 들어 워드 크기가 4바이트인 32비트 프로세서라면 0x4, 0x8 같은 주소에서는 워드를 읽고 쓸 수 있지만 0x7 같은 주소에서는 할 수 없습니다. 그 결과는 CPU와 그 세부 설정에 따라 때로는 예외가 발생하고 때로는 이상한 동작으로 나타나곤 했습니다.
그러다 x86 프로세서 계열이 세상을 지배하게 되면서 모두가 이 문제를 어느 정도 잊어버렸습니다(물론 SSE 명령어 등을 다룰 때는 예외지만, 이제는 그런 명령어들도 정렬되지 않은 접근을 지원하는 변형이 있습니다). 사실 처음부터 문제를 잊어버린 것은 아니었습니다. x86 프로세서는 예외를 발생시키지 않고 정렬되지 않은 접근을 처리할 수 있었지만, 그 비용으로 적지 않은 성능 저하가 있었습니다. 워드 경계에 걸친 부분적인 읽기와 쓰기는 두 배의 작업이 필요했기 때문입니다. 하지만 최근의 x86 프로세서는 최적화 덕분에 대부분의 경우 정렬되지 않은 접근을 정렬된 접근만큼 빠르게 처리하므로, 요즘 x86에서는 이 문제가 사실상 전혀 문제가 되지 않습니다.
ARM은 ARM v5까지만 해도 정렬되지 않은 접근이 이상하고 실제로 매우 예상치 못한 결과를 초래하던 플랫폼 중 하나였습니다. ARM 공식 문서에는 이렇게 나와 있습니다. “주소가 4의 배수가 아닐 경우, LDR 명령어는 실제로 정렬되지 않은 워드 로드를 수행하는 대신 회전된 결과를 반환합니다. 일반적으로 이러한 회전은 프로그래머가 기대하는 것이 아닙니다.” 정말 *전혀* 프로그래머가 기대하는 것이 아닙니다. 하지만 원래 라즈베리 파이조차도 ARM v6 프로세서를 탑재하고 있었습니다. v6는 성능 저하를 감수하긴 하지만 워드 크기의 정렬되지 않은 접근은 처리할 수 있습니다. 다만 여러 워드를 다루는 명령어는 예외를 발생시켜 프로그램을 버스 시그널로 종료시키거나 커널에 도움을 요청합니다(이에 대해서는 뒤에서 더 자세히 살펴보겠습니다). 이는 Redis가 파이에서 실행될 때 대부분의 정렬되지 않은 접근이 실제로는 워드 크기였기 때문에 즉시 엉망으로 충돌하지는 않는다는 의미입니다. 하지만 때때로 컴파일러가 계산 속도를 높이기 위해 다중 로드/스토어 명령어를 사용한 코드를 생성하거나, Redis 코드 자체가 정렬되지 않은 접근으로 64비트 값을 읽고 쓰려 할 때가 있었습니다. 이론상으로는 보통 충돌로 이어져야 하지만, 이 부분에서는 리눅스가 어느 정도 도움을 줍니다.
충돌하는 대신 커널에 도움을 요청하기
ARM 프로세서에서 동작하는 리눅스 커널은 CPU가 정상적으로 지원하지 않는 정렬되지 않은 주소에 대한 연산을 수행하더라도 사용자 프로세스가 예상대로 동작하도록 도와줄 수 있습니다. 이는 커널 내부에 이러한 예외를 위한 핸들러를 등록함으로써 이루어집니다. 커널은 실패한 연산을 확인하고 함수로 이를 에뮬레이션하여, 마치 프로세서가 직접 실행한 것과 같은 결과가 나오도록 만든 뒤 문제가 된 프로세스를 재개시켜 계속 실행되도록 합니다.
로우 레벨 프로그래밍에 관심이 있다면 이 리눅스 커널 파일을 한번 살펴볼 만합니다: http://lxr.free-electrons.com/source/arch/arm/mm/alignment.c
CPU가 정렬되지 않은 접근 예외를 발생시켰을 때 커널이 실제로 어떻게 동작할지는 /proc/cpu/alignment 파일로 제어됩니다.
$ cat /proc/cpu/alignment
User: 0
System: 12590 (ip6_datagram_recv_common_ctl+0xc8/0xd4 [ipv6])
Skipped: 0
Half: 0
Word: 0
DWord: 0
Multi: 12590
User faults: 2 (fixup)보시다시피 커널에 의해 수정된 모든 정렬되지 않은 접근에 대해 사용자 공간과 커널 공간을 구분한 별도의 카운터가 있습니다. 위의 경우 커널 공간에서 12,590건의 접근이 수정되었습니다. 사용자 공간 프로세스에서는 수정된 경우가 없습니다. “User faults” 줄은 CPU가 처리할 수 없는 정렬되지 않은 접근을 사용자 공간 프로세스가 수행했을 때 어떻게 할지에 대한 커널 설정을 보여줍니다. 문제를 수정하거나 SIGBUS를 보내거나 커널 로그에 사건을 기록할 수 있습니다. 이는 /proc/cpu/alignment에 쓸 수 있는 정수 값의 개별 비트로 제어되며, 예를 들어 사용자 공간의 정렬되지 않은 접근을 수정하는 것에 더해 로그까지 남기려면 “echo 3 > /proc/cpu/alignment”를 사용할 수 있습니다(비트 1은 로깅을, 비트 2는 수정을 활성화합니다).
제 생각에는 리눅스 커널이 이런 기능을 활성화한 이유가 커널 개발자들이 정렬되지 않은 메모리 접근을 제대로 다루지 못하는 가엾은 사용자 공간 프로그래머들을 걱정해서라기보다는, “System” 카운터에서 볼 수 있듯이 커널 자체도 항상 정렬된 접근만 수행하지는 않기 때문입니다. 따라서 코드의 모든 곳을 하나하나 검사하는 대신, 이것이 ARM에서 리눅스 포팅을 수정하는 가장 간단한 방법이었던 셈입니다.
리눅스가 이를 투명하게 처리해 준다면, “뭐, 어쩌면 여기서 고칠 게 없는 거 아닌가? /proc/cpu/alignment를 투명하게 수정하도록 설정해 두면 Redis가 그냥 예상대로 동작하지 않을까?” 하고 생각하기 쉽습니다. 하지만 실제로는 두 가지 이유로 그렇지 않습니다.
- 정렬되지 않은 접근이 수행되어 커널이 수정하면 실행 속도가 *매우* 느려집니다. 속도 저하는 예를 들어 정렬되지 않은 워드 크기 접근을 할 때 필요한 두 번째 메모리 접근보다 훨씬 큽니다. 이런 일은 다중 로드와 스토어 명령어에서만 일어나지만, 특정 조건에서 Redis가 필요 이상으로 훨씬 느려지는 것은 여전히 아쉬운 일입니다.
- 정렬되지 않은 접근에 대한 리눅스 커널의 ARM 구현은 완벽하지 않습니다. GCC가 생성하는 코드 중에는 Linux 4.4.34가 제대로 처리하지 못하는 명령어를 포함하는 경우가 있습니다.
간단한 예를 들면 다음과 같습니다.
#include <stdlib.h>
int main(int argc, char **argv) {
int count = 1000;
char *buf = malloc(count*sizeof(double));
double sum = 0;
double *l = (double*) (buf+1);
while(count--) {
l++;
sum += *l;
}
return 0;
}$ gcc foo.c -g -ggdb
$ ./a.out
Bus error제 파이의 커널 설정이 정렬되지 않은 접근을 처리하고 수정하도록 되어 있는데도 프로그램은 여전히 SIGBUS를 받았습니다! GDB로 어디서 발생하는지 살펴보겠습니다.
$ gdb ./a.out
(gdb) run
Program received signal SIGBUS, Bus error.
0x00010484 in main (argc=1, argv=0xbefff3b4) at foo.c:10
10 sum += *l;예상대로 정렬되지 않은 double 포인터를 역참조하는 내부 루프에서 발생했습니다. 하지만 어떤 일이 일어나고 있는지 더 자세히 확인하기 위해 예외를 발생시킨 ARM 명령어를 살펴보겠습니다.
(gdb) x/i $pc
=> 0x10484 <main+100>: vldr d6, [r11, #-20] ; 0xffffffecVLDR 명령어는 메모리 위치에서 확장 레지스터를 로드하는 데 사용되며 부동소수점 연산에 쓰입니다. 어째서인지 정렬되지 않은 접근을 보정하는 리눅스 커널 구현이 이 명령어를 처리하지 못합니다(아마 구현이 완전하지 않기 때문인 듯합니다). 실제로 “dmesg” 명령어를 보면 정렬되지 않은 접근을 수정하는 함수가 이 명령어를 인식하지 못했다는 것을 알 수 있습니다.
[317778.925569] Alignment trap: not handling instruction ed937b00 at [<00010480>]
[317778.925610] Unhandled fault: alignment exception (0x011) at 0x01cb8011즉, 파이의 기본 C 컴파일러가 기본 리눅스 커널이 처리하지 못하는 코드를 만들어낼 수 있다면, 저는 커널이 정렬되지 않은 접근을 수정하지 않도록 설정된 경우에도 Redis가 문제없이 동작하기를 바랐습니다. 이는 ARM에서 Redis가 CPU가 투명하게 처리할 수 있는 워드 크기의 정렬되지 않은 접근만 수행해야 한다는 의미입니다.
버그 수정하기
ARM이 대부분의 정렬되지 않은 메모리 접근을 잘 처리하므로, Redis는 파이에서 대체로 이미 동작하는 것처럼 보였습니다. 특히 기본적으로 커널이 지원되지 않는 정렬되지 않은 접근 중 많은 부분을 수정하도록 설정되어 있기 때문입니다. 정렬 수정을 비활성화해도 표면적으로는 여전히 동작했습니다. 하지만 테스트를 실행해 보니 특히 비트 연산이나 해시 함수 같은 자명한 영역에서 다양한 충돌이 드러났습니다.
이제 Redis가 가장 먼저 하는 일은 정렬되지 않은 접근을 지원하지 않는 아키텍처에서 컴파일될 때 USE_ALIGNED_ACCESS를 정의하는 것입니다. 그 다음에는 정렬되지 않은 접근이 수행되던 빠른 경로를 피하도록 코드를 수정하거나, 포인터 역참조를 memcpy() 연산으로 교체하는 일이 남았습니다. memcpy()를 쓰는 것이 포인터를 역참조하는 것보다 훨씬 느리다고 생각할 수도 있지만, 실제로는 그보다 훨씬 낫습니다. memcpy(src,dst,sizeof(uint64_t)) 같은 고정 크기 memcpy 호출에서는 컴파일러가 똑똑하게 함수 호출을 피합니다. 주소가 정렬되어 있지 않더라도 작업을 처리할 수 있는 가장 빠른 명령어 조합을 생성하는 것이죠. 예를 들어 x86 프로세서에서는 이 함수 호출이 실제로 단일 MOV 명령어로 변환됩니다.
이러한 수정 후 Redis와 제 라즈베리 파이 두 대 — 오리지널 모델 B 한 대와 훨씬 빠른 Pi 3 한 대 — 는 아주 좋은 친구가 되었습니다. 크래시 리포트의 호출 추적 생성에 관한 테스트 하나를 제외하고는 모든 테스트가 통과했고(이것도 곧 수정할 예정입니다), 가끔 통합 테스트에서 파이가 마스터와 슬레이브 구성을 설정하는 속도가 느려 몇 가지 실패가 발생하기도 했습니다. 하지만 이쯤 되니 정확성에 대한 욕심이 생겨, 더 많은 정렬 문제를 찾아보고 싶어졌습니다.
한 걸음 더 나아가기: SPARC
ARM용 Redis를 수정하는 동안 GitHub 저장소에는 Redis가 Solaris/SPARC에서 잘 동작하도록 만드는 병렬 이슈가 열려 있었습니다. SPARC는 ARM처럼 관대하지 않습니다. *어떤* 정렬되지 않은 접근도 처리할 수 없습니다. 저는 C 프로그래밍을 시작한 첫 몇 년 동안 아주 오래된 SPARCstation 4를 구입했기 때문에 이를 아주 잘 기억합니다. 빅 엔디안이면서 동시에 그 어떤 종류의 정렬되지 않은 접근도 처리하지 못했던 그 기계는 프로그램을 이식하는 일에 대해 저에게 어느 정도 관점을 갖게 해주었습니다. 몇 달 뒤 그 위에 보드카를 쏟는 바람에 메인보드를 영영 태워버린 것은 정말 안타까운 일이지만, 아직도 부모님 집에 보관하고 있습니다.
Solaris/SPARC에서 정렬되지 않은 접근을 다루는 방식은 Linux/ARM보다 더 복잡합니다. 32비트 정렬되지 않은 접근은 항상 커널이 수정하지만, 64비트 정렬되지 않은 접근은 컴파일 플래그에 따라 사용자 공간 트랩을 등록해 처리합니다. Sun Studio C 컴파일러는 어떤 일이 일어나도록 할지를 매우 정밀하게 제어할 수 있는 특정 옵션을 가지고 있으며, 그러한 정렬되지 않은 접근을 쉽게 찾아내고 수정할 수 있는 도구도 제공합니다.
Redis에서 워드 크기가 아닌 정렬되지 않은 접근이 드물었다면, 워드 크기의 정렬되지 않은 접근은 도처에 있을 것이라고 예상할 수 있습니다. 하지만 실제로는 그렇지 않았습니다. Redis 3.0까지는 OpenBSD/SPARC 장비로 때때로 Redis를 테스트하고 수정했기 때문입니다. 그래서 가장 큰 문제는 키를 해시하는 함수였습니다. SDS라 불리던 원래의 Redis 문자열 라이브러리는 고정 크기 헤더를 가지고 있었기 때문에 키를 해시할 때 접근이 항상 정렬되어 있었습니다. Redis 3.2부터는 SDS 헤더가 가변 크기이므로 더 이상 그렇지 않습니다. 게다가 몇 년 전 마지막으로 SPARC에서 Redis를 테스트한 이후 여기저기 새로운 정렬되지 않은 접근들이 쌓여 있었습니다.
해시 함수를 수정하면서 SipHash로 교체했는데, 이는 HashDoS 공격에 대한 보안 수정이기도 합니다. 다만 현재 C와 D 라운드 수를 줄인 SipHash 변형인 SipHash1-2를 사용하고 있다는 점은 언급할 가치가 있습니다. 이는 그렇지 않으면 적지 않은 속도 저하를 피하기 위한 것이었는데, 제가 아는 한 SipHash1-2에 대한 실질적인 공격은 없을 것이며, 어쨌든 이전에 사용하던 MurmurHash2보다 확실히 더 안전합니다. MurmurHash2는 그 측면에서 너무 취약해서 시드와 무관한 충돌을 생성하는 것이 가능할 정도입니다.
제가 사용 중인 SipHash 구현은 레퍼런스 구현을 코드를 단순화하고 대소문자를 구분하지 않는 변형을 만들기 위해 약간 수정한 것입니다. 정렬되지 않은 접근을 처리하고 엔디안에 구애받지 않도록 설계되어 있습니다. 어쩌면 처음으로 제대로 작성된 해시 함수의 레퍼런스 구현을 본 것 같습니다…
다른 SPARC 수정 작업들은 친절한 Redis 사용자가 Solaris/SPARC 접근 권한을 제공해 준 덕분에 크게 수월해졌습니다. 정렬되지 않은 접근을 수정하는 과정에서 Solaris/SPARC에서 Redis를 빌드하고 테스트하는 부분도 함께 수정하려고 했기 때문에, 전반적으로 이식성을 개선하는 좋은 연습이 되었습니다. 이 작업이 완료된 후 Redis는 적어도 단독 코드에 대해서는 마침내 “정렬 안전(alignment safe)”해졌습니다. 클러스터 영역에서는 아직 할 일이 더 남아 있습니다.
라즈베리 파이에서 Redis의 성능
자, 다시 파이 이야기로 돌아오겠습니다 :-) 이렇게 작은 하드웨어에서 Redis는 얼마나 빠를까요? 시중에 파이 모델이 하나 이상 존재하므로 이 질문에는 여러 답이 있습니다. Pi 3에서의 Redis는 놀라울 정도로 빠릅니다. 제 벤치마크는 루프백 인터페이스를 통해 수행했는데, 파이 위의 Redis는 주로 로컬 프로그램이 데이터를 쓰거나 IPC와 클라우드-엣지 간 정보 교환을 위한 메시지 버스(여기서 클라우드란 어플라이언스의 중앙 서버를, 엣지란 어플라이언스의 로컬 설치를 의미합니다)로 사용될 것을 염두에 두었기 때문입니다. 하지만 이더넷 포트를 통해 접근할 때도 잘 동작합니다.
Pi 3에서는 다음과 같은 수치를 얻었습니다.
테스트 1 : 키 100만 개에 500만 번 쓰기(키 간 균등 분포). 영속성 없음, 파이프라이닝 없음. 28000 ops/sec.
테스트 2: 테스트 1과 동일하지만 8개 연산씩 묶는 파이프라이닝 사용: 80000 ops/sec.
테스트 3: 테스트 1과 동일하지만 AOF 활성화, fsync 1초: 23000 ops/sec
테스트 4: 테스트 3과 동일하지만 AOF 재작성 진행 중: 21000 ops/sec
기본적으로 Pi 3에서의 Redis는 어떤 사용 사례에도 충분히 빠릅니다. Redis는 대부분 단일 스레드로 동작하고, AOF 로그를 재작성할 때는 백그라운드 프로세스가 하나 더 있어 이중 스레드로 동작한다는 점을 고려하면, 파이에서 다른 프로세스들이 동시에 실행되는 중에도 위와 같은 성능을 기대할 수 있습니다. 요점은 이 수치들이 파이를 포화시키고 있다는 의미가 아니라는 것입니다.
오리지널 모델 B에서는 상황이 *꽤* 다릅니다. 수치는 훨씬 낮아 파이프라이닝을 사용하지 않을 때는 2000 ops/sec 정도, 파이프라이닝을 사용하면 15000 ops/sec 정도입니다. 이 큰 격차는 컨텍스트 스위치를 필요로 하는 write나 read 같은 시스템 호출의 처리 효율이 매우 낮음을 시사하는 듯합니다. 하지만 대부분의 애플리케이션에서는 여전히 충분히 괜찮은 수치입니다. Redis가 대부분의 경우 외부 클라이언트를 서비스하지 않을 것이고, 높은 부하의 데이터 로깅이 필요할 때는 파이프라이닝을 구현하는 것이 종종 간단하기 때문입니다.
하지만 지금은 Pi 3 외에 가장 흥미로운 테스트 장치 중 하나인 Pi Zero를 가지고 있지 않습니다. 그것이 어떤 수치를 보여줄지 궁금합니다. 제가 사용 중인 모델 B보다는 좋아야 할 것입니다.
파이와의 동행
Redis가 파이에서 잘 동작한다는 점에서 제가 좋아하는 한 가지는 Pi Zero 같은 제품 덕분에 라즈베리가 잠재적으로 IoT 제품을 위한 기본 플랫폼이 되어간다는 사실에 기대가 크다는 것입니다. 최종 사용자를 위한 완제품까지도 말입니다. 시간이 있다면 하드웨어 분야에서 무엇을 하고 싶은지 생각을 멈출 수가 없습니다. 센서, 디스플레이, GPIO 포트, 그리고 매우 낮은 가격은 과거에 비해 훨씬 더 간단한 방식으로 하드웨어 스타트업을 만드는 것을 가능하게 하며, 전 세계의 해커들이 이제 다양한 종류의 스마트 기기를 출시할 수 있다는 아이디어가 정말 마음에 듭니다. 파이에서(그리고 앞으로는 Android나 다른 ARM 기반 시스템에서도) 좋은 Redis 경험을 제공함으로써, 비록 주변적일지라도 이 흐름의 일부가 되고 싶습니다. Redis는 적은 자원 요구량, 추가 전용 연산, 로깅과 기기 내 데이터 분석에 모두 적합한 데이터 모델이라는 좋은 조합을 갖추고 있어 과거의 이력에 기반해 조치를 취할 수 있게 하므로, 이 분야에서 분명 도움이 될 수 있다고 믿습니다.
그래서 지금부터 라즈베리 파이는 저에게 리눅스 서버가 원래 Redis의 “표준”으로 자리 잡았던 것처럼 Redis의 주요 타깃 플랫폼 중 하나입니다. 앞으로 몇 주 동안 수정을 계속 진행할 것이며, 이 모든 것은 Redis 4.0에 포함될 예정입니다. 동시에 Redis 공식 사이트에 Redis와 파이에 관한 모든 정보를 담은 새로운 섹션을 작성할 것입니다. 다양한 장치에서의 벤치마크, 모범 사례 등입니다. 어쩌면 앞으로는 IoT 장치와 클라우드 사이에서 Redis를 데이터 버스로 활용하기 위한 개념 증명 “에이전트”도 공개할 수 있을지 모릅니다. 장치는 단순히 Redis 안에 데이터를 기록하고, 에이전트가 외부와의 연결이 가능할 때 데이터를 클라우드로 옮기며, 동시에 장치가 실행할 명령을 가져와 응답을 다시 보내는 방식입니다. 이는 스트림 자료구조가 Redis 4.2에서 제공되면 더욱 흥미로워질 것입니다.
임베디드 환경에서 Redis가 도움이 될 수 있다고 생각하는 애플리케이션이나, 그 측면에서 Redis를 더 개선하기 위해 제가 할 수 있는 일에 대한 여러분의 의견을 듣고 싶습니다.
글을 무작위로 읽기