Solid Cache (Rails)
Redis 대신 SSD — 데이터베이스 기반 Rails 캐시 스토어
GitHub: rails/solid_cache
Solid Cache는 37시그널스가 만든 DB 기반 캐시 스토어입니다. "메모리(RAM)는 비싸고 SSD는 싸다"는 단순한 관찰에서 출발합니다. Redis의 메모리 제한(수 GB) 대신 SSD(수 TB)를 활용하면, 훨씬 큰 캐시를 훨씬 저렴하게 운영할 수 있습니다.
핵심 아이디어
캐시 항목을 DB 테이블의 행(row)으로 저장합니다.
solid_cache_entries
├── key # 캐시 키 (문자열)
├── key_hash # SHA256 → signed int64 (인덱스용)
├── value # 직렬화된 캐시 값 (blob)
├── byte_size # 값의 바이트 크기
└── created_at # 생성 시각 (별도 인덱스 없음, ID 순서 = 시간 순서)
아키텍처
app/models/solid_cache/
└── entry.rb # AR 모델 (캐시 항목)
├── entry/expiration.rb # 만료 로직 (FIFO 삭제)
└── entry/size/ # 크기 추정
lib/solid_cache/
├── store.rb # ActiveSupport::Cache::Store 서브클래스
├── store/
│ ├── api.rb # read/write/delete 구현
│ ├── connections.rb # DB 연결 관리
│ ├── entries.rb # Entry 모델 호출
│ ├── expiry.rb # 쓰기 기반 만료 트리거
│ ├── failsafe.rb # 에러 핸들링
│ └── stats.rb # 통계
├── connections/
│ ├── single.rb # 단일 DB
│ ├── sharded.rb # 샤딩 (Maglev 해싱)
│ └── unmanaged.rb # 외부 관리
└── maglev_hash.rb # Google Maglev 일관 해싱
캐시 읽기/쓰기
쓰기 (write)
# Rails.cache.write('key', value)
Entry.write_multi(entries)
# → upsert_all로 일괄 삽입/업데이트 (1000건 배치)
# → 쓰기 후 확률적으로 만료 트리거
읽기 (read)
# Rails.cache.read('key')
Entry.read_multi(keys)
# → key_hash IN 절로 조회 (SHA256 해시 인덱스)
# → uncached(dirties: false)로 쿼리 캐시 비활성화
만료 메커니즘 (핵심!)
Redis의 TTL 방식이 아닌, 쓰기 빈도에 비례한 확률적 만료를 사용합니다:
쓰기 발생
│
├─ track_writes(count) — 카운터 증가
│
├─ 카운터가 batch_size의 50% 도달?
│ ├─ Yes → expire_later 실행
│ └─ No → 다음 쓰기까지 대기
│
└─ 만료 실행 (비동기 스레드 또는 Job)
├─ max_age 초과 항목 삭제
├─ max_entries 초과 시 가장 오래된 항목 삭제 (FIFO)
└─ max_size 초과 시 크기 기반 삭제
EXPIRY_MULTIPLIER = 2: 쓰기 1건당 2건분의 만료 압력을 가합니다. 캐시가 무한히 커지지 않도록 쓰기보다 빠르게 정리합니다.
만료 시 동시 작업 충돌 방지: 후보의 3배를 가져온 뒤 랜덤 샘플링으로 선택합니다.
샤딩 (Maglev Consistent Hashing)
Google Maglev 논문 기반의 일관 해싱으로 여러 DB에 캐시를 분산합니다:
테이블 크기 2053 (소수), CRC32로 키 해싱
샤드 추가/제거 시 최소한의 키만 재분배
database.yml에서 여러 DB를 설정하면 자동 샤딩
설정 옵션
| 옵션 | 기본값 | 설명 |
|---|---|---|
max_age |
2주 | 최대 캐시 수명 |
max_entries |
무제한 | 최대 항목 수 |
max_size |
무제한 | 최대 캐시 크기 |
expiry_batch_size |
100 | 한 번에 만료시킬 항목 수 |
expiry_method |
:thread |
만료 방식 (:thread 또는 :job) |
Redis vs Solid Cache
| Redis | Solid Cache | |
|---|---|---|
| 저장소 | RAM (비쌈) | SSD (저렴) |
| 캐시 크기 | ~수 GB | ~수 TB |
| 속도 | ~0.1ms | ~1-5ms |
| 만료 | TTL 기반 | 쓰기 비례 확률적 |
| 운영 | Redis 서버 필요 | DB만 필요 |
| 복잡성 | 인프라 추가 | 제로 |
트레이드오프: 약간 느리지만(ms 수준), 훨씬 큰 캐시를 훨씬 저렴하게 운영할 수 있습니다. 대부분의 Rails 앱에서 이 차이는 체감되지 않습니다.
구조 다이어그램
캐시 저장 구조
캐시 읽기/쓰기 플로우
만료 메커니즘 (TTL 아닌 확률적 만료)
Redis vs Solid Cache
샤딩 (Maglev Consistent Hashing)
핵심 포인트
GitHub에서 rails/solid_cache 저장소 열기
app/models/solid_cache/entry.rb → 캐시 항목 모델 분석
app/models/solid_cache/entry/expiration.rb → 만료 로직 분석
lib/solid_cache/store.rb → ActiveSupport::Cache::Store 서브클래스 구조
lib/solid_cache/store/expiry.rb → 쓰기 기반 확률적 만료 트리거
lib/solid_cache/maglev_hash.rb → 일관 해싱 구현 분석
lib/solid_cache/connections/ → 단일 DB vs 샤딩 연결 관리
db/migrate/ → solid_cache_entries 테이블 스키마 확인
장점
- ✓ Redis 불필요 — 운영 인프라 단순화
- ✓ SSD 활용 — RAM보다 훨씬 큰 캐시 가능
- ✓ ActiveSupport 호환 — Rails.cache API 그대로 사용
- ✓ 별도 설정 없이 Rails 8에서 기본 동작
- ✓ Maglev 해싱 — 우아한 샤딩 지원
- ✓ 암호화 지원 — ActiveRecord Encryption 활용
단점
- ✗ Redis보다 느림 (RAM ~0.1ms vs SSD ~1-5ms)
- ✗ DB 부하 증가 — 별도 캐시 DB 권장
- ✗ 확률적 만료 — TTL처럼 정확한 만료 시점 보장 안 됨
- ✗ 고성능 실시간 시스템에는 Redis가 여전히 유리
- ✗ 쿼리 캐시 비활성화로 항상 DB 히트