동상이몽

캐싱을 도입하며 내가 경험 한 것들

by Tear94fall

캐싱 도입 과정

캐싱을 도입하면서 발생한 문제들과 문제를 해결하면서 겪은 경험을 공유하고자 글을 작성하였습니다 :b

1. 캐싱 도입을 고민 하게된 이유

캐싱을 도입하고자 하는 서비스는 다음과 같은 특징을 가지고 있었습니다.

  • 데이터가 insert된 이후 update가 거의 발생하지 않음
  • CRUD중 R 연산이 압도적으로 많음
  • 짧은 시간동안 동일한 데이터의 Read가 매우 많이 발생

이러한 이유로 인해 캐싱을 하게되면 DB접근 횟수를 줄이면서 응답속도를 빠르게 개선할 수 있을것으로 보였습니다.

2. 초창기 캐싱 도입

초기에는 In-Memory 캐시 도입을 고민하였습니다.
데이터의 접근 속도가 Redis보다는 메로리에 접근하는게 더욱 빠르기 때문이었습니다.
Redis에 데이터를 가져오기 위해서는 네트워크 통신을 통해야 하기 때문입니다.

신나게 개발을 하였고, 데이터 캐싱된 이후 캐싱 hit한뒤에는 DB 쿼리가 발생하지 않는것으로 보고 매우 좋았습니다.
Read에 캐싱을 적용해보고 제대로 작동하는것을 보고 CRUD 모든 요청에 캐싱 처리를 하였습니다.
캐싱 Evict, Update 등 모두 추가하였고 로컬 개발 환경에서 테스트가 완료된 후 Dev 환경에 적용 후...
무엇이 잘못되었음을 깨달았습니다.

네... 싱글 인스턴스 라면 아무 문제 없었을 것 입니다.
하지만 제가 개발하고 있는 서비스는 MSA 환경에 스케일 아웃이 가능한 환경입니다.
눈치 채셨나요?
바로 스케일 아웃이 가능한 환경이라는 것이 힌트입니다!

3. 도입 과정에서 발생한 문제

member-service가 만일 스케일 아웃 되었다고 생각을 해보겠습니다.
총 3개의 인스턴스가 떠있는 상황이고 각각 구분을 위해서
서비스 1, 2, 3 이라고 해보겠습니다.

쉬운 설명을 위해 사용자의 서비스 이용을 예시로 들어보겠습니다.

  1. A라는 사용자가 회원가입을 하였습니다.
  2. 회원가입 후 로그인을 하기 위해서 A라는 사용자의 정보를 조회합니다.
  3. 서비스를 사용함에 따라 A의 정보를 추가로 조회합니다.
  4. A사용자는 자신의 개인정보가 잘못 입력된 것을 보고 올바르게 변경하였습니다.
  5. 시간이 흐른뒤 A사용자는 다시 서비스를 사용합니다.

위의 과정이 단일 서비스에서 이뤄진다면 사용자 A는 아무런 문제 없이 서비스를 이용 가능합니다.
하지만 스케일 업 환경에서 만약 이런 경우라면 어떻게 될까요?

  1. A라는 사용자가 회원가입을 하였습니다. (서비스 1에 데이터 캐싱됨)
  2. 회원가입 후 로그인을 하기 위해서 A라는 사용자의 정보를 조회합니다. (서비스 2에 데이터 캐싱됨)
  3. 서비스를 사용함에 따라 A의 정보를 추가로 조회합니다. (서비스 3에 데이터 캐싱됨)
  4. A사용자는 자신의 개인정보가 잘못 입력된 것을 보고 올바르게 변경하였습니다. (서비스 2에 캐싱 데이터 갱신됨)
  5. 시간이 흐른뒤 A사용자는 다시 서비스를 사용합니다. (서비스 1, 3에서 캐싱 데이터를 조회 한다면?)

만약을 가정한 상황이긴 하지만 매우 매우 치명적인 상황입니다.
5번에서 만일 캐싱 데이터가 갱신된 서비스 2번에서 조회하지 않고, 1, 3에서 조회시 갱신전 데이터를 가져가게 됩니다.
즉, 캐싱 데이터간의 동기화가 이뤄지지 않아 정합성이 깨지는 문제가 발생합니다.

이를 해결하기 위한 동기화 방식에는 여러가지 방법이 있었습니다.
캐싱 데이터 갱신을 위해 요청을 전송, kafka를 사용, Redis pub/sub을 사용 하는 방법등...
다양한 방법이 있었습니다.
하지만 캐싱 데이터 갱신을 위해 추가적인 작업외에 가장 큰 문제가 있었습니다.

바로 캐싱 데이터 갱신 작업이 발생하는 경우 갱신 완료 시각이 서비스 별로 상이한것 입니다.
찰나의 순간이지만 데이터의 정합성이 깨지는 순간이 발생할 수 있습니다.
이 순간 요청이 발생하지 않으리라는 보장이 없었습니다.

4. 문제 해결 과정

데이터의 정합성이 중요하지 않았다면 문제 없었겠지만 해당 서비스는 정합성이 매우 중요했던 서비스였습니다.
따라서 In-Memory의 성능상의 이점을 조금 포기 하더라도 정합성을 위해서는 글로벌 캐시를 선택해야 했습니다.

글로벌 캐시로 Redis를 사용하였습니다.
글로벌 캐시로 인해 여러 서비스에서 사용이 가능했습니다.
또한 데이터가 갱신되는 경우에도 별도의 동기화 작업이 필요하지 않았습니다.

데이터 캐싱은 서비스의 특성에 대한 이해가 먼저 이뤄진 후 적절한 경우 도입 해야 합니다.
Read 연산이 적고 Write연산이 많은 경우 캐싱 도입으로 이점을 크게 보기 힘들것 입니다.

5. 마무리

여기까지 좌충우돌 캐싱 도입기를 보셨습니다.
이외에도 데이터 벌크 연산 작업시 캐싱 데이터 초기화를 같이 해줘야 하는 문제 등이 있을 수 있을 것 같습니다.
어떻게 알았냐 궁금하실 수 있을텐데, 저도 알고 싶지 않았습니다...

이상 포스팅을 마칩니다!
읽어주셔서 감사합니다! 다음 포스팅에서 또 뵙겠습니다!

블로그의 정보

동상이몽, 코딩으로 서로 다른 꿈을 꾸다

Tear94fall

활동하기