2012-10-12 2 views
25

저는 파이썬에서 redis 캐시를 만들고 싶었습니다. 자존심이 강한 과학자처럼 벤치 마크를 통해 성능을 테스트했습니다.캐싱 응용 프로그램에서 Redis vs Disk의 성능

흥미롭게도, redis는 그렇게 잘하지 못했습니다. 파이썬이 마술 (파일 저장)을하거나, 나의 버전의 redis가 엄청나게 느리다.

이것이 내 코드가 구조화 된 방식 때문인지 또는 무엇을 알았는지는 알 수 없지만 이전보다 더 잘 할 수있을 것으로 기대하고있었습니다.

redis 캐시를 만들기 위해 이진 데이터 (이 경우 HTML 페이지)를 파일 이름에서 파생 된 키로 설정하고 만료일은 5 분입니다.

모든 경우 파일 처리는 f.read()로 수행됩니다 (이 작업은 f.readlines()보다 3 배 빠르며 이진 Blob이 필요합니다).

필자의 비교에서 누락 된 것이 있습니까? 아니면 Redis가 실제로 디스크와 일치하지 않습니까? 파이썬이 파일을 어딘가에 캐싱하고 매번 다시 액세스합니까? 왜 이것이 홍채에 접근하는 것보다 훨씬 빠릅니까?

저는 64 비트 우분투 시스템에서 모두 redis 2.8, python 2.7 및 redis-py를 사용하고 있습니다.

파이썬 객체에 파일 데이터를 저장하고 영원히 굴복시킨 함수를 만들었 기 때문에 파이썬이 특히 마술처럼 작용한다고 생각하지 않습니다.

나는 네 가지 기능은 내가 그룹화하는 것이 통화를 할 :

파일을 읽기 X 시간

레디 스 객체가 메모리에 남아있는 경우, 참조를로드하거나 (단일 새 파일을 캐시 호출되는 함수를 및 여러 개의 redis 인스턴스).

redis 데이터베이스의 결과를 생성하는 생성기를 생성하는 함수입니다 (단일 또는 다중 인스턴스의 redis).

마지막으로 파일을 메모리에 저장하고 영원히 산출합니다.

import redis 
import time 

def load_file(fp, fpKey, r, expiry): 
    with open(fp, "rb") as f: 
     data = f.read() 
    p = r.pipeline() 
    p.set(fpKey, data) 
    p.expire(fpKey, expiry) 
    p.execute() 
    return data 

def cache_or_get_gen(fp, expiry=300, r=redis.Redis(db=5)): 
    fpKey = "cached:"+fp 

    while True: 
     yield load_file(fp, fpKey, r, expiry) 
     t = time.time() 
     while time.time() - t - expiry < 0: 
      yield r.get(fpKey) 


def cache_or_get(fp, expiry=300, r=redis.Redis(db=5)): 

    fpKey = "cached:"+fp 

    if r.exists(fpKey): 
     return r.get(fpKey) 

    else: 
     with open(fp, "rb") as f: 
      data = f.read() 
     p = r.pipeline() 
     p.set(fpKey, data) 
     p.expire(fpKey, expiry) 
     p.execute() 
     return data 

def mem_cache(fp): 
    with open(fp, "rb") as f: 
     data = f.readlines() 
    while True: 
     yield data 

def stressTest(fp, trials = 10000): 

    # Read the file x number of times 
    a = time.time() 
    for x in range(trials): 
     with open(fp, "rb") as f: 
      data = f.read() 
    b = time.time() 
    readAvg = trials/(b-a) 


    # Generator version 

    # Read the file, cache it, read it with a new instance each time 
    a = time.time() 
    gen = cache_or_get_gen(fp) 
    for x in range(trials): 
     data = next(gen) 
    b = time.time() 
    cachedAvgGen = trials/(b-a) 

    # Read file, cache it, pass in redis instance each time 
    a = time.time() 
    r = redis.Redis(db=6) 
    gen = cache_or_get_gen(fp, r=r) 
    for x in range(trials): 
     data = next(gen) 
    b = time.time() 
    inCachedAvgGen = trials/(b-a) 


    # Non generator version  

    # Read the file, cache it, read it with a new instance each time 
    a = time.time() 
    for x in range(trials): 
     data = cache_or_get(fp) 
    b = time.time() 
    cachedAvg = trials/(b-a) 

    # Read file, cache it, pass in redis instance each time 
    a = time.time() 
    r = redis.Redis(db=6) 
    for x in range(trials): 
     data = cache_or_get(fp, r=r) 
    b = time.time() 
    inCachedAvg = trials/(b-a) 

    # Read file, cache it in python object 
    a = time.time() 
    for x in range(trials): 
     data = mem_cache(fp) 
    b = time.time() 
    memCachedAvg = trials/(b-a) 


    print "\n%s file reads: %.2f reads/second\n" %(trials, readAvg) 
    print "Yielding from generators for data:" 
    print "multi redis instance: %.2f reads/second (%.2f percent)" %(cachedAvgGen, (100*(cachedAvgGen-readAvg)/(readAvg))) 
    print "single redis instance: %.2f reads/second (%.2f percent)" %(inCachedAvgGen, (100*(inCachedAvgGen-readAvg)/(readAvg))) 
    print "Function calls to get data:" 
    print "multi redis instance: %.2f reads/second (%.2f percent)" %(cachedAvg, (100*(cachedAvg-readAvg)/(readAvg))) 
    print "single redis instance: %.2f reads/second (%.2f percent)" %(inCachedAvg, (100*(inCachedAvg-readAvg)/(readAvg))) 
    print "python cached object: %.2f reads/second (%.2f percent)" %(memCachedAvg, (100*(memCachedAvg-readAvg)/(readAvg))) 

if __name__ == "__main__": 
    fileToRead = "templates/index.html" 

    stressTest(fileToRead) 

이제 결과

10000 file reads: 30971.94 reads/second 

Yielding from generators for data: 
multi redis instance: 8489.28 reads/second (-72.59 percent) 
single redis instance: 8801.73 reads/second (-71.58 percent) 
Function calls to get data: 
multi redis instance: 5396.81 reads/second (-82.58 percent) 
single redis instance: 5419.19 reads/second (-82.50 percent) 
python cached object: 1522765.03 reads/second (4816.60 percent) 

결과 빨리 기능마다, b) 레디 스 디스크로부터 판독보다 느린 전화보다하는) 발전기의 흥미 및 c) 파이썬 객체에서 읽는 것은 엄청나게 빠릅니다.

왜 디스크에서 읽는 것이 redis에서 메모리 내 파일을 읽는 것보다 훨씬 빠릅니까?

편집 : 몇 가지 추가 정보 및 테스트.

나는, 결과는

if r.exists(fpKey): 
    data = r.get(fpKey) 


Function calls to get data using r.exists as test 
multi redis instance: 5320.51 reads/second (-82.34 percent) 
single redis instance: 5308.33 reads/second (-82.38 percent) 
python cached object: 1494123.68 reads/second (5348.17 percent) 


Function calls to get data using if data as test 
multi redis instance: 8540.91 reads/second (-71.25 percent) 
single redis instance: 7888.24 reads/second (-73.45 percent) 
python cached object: 1520226.17 reads/second (5132.01 percent) 

실제로 눈에 띄는 읽기 속도에 영향이없는 각 함수 호출에 새로운 레디 스 인스턴스 작성에서 크게 다르지 않다

data = r.get(fpKey) 
if data: 
    return r.get(fpKey) 

에 기능을 대체 테스트에서 테스트까지의 변동성은 이득보다 큽니다.

Sripathi Krishnan은 임의 파일 읽기를 구현할 것을 제안했습니다. 이것이 캐싱이 실제로 도움이되는 곳입니다. 이러한 결과에서 알 수 있습니다.

Total number of files: 700 

10000 file reads: 274.28 reads/second 

Yielding from generators for data: 
multi redis instance: 15393.30 reads/second (5512.32 percent) 
single redis instance: 13228.62 reads/second (4723.09 percent) 
Function calls to get data: 
multi redis instance: 11213.54 reads/second (3988.40 percent) 
single redis instance: 14420.15 reads/second (5157.52 percent) 
python cached object: 607649.98 reads/second (221446.26 percent) 

파일에 변화의 거대한 양 그래서 %의 차이는 속도 향상의 좋은 지표하지가 읽기입니다.

Total number of files: 700 

40000 file reads: 1168.23 reads/second 

Yielding from generators for data: 
multi redis instance: 14900.80 reads/second (1175.50 percent) 
single redis instance: 14318.28 reads/second (1125.64 percent) 
Function calls to get data: 
multi redis instance: 13563.36 reads/second (1061.02 percent) 
single redis instance: 13486.05 reads/second (1054.40 percent) 
python cached object: 587785.35 reads/second (50214.25 percent) 

random.choice (fileList)를 사용하여 함수를 통과 할 때마다 임의로 새 파일을 선택했습니다.

전체 요점은 누군가가 그것을 밖으로 시도하려는 경우 여기에있다 - https://gist.github.com/3885957

편집 편집 : 내가 (발전기에 대해 하나 개의 단일 파일을 호출하는 것을 인식하지 않았지만 함수 호출 및 발전기의 성능 매우 유사했다). 다음은 생성기와 다른 파일의 결과입니다.

Total number of files: 700 
10000 file reads: 284.48 reads/second 

Yielding from generators for data: 
single redis instance: 11627.56 reads/second (3987.36 percent) 

Function calls to get data: 
single redis instance: 14615.83 reads/second (5037.81 percent) 

python cached object: 580285.56 reads/second (203884.21 percent) 
+1

모든 함수 호출에서 새 redis 인스턴스를 작성하는 위치가 표시되지 않습니다. 그것은 단지 기본 주장 일 이었습니까? – jdi

+0

예, redis 인스턴스를 전달하지 않으면 함수 호출은 새 값을 가져옵니다. def cache_or_get (fp, expiry = 300, r = redis.Redis (db = 5)) : – MercuryRising

+2

실제로는 사실이 아닙니다. 이러한 기본 인수는 스크립트가로드 될 때 한 번만 평가되고 함수 정의와 함께 저장됩니다. 전화 할 때마다 평가되지 않습니다. 그것은 왜 당신이 하나를 넘기는 것 사이에 어떤 차이를 보지 못했는지, 또는 그것들을 디폴트 값으로 사용하는 것의 차이를 보지 못했음을 설명 할 것입니다. 사실 당신이하고 있던 일은 각 함수 정의에 대해 하나씩 생성하고, 그것을 전달할 때마다 하나씩 생성하는 것입니다. 2 unused connections – jdi

답변

28

이것은 사과와 오렌지를 비교 한 것입니다. http://redis.io/topics/benchmarks

Redis는 효율적인 원격 데이터 저장소입니다. Redis에서 명령이 실행될 때마다 메시지가 Redis 서버로 전송되고 클라이언트가 동기이면 응답 대기를 차단합니다. 따라서 명령 자체의 비용을 넘어 네트워크 왕복 또는 IPC 비용을 지불하게됩니다.

현대적인 하드웨어에서 네트워크 왕복 또는 IPC는 다른 작업에 비해 놀랍도록 비쌉니다. 이것은 여러 가지 요인에 기인한다 :

  • (리눅스/유닉스 보장 할 수 없습니다) 운영 체제 스케줄러
  • 메모리 캐시 미스가 비싸의 지연 시간 (주로 네트워크) 매체의 원시 대기 시간 클라이언트 및 서버 프로세스가 스케줄링되는 동안 캐시 누락 확률이 증가합니다. 하이 엔드 박스에
  • , NUMA 부작용

지금의 결과를 살펴 보자.

Generators를 사용한 구현과 함수 호출을 사용하는 구현을 비교하면 Redis에 대한 왕복 수는 동일하지 않습니다. 생성기를 사용하면 간단하게 :

while time.time() - t - expiry < 0: 
     yield r.get(fpKey) 

반복 당 1 라운드 트립. 이 함수를 사용하면 다음을 얻게됩니다.

if r.exists(fpKey): 
    return r.get(fpKey) 

반복 당 2 왕복. 발전기가 더 빠를 것입니다.

물론 최적의 성능을 위해 동일한 Redis 연결을 재사용해야합니다. 체계적으로 연결/연결 해제하는 벤치 마크를 실행할 필요가 없습니다.

마지막으로 Redis 호출과 파일 읽기 간의 성능 차이와 관련하여 로컬 호출을 원격 호출과 단순히 비교하는 것입니다.파일 읽기는 OS 파일 시스템에 의해 캐시되므로 커널과 파이썬 간의 빠른 메모리 전송 작업입니다. 여기에 관련된 디스크 I/O는 없습니다. Redis를 사용하면 왕복 비용을 지불해야하므로 훨씬 느립니다.

+4

당신은이 하나에서 나를 때려! 나는 a) 레디 스에 대한 exist() 체크 제거, b) 레디스 커넥션을 재생성하는 대신에 영구적 인 레디 스 커넥션을 사용하고, c) 하드 코딩 된 단일 파일 대신 랜덤 파일을 읽은 후에 벤치 마크를 실행하도록 OP에게 요청할 것이다. –

+0

추가 정보가 추가되었습니다. 랜덤 읽기는 캐싱이 실제로 도움이되는 곳입니다. Redis 인스턴스를 재사용하고 새 인스턴스를 생성하는 것의 차이점이 실제로 없다는 것은 나에게 이상한 것 같습니다. 생성에 많은 오버 헤드가 있어서는 안됩니다 (인증을 통해 얼마나 많이 변경 될지 궁금합니다). – MercuryRising

+0

인증 비용은 연결 직후에 발생하는 왕복 1 회입니다. 클라이언트가 서버와 동일한 호스트 상에 있기 때문에 새로운 Redis 인스턴스를 생성하는 것은 저렴합니다. –