[입 개발] Redis Pipeline and Transaction(Multi/Exec)

해당 블로그는 KT Olleh UCloudBiz의 지원을 받고 있습니다.

Redis Pipeline에 대한 내용을 보고 나면, 다음과 같은 질문이 꼭 나오게 됩니다. Pipeline을 이용하면 성능은 좋아지는데, Redis는 Single Thread 라, Pipeline으로 처리하는 동안에는 다른 클라이언트의 작업을 처리못하게 되는거 아닌가요? 라는 질문입니다.

먼저 결론부터 내리자면, Pipeline 형태로 데이터를 전달하더라도 중간에 명령을 수행할 수 있습니다.(진짜루?)

그럼 다음과 같이 간단하게 테스트를 해보도록 하겠습니다. 테스트에는 KT Olleh UcloudBiz 의 8vCore, 16GB 머신을 이용했습니다.

일단 지난번의 테스트 코드를 다시 사용합니다. 다만 테스트를 위해서 프로세스는 하나만 띄웁니다.

from multiprocessing import Process
import redis
import random
import string

def id_generator(size=6, chars=string.ascii_uppercase + string.digits):
    return ''.join(random.choice(chars) for x in range(size))

data = id_generator(512, "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz!@#$%^&*()")

def f(idx):
    print 'PR', idx
    r = redis.StrictRedis(host='172.27.217.193', port=6379, db=0)
    pipeline = r.pipeline()
    for i in xrange(idx * 1024 * 1024 * 2, (idx+1)*1024*1024 * 2):
        key = 'PR%s'%(i)
        pipeline.get(key)
        if (i != idx * 0124 * 1024 and i%1024==0):
            pipeline.execute();

if __name__ == '__main__':
    print data
    pids = []
    for i in xrange(1):
        p = Process(target=f, args=(i,))
        pids.append(p)
        p.start()

    for pid in pids:
        pid.join()

이제 이 코드를 실행하고 모니터링을 해보면… 이상한 결과가 보여집니다.

redis-pipeline-with-transaction

분명히 문제 없다고 해놓고, 시간이 뻥뛰고 있습니다. redis-stat이라는 모니터링 툴 역시 내부적으로 info 명령을 주기적으로 전달하는 건데, 저렇게 시간이 확 지난거면… 중간에 명령이 실행이 안되는거지 않느냐!!! 라는 생각이 부글부글 드실껍니다.

여기에 비밀이 하나 있습니다. 제가 사용한 Python Redis Clinet 인 redis-py 가 Redis 의 다른 특성을 함께 사용하고 있어서 그렇습니다. 여기서 설명할 것이 Redis Transaction Multi/Exec 입니다. Transaction 이라는 것은 여러 개의 명령을 하나의 성공/실패의 그룹으로 묶어서 수행하는 것을 말합니다. 예를 들어 다음과 같은 명령들이 있다고 합니다.

set A 123
get A

당연히 기대하는 값은 “123” 이겠지만, set A 123 명령 다음에 set A 23이라는 명령을 다른 클라이언트에 의해서 실행이 되면 기대했던 결과를 얻을 수가 없습니다. 그래서 redis 2.6 부터는 MULTI/EXEC 라는 Transaction을 지원하기 위한 명령을 지원하고 있습니다. MULTI/EXEC 의 특징은 그 안의 명령을 모두 한꺼번에 실행시켜준다라는 특성이 있습니다. 즉

MULTI
set A 123
get A
EXEC

의 마지막 결과는 항상 123 입니다. 이것의 구현은 어떻게 되는 것일까요? 아주 간단합니다. MULTI가 나오면 Redis 가 해당 Client로 할당된 큐에 EXEC가 나오기 전까지 모든 명령을 담아두고 있다가 EXEC 명령이 들어오면, 그냥 모두 한꺼번에 수행시켜줍니다. 그래서 이 안에 blocking 관련 BLPOP등의 명령은 데이터가 없으면 바로 실패하게 됩니다. 그리고 MUTLI/EXEC 사이에 명령이 많으면, 이걸 수행하기 위해서, 그 동안에는 다른 클라이언트의 명령을 처리하지 못합니다.(Single Thread 라…)

왜 이 설명을 했냐 하면, Python Redis Client 는 기본적으로 pipeline을 실행할 때, MULTI/EXEC를 사용합니다.(이 특성 때문에 redis-py 의 pipeline 기능을 twemproxy와 함께 사용하려고 하면 지원하지 않는 명령이라 제대로 실행이 되지 않습니다.)

그럼 어떻게 해야 하는가? 호출시에 transaction=False 를 넣어주면 됩니다. 다음과 같습니다.

def f(idx):
    print 'PR', idx
    r = redis.StrictRedis(host='172.27.217.193', port=6379, db=0)
    pipeline = r.pipeline()
    for i in xrange(idx * 1024 * 1024 * 2, (idx+1)*1024*1024 * 2):
        key = 'PR%s'%(i)
        pipeline.get(key)
        if (i != idx * 0124 * 1024 and i%1024==0):
            pipeline.execute();

자 이렇게 바꾸고 다시 실행 후 모니터링 해보도록 하겠습니다.

redis-pipeline-without-transaction

자, 이제 2초마다 제대로 모니터링 되는 걸 알 수 있습니다. 즉 제대로 중간에 명령이 끼어들 수 있다는 겁니다.
(제가 거짓말 한게 아닙니다.)

정리하면 다음과 같습니다.
1. Redis Pipeline은 중간에 명령을 허용한다. 즉 이로 인해 뭔가 데이터가 바뀔 수도 있다.(다른 클라이언트가 변경할 수 있으니…)
2. Multi/Exec를 쓰면, 그 사이의 명령이 전부 한꺼번에 수행된다. 이 안의 작업이 길면 다른 클라이언트들의 명령이 늦게 처리된다.
3. 사용시에 라이브러리를 잘 확인하자. 위와 같은 이유로 안될 수 있다.

About these ads

2 comments on “[입 개발] Redis Pipeline and Transaction(Multi/Exec)

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s