Redis, Commands

3 분 소요

Introduction

집에만 박혀있는 설날에 포스트를 작성한다.

지금 듣고 있는 노래 새소년 (SE SO NEON) ‘자유(Jayu)’다.

Commands

레디스에서는 네트워크 통신 지연을 줄여 줄 여러가지 기능을 제공한다. 이와 관련된 커맨드를 알아보도록 하자.

Pub/Sub

Pub/Sub는 수신자에게 메세지를 직접적으로 전달하지 않는 패턴이다. Publisher는 채널에 메세지를 보내고 Subscriber는 채널을 구독 중이라면 메세지를 전달받는다.

아래는 Pub/Sub 패턴을 사용하는 예다.

  • 뉴스와 날씨 정보
  • 채팅 서비스
  • 푸쉬메세지
  • SaltStack과 같은 Remote code excution

PUBLISH

PUBLISH를 통해 레디스 채널에 메세지를 보낸다.

var redis = require("redis");
var client = redis.createClient();
var channel = process.argv[2]; // 1
var command = process.argv[3]; // 2
client.publish(channel, command); // 3
client.quit();

SUBSCRIBE, UNSUBSCRIBE

한 개 이상의 채널을 구독 혹은 구독취소한다.

PSUBSCRIBE, PUNSUBSCRIBE도 비슷한 동작을 하지만 glob-style pattern을 지원한다.

glob-style pattern

  • h?llo subscribes to hello, hallo and hxllo
  • h*llo subscribes to hllo and heeeello
  • h[ae]llo subscribes to hello and hallo, but not hillo
var os = require("os"); // 1
var redis = require("redis");
var client = redis.createClient();
var COMMANDS = {}; // 2
COMMANDS.DATE = function() { // 3
 var now = new Date();
 console.log("DATE " + now.toISOString());
};
COMMANDS.PING = function() { // 4
 console.log("PONG");
};
COMMANDS.HOSTNAME = function() { // 5
 console.log("HOSTNAME " + os.hostname());
};
client.on("message", function(channel, commandName) { // 6
 if (COMMANDS.hasOwnProperty(commandName)) { // 7
 var commandFunction = COMMANDS[commandName]; // 8
 commandFunction(); // 9
 } else { // 10
 console.log("Unknown command: " + commandName);
 }
});
client.subscribe("global", process.argv[2]); // 11

Transcations

Transcationatomic하게 레디스 커맨드를 실행시킬 수 있다.

MULTI, EXEC

MULTI는 트랜잭션에 시작을, EXEC가 끝을 나타낸다. 모든 커맨드는 대기열에 올라 EXEC 커맨드가 동작할 때 서버로 보내진다.

EXEC 커맨드 대신 DISCARD를 사용하여 트랜잭션이 실행되지 않도록 할 수 있다.

Flushes all previously queued commands in a transaction and restores the connection state to normal.

SQL과는 달리 트랜잭션으로 발생한 변경사항은 트랜잭션이 도중에 오류를 일으켜도 롤백이 불가능하다. 중간에 실패한 커맨드는 무시한 체 다음 명령어를 계속해서 실행한다.

모든 커맨드가 한 번에 대기열에 올라가기 때문에 트랜잭션 내부 상태에 따라 다른 결정이 불가능하다.

아래는 통장에 상태에 따라 DISCARD 혹은 입금을 진행하는 예제코드다.

var redis = require("redis");
var client = redis.createClient();
function transfer(from, to, value, callback) { // 1
 client.get(from, function(err, balance) { // 2
 var multi = client.multi(); // 3
 multi.decrby(from, value); // 4
 multi.incrby(to, value); // 5
 if (balance >= value) { // 6
 multi.exec(function(err, reply) { // 7
 callback(null, reply[0]); // 8
 });
 } else {
 multi.discard(); // 9
 callback(new Error("Insufficient funds"), null); // 10
 }
 });
}

WATCH, UNWATCH

WATCH는 트랜잭션에서 현재 등록된 키들이 모두 변경되지 않았다는 것을 확인할 수 있다.

EXEC가 호출되면, 트랜잭션의 동작여부와 상관없이 등록된 모든 키는 UNWATCH 상태가 된다. 클라이언트 연결이 종료될 경우에도 이와 같은 동작을 한다.

UNWATCH는 인자 없이 호출 시 등록된 모든 키를 제거한다. 이는 트랜잭션 내부에서 해당 키들이 필요없어지고 새로운 키들을 이용할 경우에 매우 유용하다.

Optimistic lockgin using check-and-set

WATCH는 트랜잭션에서 check-and-set(CAS) 동작을 제공한다.

위 커맨드는 특정 키가 변경되는 것을 감시하며, EXEC 커맨드가 시작되기 전에 만약 하나의 키 값이라도 변경될 시, 현재 트랜잭션을 기각한다.

var redis = require("redis");
var client = redis.createClient();
function zpop(key, callback) { // 1
 client.watch(key, function(watchErr, watchReply) { // 2
 client.zrange(key, 0, 0, function(zrangeErr, zrangeReply) { // 3
 var multi = client.multi(); // 4
 multi.zrem(key, zrangeReply); // 5
 multi.exec(function(transactionErr, transactionReply) { // 6
 if (transactionReply) {
 callback(zrangeReply[0]); // 7
 } else {
 zpop(key, callback); // 8
 }
 });
 });
 });
}
client.zadd("presidents", 1732, "George Washington");
client.zadd("presidents", 1809, "Abraham Lincoln");
client.zadd("presidents", 1858, "Theodore Roosevelt");
zpop("presidents", function(member) {
 console.log("The first president in the group is:", member);
 client.quit();
});

Pipelines

파이프라인은 레디스 서버에 복수의 요청을 한 번에 처리할 수 있도록 한다.

Redis는 고성능의 key-value store지만, TCP 기반의 클라이언트-서버 모델을 따르기에 아래와 같이 동작하고, 네트워크 IO에 대한 병목이 존재할 수밖에 없습니다.(물리적인 네트워크 지연, 3-way handshake 등의 이유로)

PlanB의 백엔드 엔지니어링

Round Trip Time(RTT)란 커맨드를 전송하고 그에 대한 응답을 레디스 서버로부터 얻는데까지 걸리는 시간을 의미한다. 예를들면 \(n\)개의 커맨드를 전송할 경우 \(n\)개 RTT를 받게 되는 것이다.

이미지

레디스는 이러한 병목 현상 완화를 위해 파이프라인을 제공한다. 자세한 코드는 5장에서 볼 수 있지만, 파이프라인을 사용한다면 아래와 같이 통신이 이루어질 것이다.

이미지

마치며,

오늘은 레디스가 제공하는 여러 커맨드를 알아보았다.

현 챕터에서는 자료구조에 대한 튜닝 혹은 기본설정 또한 다룬다. 꼭 다루고 싶은 주제가 아니기에 위 포스트에선 생략했다. 나중에 기회가 되면 다루고 싶긴 하다

Refernce
  • Maxwell Dayvson Da Silva, Redis Essentials
  • https://redis.io/topics