Rails 2.1 : ActiveSupport::Cache

Last Modified on 2008/07/02 04:21 by 크레이지DK

개요

Rails 2.1에서는 ActiveSupport::Cache 모듈이 추가되면서 간편하게 cache를 설정하고 사용하는 것이 가능해졌다. cache를 실제로 저장하고 처리하는 caching store도 여러가지가 제공되므로 자신의 편의에 맞는 것을 사용할 수 있게 되었다. 이번 문서에서는 Rails의 새로운 Cache 기능에 대해서 자세히 살펴보자.

 

 

ActiveSupport::Cache::Store

ActiveSupport::Cache에서 가장 중요한 클래스는 Store 클래스이다. 이 클래스는 다양한 caching store를 위한 인터페이스 역할을 한다. FileStore, MemCacheStore 등의 클래스가 이 클래스를 상속해서 실질적인 처리를 구현하고 있다. Store 클래스의 대표적인 인스턴스 메소드는 다음과 같다.

메소드 내용
read(key, options = nil) key에 해당하는 값을 읽어온다.
write(key, value, options = nil) key에 해당하는 값을 저장한다.
fetch(key, options = nil) key에 해당하는 값이 저장되어 있으면 읽어오고, 값이 없을 경우 전달되는 block을 실행시킨 결과를 저장 후 리턴한다.
delete(key. options = nil) key에 해당하는 값을 삭제한다.
delete_matched(matcher, options = nil)                  정규식 matcher와 matching되는 결과만을 삭제한다. MemCacheStore에서는 구현되어 있지 않다.
exist?(key, options = nil) key에 해당하는 값이 존재하는지 여부를 리턴한다.
increment(key, amount = 1) key에 해당하는 값이 존재하는 경우, amount만큼 증가시킨다.
decrement(key, amount = 1) key에 해당하는 값이 존재하는 경우, amount만큼 감소시킨다.

어플리케이션 개발 시에 cache의 처리 상황을 파악하기 쉽도록, 각 메소드가 실행되는 경우 debug 수준의 로그를 남기게 된다. 따라서 처리된 결과값이 cache로부터 가져온 것인지, 혹은 DB를 access해서 가져온 것인지를 모니터링하면서 개발을 진행할 수 있다.

 

또한 ActiveSupport::Cache에는 ThreadSafety라는 모듈이 선언되어 있다. 모듈명에서도 알 수 있듯이 이 모듈은 thread safety를 위해서, read, write, delete 메소드를 Mutex#synchronize 메소드로 감싸는 wrapping 메소드를 제공한다. caching store에서 thread safety를 가능하게 하기 위해서는 Store#threadsafe! 메소드를 실행하면 된다.

 

위의 기본적인 CRUD 관련 메소드뿐만 아니라, 두 개의 클래스 메소드도 제공한다. lookup_store(*store_option)은 인수로 전달된 option에 해당하는 caching store 인스턴스를 리턴한다. environment.rb의 config.cache_store 설정에서 내부적으로 사용되는 메소드이다. 사용가능한 인수들은 config.cache_store 설정을 살펴보면서 정리하자.

 

  1. new_cache = ActiveSupport::Cache.lookup_store(:file_store, 'tmp/new_cache')

 

또한 expand_cache_key(key, namespace = nil) 클래스 메소드는 전달된 key를 확장하게 된다. Store 클래스에서 각 cache는 결국 hash 형식으로 처리되게 된다. 따라서 여러가지 caching되는 값들이 중복되지 않기 위해서는 유일한 key를 필요로 하게 된다. 이를 위해서 이 메소드에서는 다음과 같은 형식으로 키를 확장한다.

 

key => [namespace/][RAILS_CACHE_ID or RAILS_APP_VERSION/]<cache_key or to_param>

 

각 파트들은 해당 값이 인수로 전달되거나 설정된 경우에 추가되게 된다. 세번째 파트에서 key가 :cache_key라는 메소드가 있는 경우, 해당 값을 설정하게 된다. ActiveRecord::Base에는 cache_key라는 메소드가 추가되어서 쉽게 caching 처리가 가능해졌다. (<model_name>/<id>-<updated_at> 형식) 만약 key가 Array로 전달된 경우는 모든 요소들에 대해서 expance_cache_key 메소드가 실행된 결과가 to_param 형식으로 리턴된다.

 

 

Caching Stores

Rails 2.1에서는 5개의 caching store을 제공한다. 각각은 다음과 같다.

Caching Store 내용
FileStore cache를 개별 파일에 저장하고 읽어온다.
MemoryStore cache를 hash 변수에 저장하고 읽어온다.
DRbStore 메모리 대신 DRb server에 cache를 사용하는 것을 제외하곤, MemoryStore와 동일하다.
MemCacheStore memcache를 사용하여 저장하고 읽어온다
CompressedMemCacheStore read/write 시에 Gzip을 이용해서 압축과 해제를 하는 것을 제외하곤, MemCacheStore와 동일하다.

위와 같이 5가지의 caching store를 자신의 환경에 맞게 선택해서 사용하는 것이 가능하다. 저장공간을 기준으로 나누었을 때, 크게 File, Memory, Shared Memory에 저장하는 경우로 나누어볼 수 있다.

 

FileStore

FileStore에서는 각각의 cache를 개별 파일에 저장하고 읽어오게 된다. 추가적인 config.cache_store 설정이 없을 경우, tmp/cache 디렉토리가 존재한다면 Rails 2.1에서는 기본적으로 FileStore를 기본 caching store로 설정하게 된다. 예를 들어, cache가 저장되는 곳이 tmp/cache이고, inocrazy=Crazy For Innovation! 이라는 key=value를 저장한다고 가정하면, 실제 cache는 tmp/cache/inocrazy.cache 파일에 "Crazy For Innovation!"이라는 값을 쓰게 된다.

 

FileStore의 장점은 추가적인 설정없이 간편하게 사용할 수 있다는 것이다. 반면 매번 cache를 읽고 쓸 때마다 File IO가 발생하게 되므로, 상대적으로 속도가 느리다. 또한 app. server가 여러 대인 경우, cache가 각 서버에 저장되므로 정상적으로 사용할 수 없다.

 

MemoryStore

MemoryStore는 일반적인 hash 변수에 cache를 저장하고 읽어온다. FileStore와 같이 간편하게 사용할 수 있고, 또한 FileStore에 비해 빠르다는 장점이 있다. 하지만 마찬가지로 여러 대의 app. server에서는 사용할 수 없다.

 

DRbStore

DRbStore는 cache 저장을 위해서 DRb server를 사용한다는 점을 제외하고는 MemoryStore와 동일하다. (실제로 MemoryStore를 상속받아서 구현되었다.) MemoryStore의 장점에, 분산된 app. server에서도 사용할 수 있다는 추가적인 장점을 가지고 있다.

 

MemCacheStore

memcache를 이용해서 cache를 처리한다. 여러 대의 memcache server를 이용해서 효율적인 caching 처리를 할 수 있다. Rails 2.1에서는 ActiveSupport에 memcache-client 1.5.0이 포함되므로써 추가적으로 gem을 설치할 필요가 없게 되었다.

 

CompressedMemCacheStore

CompressedMemCacheStore는 MemCacheStore를 상속하여 구현한 클래스로서, read/write 시에 ActiveSupport::Gzip을 이용하여 압축과 해제를 추가적으로 하게 된다.

 

 

MemCacheStore 자세히 살펴보기

MemCacheStore는 실제 서비스에서 가장 많이 사용되므로 여기서 좀 더 자세히 살펴보자.

 

MemCache 설정

MemCacheStore는 다음과 같이 서버 목록과 option들을 인수로 전달받는다.

 

  1. cache = ActiveSupport::Cache.lookup_store(:mem_cache_store, '192.168.10.1:11211', '192.168.10.2:11212', :namespace => 'inocrazy')

 

memcache는 기본적으로 여러 대의 분산된 memcache server를 이용할 수 있으므로, 해당 서버들의 목록을 설정할 수 있다. 또한 각 서버의 weight를 설정함으로써 처리 빈도를 조절할 수 있다. 서버는 hostname[:port][:weight]와 같은 형식으로 설정한다.

 

또한 사용가능한 옵션들은 다음과 같다.

옵션 내용
:namespace cache의 namespace를 설정한다. memcache server는 여러 서비스가 함께 사용하게 되므로 서비스 간에 키의 중복이 발생할 수 있다. 이 namespace를 접두어로 붙임으로써, 유일한 키로 설정되도록 한다.
:readonly memcache를 읽기 전용으로 사용한다. 따라서 cache를 쓰려고 하는 경우, 예외를 발생시키게 된다.
:multithread thread safety를 위해서 cache access를 Mutex로 wrapping한다.
read/write 옵션

MemCacheStore에서 read/write 메소드의 경우, 다음과 같은 추가적인 옵션을 사용할 수 있다.

옵션 내용
:raw memcache-client에서는 값을 저장하고 가져오는 경우, Marshal#dump와 Marshal#load 메소드를 사용한다. 만약 :raw가 true로 설정되게 되면, marshaling되지 않은 raw value로 저장하고 읽어오게 된다. (기본값 false)
:unless_exist write 메소드에서 사용되는 옵션으로 :unless_exist가 true인 경우, 값이 존재하지 않는 경우에만 값을 저장하게 된다. 기본적으로는 값이 존재하는 경우, 기존값을 대체한다. (기본값 false)
:expires_in write 메소드에서 사용되는 옵션으로 cache가 유효한 시간을 초 단위로 설정한다. cache가 일정 시간에 한번씩 업데이트되어야 하는 경우에 이 옵션을 사용할 수 있다.
  1. cache.write('inocrazy', 'Crazy For Innovation!', :raw => true)
  2. # :unless_exist가 true로 설정되었고, 이미 값이 존재하므로 저장되지 않는다.
    cache.write('inocrazy', 'InoCrazy', :unless_exist => true, :expires_in => 10.miutes)
    cache.read('inocrazy', :raw => true)
    => 'Crazy For Innovation!

 

 

Rails에서 Cache

지금까지 ActiveSupport::Cache에 대해서 자세히 살펴보았다. 이제 Rails에서 실제로 caching store를 설정하고, 사용하는 방법에 대해서 살펴보자. 특정 caching store를 설정하기 위해서는 config/environment.rb (또는 config/environments 디렉토리의 환경별 설정파일)에서 다음과 같이 설정할 수 있다.

 

  1. # FileStore 사용. '/tmp/cache'를 cache 저장소로 설정.
    config.cache_store = :file_store, '/tmp/cache'
    # MemoryStore 사용
    config.cache_store = :memory_store
    # DRbStore 사용, 192.168.10.1:9192의 drb server로 설정.
    config.cache_store = :drb_store, 'druby::/192.168.10.1:9192'
    # MemCacheStore 사용. 192.168.10.1:11211, 192.168.10.2:11212를 memcache server로 설정
    config.cache_store = :mem_cache_server, '192.168.10.1:11211', '192.168.10.2:11212', :namespace => 'inocrazy'
    # CompressedMemCacheStore 사용
    config.cache_store = :compressed_mem_cache_store, 'localhost'

 

위에서 언급하였듯이, 위의 설정은 내부적으로 ActiveSupport::Cache.lookup_store 메소드를 통해서 cache 인스턴스를 설정하게 된다. 위의 설정이 존재하지 않는 경우의 기본 caching store는 FileStoreMemoryStore 중 하나를 사용하게 된다. tmp/cache 디렉토리가 존재할 경우, FileStore가 사용되며, 그렇지 않을 경우 MemoryStore가 기본이 된다. Rails에서는 기본적으로 global cache를 생성한다. 실제 코드에서 이를 사용하기 위해서는 RAILS_CACHE 전역변수나 Rails.cache를 통해서 접근할 수 있다.

 

  1. # 'date'라는 key를 갖는 cache에 쓰고 읽는다.
    Rails.cache.write('date', Date.today)
    Rails.cache.read('date')
    => '2008-07-02'

    # 'time'이라는 cache가 존재하지 않으므로 block의 실행결과를 cache에 저장하고, 리턴한다.
    Rails.cache.fetch('time') { Time.now }
    => Wed Jul 02 02:44:34 +0900 2008
    # 이미 'time'이라는 cache가 존재하므로 cache에서 값을 읽어온다.
    Rails.cache.fetch('time') { Time.now }
    => Wed Jul 02 02:44:34 +0900 2008

 

 

정리

Rails 2.1에서는 기존에 복잡한 설정과 추가적인 gem/plugin으로 구현해야 했던, cache 처리를 ActiveSupport::Cache를 통해서 간편하게 사용할 수 있게 되었다. 이 cache를 적절히 사용함으로써 Rails 서비스의 Performance를 더욱 향상시켜보자. ^^

 

참조