AppSignal을 사용하는 앱이 실행 중인 모든 서버는 30초마다 샘플과 메트릭 데이터를 저희 Push API로 전송합니다.
각 요청에는 해당 데이터가 어떤 앱에서 전송된 것인지 식별하기 위한 키가 포함되어 있습니다. 이를 확인하려면 수신되는 모든 요청에 대해 데이터베이스를 조회해 해당 앱을 찾아야 합니다. 월 300억 건에 달하는 요청을 처리하는 만큼, 저희는 AppSignal의 속도를 높이기 위해 쿼리 수를 줄일 방법을 끊임없이 고민하고 있습니다.
캐싱 도입 후 오히려 늘어난 쿼리
데이터베이스 클러스터의 부하를 줄이기 위해 저희는 캐싱을 도입했습니다. 데이터베이스에서 앱 정보를 가져올 때마다 Memcached에 1분간 저장하는 방식입니다. 그런데 이 변경 사항을 프로덕션 환경에 배포한 결과, 캐시 적용 전보다 쿼리 수가 오히려 더 많아진 것을 발견했습니다. 캐시가 너무 자주 무효화되고 있는 듯했지만, 정확히 어디서 그런 일이 벌어지는지 알 수 없었습니다.
원인을 파악하기 위해 저희는 커스텀 메트릭을 추가해 캐시가 부적절하게 무효화되는 지점을 찾아내기로 했습니다.
캐시 무효화 지점 분석
캐시가 무효화되는 지점은 여러 곳이 있었습니다. 대표적으로 푸시 처리 시간(push processed time)이 갱신될 때와 새로운 네임스페이스(namespace)가 감지될 때였습니다.
# 마지막 푸시 처리 시간 업데이트
if app.last_push_processed_at < 5.minutes.ago
app.set(:last_push_processed_at => Time.now)
Rails.cache.delete(cache_key)
end
if namespaces_diff.any?
app.add_to_set(:namespaces => namespaces_diff)
Rails.cache.delete(cache_key)
end카운터를 활용한 원인 추적
어떤 무효화 로직이 문제의 원인인지 밝혀내기 위해 여러 개의 카운터를 추가했습니다. 아래 예제에서는 전체 무효화 횟수를 집계하는 app.cache.invalidate 카운터를 증가시키고, 특정 원인별로는 app.cache.invalidate_push_time, app.cache.invalidate_namespaces 같은 개별 키를 사용해 구분해서 기록합니다.
# 마지막 푸시 처리 시간 업데이트
if app.last_push_processed_at < 5.minutes.ago
app.set(:last_push_processed_at => Time.now)
Rails.cache.delete(cache_key)
Appsignal.increment_counter('app.cache.invalidate', 1)
Appsignal.increment_counter('app.cache.invalidate_push_time', 1)
end
if namespaces_diff.any?
app.add_to_set(:namespaces => namespaces_diff)
Rails.cache.delete(cache_key)
Appsignal.increment_counter('app.cache.invalidate', 1)
Appsignal.increment_counter('app.cache.invalidate_namespaces', 1)
end이렇게 커스텀 메트릭을 추가하자 시간의 흐름에 따른 캐시 적중률을 그래프로 시각화할 수 있게 되었고, 어떤 캐시 키가 쿼리 급증의 원인인지 바로 드러났습니다. 조사 결과 app.cache.invalidate_namespaces 키가 요청마다 무효화되고 있었습니다.
참고로 캐싱이 가능한 전체 요청 수는 app.cache.maybe로 집계됩니다.
문제 해결과 배운 점
이 문제에 대한 수정 사항을 배포한 후, 앱의 네임스페이스가 업데이트되지 않는 한 무효화 횟수는 0으로 떨어졌습니다.
커스텀 메트릭을 활용하면 무슨 일이 어디서, 언제, 얼마나 자주 발생하는지 훨씬 쉽게 파악할 수 있습니다. 이번 사례에서도 캐시 무효화 횟수를 읽기 쉬운 그래프로 시각화할 수 있었기 때문에 문제를 신속하게 찾아낼 수 있었습니다. 특정 값을 증가시키는 코드 몇 줄과 대시보드 생성만으로 충분합니다.
커스텀 메트릭에 대해 궁금한 점이 있거나, 애플리케이션에 커스텀 메트릭을 설정하는 데 도움이 필요하다면 언제든지 알려주세요. 기꺼이 도와드리겠습니다!