원문(http://inocrazy.com/docs/10)에서 좀 더 깔끔하게 보실 수 있습니다.
개요
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 설정을 살펴보면서 정리하자.
- 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들을 인수로 전달받는다.
- 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가 일정 시간에 한번씩 업데이트되어야 하는 경우에 이 옵션을 사용할 수 있다. |
- cache.write('inocrazy', 'Crazy For Innovation!', :raw => true)
- # :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 디렉토리의 환경별 설정파일)에서 다음과 같이 설정할 수 있다.
- # 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는 FileStore나 MemoryStore 중 하나를 사용하게 된다. tmp/cache 디렉토리가 존재할 경우, FileStore가 사용되며, 그렇지 않을 경우 MemoryStore가 기본이 된다. Rails에서는 기본적으로 global cache를 생성한다. 실제 코드에서 이를 사용하기 위해서는 RAILS_CACHE 전역변수나 Rails.cache를 통해서 접근할 수 있다.
- # '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를 더욱 향상시켜보자. ^^
참조
- ActiveSupport::Cache
- Caching in Rails 2.1
- Rails 2.1: now with better integrated caching
- Changeset - 8393
이 글은 스프링노트에서 작성되었습니다.



::: 사람과 사람의 교감! 人터넷의 첫 시작! 댓글을 달아주세요! :::