본문 바로가기메뉴 바로가기


if(kakao)2020 코멘터리 01 : 카카오톡 캐싱 시스템의 진화 — Kubernetes와 Redis를 이용한 캐시 팜 구성

안녕하세요, 카카오 기반서비스개발팀 톡서버파트 마이클입니다. 톡서버파트는 카카오톡의 가입과 인증, 사용자 정보, 프로필, 친구관계, 디바이스 설정 정보 등의 카카오톡과 카카오톡을 플랫폼으로 하는 서비스들을 위한 API를 개발하고 운영하는 조직입니다.

카카오톡이 전 국민에게 사랑을 받는 만큼 톡서버파트는 많은 양의 트래픽을 처리합니다.
초당 4,000,000건이 넘는 데이터 접근 트래픽을 처리하고 있으며, DB 쿼리 결과를 캐싱 하여 DB의 부하를 분산하고 있습니다. 기존에는 이를 위한 캐싱 시스템이 256여 대의 물리 장비에 분산된 Memcached를 통해서 구성되어 있었는데요, 이 캐싱 시스템을 캐시 팜으로 진화 시킨 이야기를 소개하고자 합니다.

시작하기 전에 혼동되어 사용될 수 있는 단어 몇 개만 정리하고 갈게요.

  • 캐시 노드 : 캐시 데이터를 저장할 저장소를 의미합니다. 주로 멤캐시나 레디스 단위가 됩니다. 하나의 물리 장비에 하나의 레디스만 올라가있다면, 그 장비 자체가 노드로 불릴 수 있습니다.
  • 파티션 : 캐시 클러스터의 모든 데이터를 하나의 노드에 담을 수 없을 때, 데이터를 파티셔닝 하여 여러 노드에 나눠 담습니다. 각각의 노드가 각각의 파티션이 됩니다. 이 파티션은 복제 노드를 포함할 수 있습니다.
  • 캐시 클러스터 : 파티션의 집합입니다. 데이터 성격에 따라 캐시 클러스터를 분류하여 운영합니다.
  • 쿠버네티스 클러스터 : 컨테이너화된 애플리케이션을 실행하는 노드라고 하는 워커 머신의 집합입니다.
  • 쿠버네티스 노드 : 쿠버네티스의 워커 노드를 의미합니다. 캐시 팜에서는 각각의 워커 노드를 물리 장비로 구성합니다.

90대의 캐시 장비를 상면 이전한다고?

어느 날 톡서버파트는 인프라팀으로부터 뒷골 서늘한 소식을 듣게 됩니다. 대규모 상면 계약 만료로 물리 서버들을 다른 IDC에 있는 서버들로 교체해야 하는데, 그중에 톡서버파트의 멤캐시 장비 90대가 포함되어 있다고 말입니다. 기존에 운영 과정에서 캐시 장비를 한대 교체하는 과정을 생각하면 정말 끔찍한 일입니다. 캐시 장비를 교체하는 일은 다음과 같은 작업들로 이루어집니다.

1. 캐시 장비 발주 및 멤캐시디 설치(인프라 엔지니어에게 요청)
2. 신규 멤캐시디에 대한 모니터링 셋업
**3. 새벽에 기상**
4. 캐시 클러스터 별로 zookeeper를 통해 관리되는 캐시 호스트 목록 수정
5. 애플리케이션 지표 및 DB 모니터링 (DBA 동참)

일단 새벽에 일어나야 되는 것부터 골치가 아파지고 파트원들 서로가 눈 마주치는 것을 피하기 시작합니다. 왜 새벽에 일어나야 하냐고요?

톡서버파트의 각 캐시 클러스터들은 Consistent hasing(https://ko.wikipedia.org/wiki/일관된_해싱)을 통해 클러스터를 이룹니다. Consistent hasing은 노드의 추가 및 삭제 시 (데이터의 수=K) / (노드의 수=n) 만큼만 re-hashing 되도록 유도하여 전체 데이터가 re-hasing 되며 클러스터와 데이터 소스가 과부하 걸리는 것을 막는 기법입니다.

캐시 노드 변경 작업 시 과부하를 막기 위해 Constent hashing을 사용했음에도 불구하고, 톡서버에서는 여전히 개별 노드의 트래픽이 워낙 높았습니다. 어쨌든 K/n의 데이터를 re-hashing 하기 위한 트래픽이 발생하는데 이것 자체가 너무 큽니다.

그라파나(Grafana)

따라서 캐시 노드 변경 시, 노드 추가/투입 및 re-hashing 과정에서의 세심한 모니터링이 필요했고, 이렇게 부하가 심한 작업을 서비스가 가장 뜨거운 일과 시간에 할 수는 없었습니다. 그래서 트래픽이 가장 뜸한 새벽에 일어나서 캐시 변경 작업을 진행하고는 했습니다. 새벽에 일어나서 최첨단 수동으로 호스트 목록을 변경하며 휴먼 에러를 일으키지 않을까 노심초사하고, 애플리케이션과 DB의 높은 부하를 모니터링하는 일은 피하고 싶은 일이기도 합니다.

그런데 이 일을 90대나 가지고 하라고요?

마음의 짐을 풀어보자

해야 할 일은 해야죠. 하는 김에 좀 더 해보기로 했습니다. 톡서버파트에서는 ‘마음의 짐’ 목록을 갖고 있었는데요, ‘마음의 짐’ 목록은 분명 개선하고 싶지만 서비스 개발이라는 우선순위에 밀려 하지 못한 일 목록을 말합니다. 그중에는 캐싱 시스템의 비효율성도 있었습니다. 상면 이전이 닥쳐올 때 파트원들은 이 짐을 떠올렸습니다. 위에 설명한 것들도 바로 우리가 운영하는 캐싱 시스템의 비효율성이죠. 몇 가지를 덧붙여 다시 정리해볼게요.

1. 메모리 비효율성

데이터 수는 한정 돼 있는데, 트래픽 분산을 위해 캐시 노드를 늘릴수록 노드 하나에 담긴 데이터의 양은 줄어들게 됐습니다. 어떤 캐시 클러스터는 트래픽 분산을 위해 60개의 노드로 클러스터를 구성하고 나니, 메모리 사용량은 서버 사양인 32GB 중 300MB만 사용하게 되었습니다. 전체 캐시 시스템에서 사용되는 메모리는 6.5TB중 1.8TB 뿐이었고, 4.7TB의 메모리를 낭비하고 있었습니다.

이 문제를 해결하기 위해 물리 장비에 여러 대의 멤캐시디나 레디스 같은 인 메모리 스토리지를 설치하여 사용하는 것도 고려해봤습니다. 그런데 어떤 물리 장비에, 어떤 사이즈의 메모리 스토리지가 몇 개씩 올라갈 건지 누가 관리하죠?

2. 최첨단 수동 작업

캐시 노드 변경 시에 발생하는 수작업에 대해서는 위에서 설명드렸는데요, 캐시 노드 장애 시 복구에도 비슷한 수작업이 필요했습니다. 서비스 모니터링이 빠르게 일어나지 못하거나, 장애 조치 시 시간이 걸리거나 실수를 하게 되면 장애 복구에 시간이 오래 소요되고 장애가 확산될 수 있습니다. 인프라 확장 및 변경을 좀 더 편하게 할 수 없을까요? 장애 복구 정도는 더더더 편하게 할 수 있지 않을까요?

3. 비싼 커뮤니케이션 비용

이 캐싱 시스템을 구성, 변경하는 것은 서비스 개발자 입장에서는 꽤나 비싼 커뮤니케이션입니다. 캐시 노드 변경, 장애 조치 시에 모두 인프라 엔지니어와 DB 엔지니어에게 장비와 작업 그리고 모니터링을 요청하고 커뮤니케이션해야 했습니다.

4. 불필요하게 Memcached와 Redis를 동시에 운영

기존의 캐시 클러스터는 멤캐시디로 구성되어 있었습니다. 일부 단순 key-value 데이터에 대해서도 레디스를 운영하는 곳도 있었습니다. 멤캐시와 레디스는 엄밀히 따지면 다른 점이 많지만 톡서버의 사용성에 있어서는 같았습니다. 같은 일을 하는 플랫폼을 굳이 두 개로 나눠서 운영해야 할 필요가 있을까요?

쿠무새: “쿠버네티스에 올려!”

이 문제를 해결하기 위해 고민하던 중에, 한참 쿠버네티스 문서를 읽으며 모든 일에 쿠버네티스만 외치던 쿠무새가 등장했습니다. 바로 전데요. 여러분은 쿠버네티스 하면 어떤 키워드가 가장 먼저 떠오르시나요? 쿠린이었던 저에겐 container orchestration, easy scaling, self-healing 등이 떠올랐습니다.

  1. 적절한 자원 제한과 요구로 컨테이너를 노드에 효율적으로 자동으로 분산 배치하고
  2. 장애 시엔 자동화된 프로세스 복구가 일어나고
  3. 필요시 쉬운 스케일링이 가능하며
  4. 이 모든 것이 선언적으로 자동화되며
  5. 클라우드 기반이기 때문에 인프라 엔지니어와의 커뮤니케이션 비용이 줄어드는 쿠버네티스

우리가 가진 마음의 짐을 풀기에 딱 좋은 플랫폼 아닌가요?

그리고 캐시 스토리지는 레디스를 선택했습니다. 톡서버의 사용성에서는 레디스나 멤캐시디나 성능 등의 차이점이 발생하지 않을거라고 생각해서, 가능성이 더 풍부하고 인-메모리 스토리지를 하나로 통일할 수 있는 레디스를 선택했습니다.

이렇게 쿠버네티스와 레디스를 이용한 캐시팜을 구축하는 작업을 시작했습니다. 쿠버네티스가 재배하는 레디스 농장에 비유 했어요. 쿠버네티스에서 레디스를 사용하는 가장 쉬운 방법은, 각각의 목적에 맞게 helm chart로 레디스를 설치하는 것일텐데요. 톡서버가 목적한 설계에 맞게 커스텀하는 것이 어려워 직접 statefulset을 정의해서 사용하고 있습니다.

Statefulset과 onDelete 업데이트 전략

우선 쿠버네티스 위에 레디스 파드들을 생성하기 위해서 워크로드 오브젝트로는 statefulset을 선택했습니다. 다음과 같은 특징들 때문인데요.

  • 안정된, 고유한 네트워크 식별자
  • 안정된, 지속성을 갖는 스토리지
  • 순차적인, 정상 배포(graceful deployment)와 스케일링
  • 순차적인, 자동 롤링 업데이트

이 중에서도 순차적인 스케일링과 롤링 업데이트가 가장 필요했습니다. 특히 StatefulSet은 onDelete 업데이트 전략을 제공합니다. OnDelete 전략은 StatefulSet의 template/spec이 변경되면 이를 desired state에 반영은 하지만 즉시 파드를 업데이트하지 않고, 개별 파드가 지워질 때까지 기다리고 재생성 될 때 업데이트합니다. 레디스 버전 업그레이드로 인해 이미지를 교체하는 등 대규모 파드 업데이트가 필요한 상황에서 서비스와 DB가 부하를 복구하는 과정을 충분히 모니터링하며 안전하게 배포할 수 있는 방법이 필요했습니다.

보통 쿠버네티스 클러스터에 레디스를 설치하여 사용할 때는 사용 목적 별로 statefulset을 정의하여 사용하는 것이 일반적인데요, 톡서버에서는 메모리 크기에 따라 타입을 나누어 statefulset들을 정의했습니다. 이 과정에서 충분한 레디스 파드를 생성해놓고, 각 레디스 파드의 사용처나 목적은 다른 컴포넌트가 책임지도록 했습니다. 이것이 데이터센터 별로 다중화된 쿠버네티스 클러스터 구성 위에 더 유연하고 견고한 파티션 및 캐시 클러스터를 대규모로 구성하는 것을 편리하게 합니다.

# values.yml
{
    type: M1,
    resource: {
      memory: {
        request: '1.5Gi',
        limit: '1.5Gi'
      }
    },
    conf: {
      maxMemory: '1gb'
    },
    ports: [6378, 6379, 6380],
    replicas: 40,
  },
  {
    type: M4,
    resource: {
      memory: {
        request: '6Gi',
        limit: '6Gi'
      }
    },
    conf: {
      maxMemory: '4gb'
    },
    ports: [6381, 6382],
    replicas: 30
  },
  {
    type: M16,
    resource: {
      memory: {
        request: '24Gi',
        limit: '24Gi'
      }
    },
    conf: {
      maxMemory: '16gb'
    },
    ports: [6383],
    replicas: 60
  }

# statefulset.yml
{{- range $props := .Values.redis.types }}
{{- range $port := $props.ports }}
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: {{ $redis.commonPrefix }}-{{ $props.type | lower }}-{{ $port }}
...

Resource request / limit

쿠버네티스의 리소스 요구와 제한을 이용하여 적절한 메모리 단위로 캐시 노드를 운영하기로 했습니다. 기존에는 캐시로 할당된 장비의 메모리는 무조건 32GB였습니다. 그래서 노드 당 데이터가 300MB 밖에 안되는 클러스터에는 큰 낭비가 발생했죠. 이제는 리소스 요구와 제한을 단위로 하는 캐시 파드들을 캐시 노드의 단위로 사용하기 때문에, 물리 장비 사양에 제한받지 않고 리소스를 더 유연하게 쓸 수 있게 됐습니다.

기존 캐시 클러스터들의 메모리 사용량을 분석해 각각 1GB, 4GB, 16GB 메모리 요구/제한을 가진 StatefulSet 여러 벌을 만들어 쿠버네티스 클러스터 상에 캐시 노드들을 배치하였습니다. 이제는 노드 당 300MB의 데이터를 저장하는 캐시 클러스터는 1GB 타입의 캐시 파드를 사용하면 됩니다. 32GB 메모리의 물리 장비가 아니라!

hostNetwork

대용량 트래픽의 부하 분산이 주 목적인 만큼, 쿠버네티스 네트워크 상에서 발생할 수 있는 오버헤드와 병목을 최대한 줄이고 싶었습니다. Ingress와 service 레이어를 없애고, 쿠버네티스의 오버레이 네트워크를 타지 않도록 각각의 레디스 노드는 hostNetwork를 통해 외부와 직접 통신하도록 결정했습니다. 캐시만을 위해 특수 목적으로 설계되는 쿠버네티스 클러스터이기 때문에 아키텍처 확장성을 고려할 필요도 적었기 때문에 가능한 판단이었습니다.

그런데 잠깐, Ingress와 service 없이 hostNetwork를 사용하면 각각의 캐시 노드들이 추상화가 되지 않기 때문에 self-healing 시에 클라이언트는 레디스 노드를 잃어버리게 됩니다. 클라이언트 입장에서는 캐시 노드의 추상화가 자동화된 복구의 핵심입니다. 쿠버네티스가 self-healing에 기반한 고가용성을 내세울 수 있는 것도 드넓은 클러스터 상에 어딘가에 위치한 컨테이너를 파드 이름과 레이블로 추상화하고, 이 파드들을 다시 서비스로 추상화했기 때문입니다. 이 추상화 덕분에 서비스 뒤에서 파드들이 셀프 힐링 되며 지지고 볶아도 클라이언트는 안정적으로 서비스를 사용할 수 있습니다.

일반적인 K8s 네트워크 구성, 안정적인 환경
일반적인 K8s 네트워크 구성, 장애 발생 및 자동 복구

하지만 hostNetwork를 사용한다는 것은 쿠버네티스가 추상화 시킨 네트워크 계층이 아닌, 파드가 스케쥴 된 쿠버네티스 노드의 ip와 port를 이용해 통신한다는 뜻입니다. 어떤 레디스 노드가 쿠버네티스 노드 A의 K 포트에 자리를 잡았다면, 클라이언트는 [A의 IP]”[ 포트 K]를 이용해 레디스에 접근해야 합니다. 그런데 쿠버네티스는 셀프 힐링 후에 이 파드를 같은 위치, 노드 A의 K 포트에 자리 시킨다는 보장이 없습니다. 노드 B의 N포트로 이동시킨다면 클라이언트는 레디스 노드와 통신하지 못해 장애를 겪습니다.

hostNetowrk 사용, 안정적인 환경
hostNetwork 사용, 장애 발생 및 자동 복구

장애 복구는 여전히 최첨단 수동에 맡겨야 할까요?

우리는 레디스를 쓰기로 했잖아

사실 마음의 짐 4번에 의거하여, 이참에 캐시 저장소를 레디스를 교체하기로 했습니다. 이미 일부 키-밸류 성격의 간단한 데이터들을 레디스에 저장하여 쓰고 있는 부분들이 있었는데, 어설프게 두 개 쓰지 말고 하나를 잘 쓰자고 말입니다. 그래서 레디스의 기능을 좀 더 사용하여 위에서 언급한 추상화 문제를 해결하기로 했습니다.

Sentinel 활용

Redis Sentinel(센티넬)은 health check, failover, discovery를 제공해 주기 때문에, 레디스 노드들을 추상화하고 장애 복구를 자동화해줄 수 있습니다. 쿠버네티스에 레디스 파드들을 구성할 때는, 각 레디스 파드가 어떤 데이터를 다룰 레디스인지 전혀 명시하지 않도록 했습니다. 어떤 레디스 파드가 어떤 데이터를 위해 사용될 지는 어드민 애플리케이션에 의해서 센티넬에 저장 및 관리되도록 했습니다.

센티넬에 “User.id::0이라는 이름으로 127.0.0.1:6379를 모니터링해!” 라고 명령하면,

redis-cli> sentinel monitor User.id::0 127.0.0.1 6379 2

센티넬은 다음과 같이 레디스 노드와 해당 노드의 레플리케이션(Replication) 노드들에 대한 관련 정보를 저장하고 모니터링합니다. 저희는 센티넬이 모니터링하는 이 단위를 파티션이라고 부릅니다. 센티넬을 통해 파티션들을 조회하면 다음과 같은 정보를 얻을 수 있습니다.

redis-cli> sentinel masters
1)  1) "name"
    2) "User.id::0"
    3) "ip"
    4) "127.0.0.1"
    5) "port"
    6) "6379"
    7) "runid"
    8) "f547642dce5b3c41a2ef2ef51808d337d8f1d3a0"
    9) "flags"
   10) "master,odown"
...
   31) "num-slaves"
   32) "0"
   33) "num-other-sentinels"
   34) "8"
   35) "quorum"
   36) "4"
   37) "failover-timeout"
   38) "3000"
...
2)  1) "name"
    2) "User.id::1"
...

아래 예시 코드처럼 클라이언트는 센티넬을 통해 User.id::0이라는 이름으로 레디스 노드를 찾아갈 수 있습니다. User.id::0으로 네이밍 된 레디스가 127.0.0.1:6379에서 127.0.0.2:6380으로 바뀌어도 말이죠.

// Kotlin과 Lettuce를 이용해 작성된 예시입니다.
val masterId = "User.id::0" // 파티션 이름
RedisURI.builder()
        .also { builder ->
            sentinelUrls.forEach {								
               // 여기서 센티넬은 연결 시에만 접근하기 때문에 트래픽이 많지 않고,
              // 클라이언트 입장에서 개별 센티넬이 구분되어야 할 필요도 없기 때문에 
                 Domain과 VIP로 묶어서 사용합니다.
                builder.withSentinel("talk-sentinel blahblah.kakao.com", 26379)
            }
        }
	// 마스터 ID만 가지고 레디스 접근
        .withSentinelMasterId(masterId)
        .build()
        .let {
            RedisClient.create(it)
        }
        .connect()
	.sync()
	.get("key1")

레플리케이션(Replication)을 안 쓰면?

hostNetwork 환경에서 센티넬을 사용했더라도, 레디스 파드가 self-healing 되어 다른 곳에 재배치된 경우에 클라이언트는 여전히 장애를 겪을 수 있습니다. 센티넬은 모니터링하라고 한 레디스 노드를 모니터링하고, 레플리케이션이 있을 경우 failover 시켜줍니다. 레플리케이션이 없을 때 다른 레디스 노드를 알아서 찾아서 모니터링할 주소를 바꿔줄 수는 없기 때문입니다.

Barn의 등장

그래서 Barn이라는 운영자 도구를 선언적으로 구현했습니다. 개발자가 어드민의 Web UI를 통해 다음과 같이 클러스터 상태를 명시하면,

{
	"clusterName": "User.id",
	"clusterSize": 10,
	"partitionSize": 1
}

Barn은 Control loop를 통해 이 상태를 ‘Desired state’로 두고 위에서 보여드렸던 센티넬에 저장된 파티션들의 상태 값과 비교하여 ‘Desired state’를 달성시킵니다. 이 컨트롤 루프는, 클러스터 이름은 User.id이고, 클러스터를 구성하는 파티션 수가 10이기 때문에 Sentinel이 모니터링하고 있는 목록에 User.id::[0-9]이 존재하는지 확인합니다. User.id::0이 없다면 쿠버네티스 클러스터에 생성되어 있는 레디스 노드들 중에 장애 분산이 가장 잘 되는 노드를 골라 User.id::0의 마스터로 임명하고, 센티넬에게 모니터링을 명령합니다. partitionSize가 1이기 때문에 만약 User.id::1의 레플리케이션이 존재한다면, 레플리케이션 관계를 지웁니다.

사용자가 명시한 기대 상태 값에 맞춰 클러스터를 구성하는 일 외에 이 Control loop에게 한 가지 일을 더 맡겼습니다. Sentinel을 통해 User.id::2에 대한 정보를 조회했을 때 다음과 같은 정보를 얻을 수 있습니다. 여기서 ODOWN이 발생했는지, 레플리케이션을 쓰고 있는지 감시합니다. ODOWN이고 레플리케이션을 쓰지 않는 파티션을 발견하면 자동 복구 액션을 직접 취합니다.

redis-cli> sentinel masters
    1) "name"
    2) "User.id::0"
    3) "ip"
    4) "127.0.0.1"
    5) "port"
    6) "6379"
   10) "master,odown" # ODOWN 이고,
...
   31) "num-slaves"
   32) "0"            # Replication이 없음.
...

그래서 어떻게 장애를 자동 복구 하지?

그래서 쿠버네티스에서 hostNetwork를 쓰며 난관에 봉착한 자동 복구는 어떻게 수행하도록 했을까요? 쿠버네티스와 센티넬, 반의 협동 작업으로 이뤄집니다. 위에서 언급한 것처럼 톡서버에서 기존에 사용하던 캐시 시스템에서는 다음과 같이 수동으로 장애 복구(Failover)를 수행했습니다.

  1. 개발자가 로그 경보를 통해 장애를 감지합니다.
  2. 장애가 발생한 노드를 클라이언트 설정에서 제거합니다.
  3. 새로운 물리 장비를 인프라 팀에 요청합니다.
  4. 새로 받은 노드를 클라이언트 설정에 추가합니다.

이 과정을 쿠버네티스와 센티넬, 반을 이용해서 자동화했습니다. 보통 데이터 스토리지 시스템들은 고가용성(High availability)을 위해서 복제(Replication)를 제공합니다. 복제가 구성된 저장소에 장애(Failure)가 발생하면 다음과 같은 순서로 장애 복구를 진행합니다.

  1. 복제의 리더가 장애 상태임을 감지합니다.
  2. 새로운 리더를 선택합니다.
  3. 클라이언트 혹은 시스템 내 프록시가 새로운 리더를 사용하도록 재설정합니다.

새로 구성한 캐시 팜에서도 레디스 복제와 센티넬을 이용한 자동 복구가 가능합니다. 하지만 일반적으로 캐시 목적의 클러스터 구성에서는 복제를 사용하지 않도록 하고 있습니다. 레디스의 복제가 비동기적으로 이뤄지기 때문에 failover 과정에서 정합성이 깨질 수가 있기 때문입니다. 캐시 팜에서는 복제가 구성되지 않은 클러스터도 다음과 같이 자동으로 복구를 수행합니다.

  1. Barn이 Sentinel을 통해 레디스가 장애 상태임을 감지합니다.
  2. Barn이 쿠버네티스에 사용 대기 중인 레디스 파드들 중에 노드별, IDC별, 캐시 클러스터별 파드 분포를 기반으로 향후 장애 분산에 가장 강한 파드를 선택합니다.
  3. Barn이 Sentinel이 새로운 레디스 파드를 사용하도록 명령합니다.
  4. 장애가 발생한 레디스 파드는 쿠버네티스에 의해서 자동으로 복구됩니다.

이렇게 서버 사이드와 클라이언트 사이드에서 엔지니어들은 아무 일도 하지 않았지만 장애가 복구됩니다. 서버 사이드의 장애는 쿠버네티스에 의해서 클라이언트 사이드의 장애는 Barn과 Sentinel의 협업으로 복구됩니다. 여전히 hostNetwork로 빠르고 쉬운 레디스 접근을 허용하면서요.

마치며

새로운 캐싱 시스템을 구축하며 여러 같은 성과를 얻을 수 있었습니다.

자원 활용 효율화와 사용성 확장

기존에 256대의 물리 장비를 이용해서 서비스하던 캐시 시스템을 180대까지 줄일 수 있었습니다. 기존에는 256대를 쓰면서도 신규 서비스 투입이나 시스템 개선으로 인한 캐시 확장 등을 위해 새로 장비를 신청해야 했지만, 지금은 180대의 물리 장비 안에도 넉넉한 수의 레디스 파드가 사용 대기 중이기 때문에 언제든 꺼내 사용할 수 있습니다. 전체 시스템의 메모리 가용률도 28%에서 48% 정도로 끌어올렸습니다. 언뜻 큰 차이가 아닐 수도 있지만, 기존에 멤캐시에서는 기대할 수 없었던 레디스의 Persistence와 레플리케이션을 이용한 캐시 팜의 사용성 확장을 고려한 메모리 배치이기 때문에 큰 개선이라고 할 수 있습니다.

운영 자동화

캐시 클라우드를 구성해 놓고 어드민 애플리케이션을 통해 조작되기 때문에 많은 운영이 자동화되고, 인프라 팀과의 커뮤니케이션 비용도 감소했습니다. 또한 장애 복구까지 자동으로 수행하도록 했기 때문에 고가용성과 회복 탄력성(Resilience)까지 확보할 수 있게 됐습니다.

그리고, if(kakao)2020!

일반적인 서비스 개발 팀에서는 겪기 힘든 프로젝트를 수행하며 다양한 고민을 하고 배움을 얻을 수 있었습니다. 사실 이렇게 자동화된 시스템을 구성하는 데 있어서 고려해야 할 치명적인 리스크도 있습니다. 또한 이렇게 새로 구축한 캐시 팜으로 기존 캐싱 시스템의 트래픽을 마이그레이션하는 과정도 겪었는데, 이번 글을 통해 다 공유하지는 못했습니다. 이 이야기는 이 글에서 나눈 이야기들을 포함해서 if(kakao)2020을 통해서 더 생생하게 전해 드릴게요.


11월18일~20일, if(kakao)2020 에서 만나요!
관련 세션 👉 카카오톡 4M/s 캐시 클러스터 전환기

michael.10
michael.10 뛰어난 10번 유형의 플레이어가 되기 위해 노력하고 있는 개발자입니다.
Top Tag
2021
2021-new-krew
adaptive-hash-index
adt
agile
agilecoach
ai
Algorithm/ML
Algorithm/Ranking
almighty-data-transmitter
android
angular
anycast
App2App
applicative
Architecture
arena
async
aurora
Backend
BApp
bgp
ble
blind-recruitment
block
Block Chain
blockchain
bluetooth
brian
Cache
cahtbot
Caver
cd
ceph
certificate
certification
cgroup
ci
cite
client
clojure
close-wait
cloud
cloudera-manager
clustered-block
cmux
cnn
code-festival
code-review
codereview
coding
competition
component
conference
consul
container
contents
contest
couchbase
COVID-19
cpp
Data
DB
deep-learning
dev
dev-session
dev-track
developer
developer relations
developers
devops
digitalization
digitaltransformation
dns
docker
dr
employeecard
eslint
Feature List
Featured
friendstime
front-end
frontend
functional-programming
funfunday
fzf
garbage-collection
gawibawibo
GC
github
go
graphdb
graphql
Ground X
growth
ha
hadoop
hbase
hbase-manager
hbase-region-inspector
hbase-snashot
hbase-table-stat
hbase-tools
hri
id
if kakao
ifkakao
infrastructure
innodb
internship
ios
item
Java
javascript
jsconf
jsconfkorea
json
k8s
kafka
kakao
kakao-commerce
kakao-games
kakaoarena
kakaocon
kakaok
kakaokey
kakaokrew
kakaomap
kakaotalk
KAS
KCDC
khaiii
Klaytn
Klip
kubernetes
l3dsr
l4
links
load-balancing
machine-learning
marathon
meetup
melon
mesos
Messaging
microservice
mobil
monad
mtre
mysql
mysql-realtime-traffic-emulator
nand-flash
network
new
new-krew
nfc
nomad
ocp
open
opensource
openstack
OpenWork
page
parallel
PBA
planning poker
programming-contest
pycon
python
quagga
react
reactive-programming
reactor
recap
recommendation
recruitment
redis
redis-keys
redis-scan
related-blind
rest
rubics
ruby
rxjs
s2graph
scala
scalaz
server
service
sharding
shopping
socket
spark
spark-streaming
SpringBoot
ssd
Statistics/Analysis
Stomp
storage
storm
style-guide
support
System
talk
talkchannel
tcp
tech
test
Thread-Debugging
time-wait
tmux
typescript
update
User Story
vim
vim-github-dashboard
vim-plugin
vue
vue.js
web-cache
webapp
WebSocket
weekly
All Tag
2021
2021-new-krew
adaptive-hash-index
adt
agile
agilecoach
ai
Algorithm/ML
Algorithm/Ranking
almighty-data-transmitter
android
angular
anycast
App2App
applicative
Architecture
arena
async
aurora
Backend
BApp
bgp
ble
blind-recruitment
block
Block Chain
blockchain
bluetooth
brian
Cache
cahtbot
Caver
cd
ceph
certificate
certification
cgroup
ci
cite
client
clojure
close-wait
cloud
cloudera-manager
clustered-block
cmux
cnn
code-festival
code-review
codereview
coding
competition
component
conference
consul
container
contents
contest
couchbase
COVID-19
cpp
Data
DB
deep-learning
dev
dev-session
dev-track
developer
developer relations
developers
devops
digitalization
digitaltransformation
dns
docker
dr
employeecard
eslint
Feature List
Featured
friendstime
front-end
frontend
functional-programming
funfunday
fzf
garbage-collection
gawibawibo
GC
github
go
graphdb
graphql
Ground X
growth
ha
hadoop
hbase
hbase-manager
hbase-region-inspector
hbase-snashot
hbase-table-stat
hbase-tools
hri
id
if kakao
ifkakao
infrastructure
innodb
internship
ios
item
Java
javascript
jsconf
jsconfkorea
json
k8s
kafka
kakao
kakao-commerce
kakao-games
kakaoarena
kakaocon
kakaok
kakaokey
kakaokrew
kakaomap
kakaotalk
KAS
KCDC
khaiii
Klaytn
Klip
kubernetes
l3dsr
l4
links
load-balancing
machine-learning
marathon
meetup
melon
mesos
Messaging
microservice
mobil
monad
mtre
mysql
mysql-realtime-traffic-emulator
nand-flash
network
new
new-krew
nfc
nomad
ocp
open
opensource
openstack
OpenWork
page
parallel
PBA
planning poker
programming-contest
pycon
python
quagga
react
reactive-programming
reactor
recap
recommendation
recruitment
redis
redis-keys
redis-scan
related-blind
rest
rubics
ruby
rxjs
s2graph
scala
scalaz
server
service
sharding
shopping
socket
spark
spark-streaming
SpringBoot
ssd
Statistics/Analysis
Stomp
storage
storm
style-guide
support
System
talk
talkchannel
tcp
tech
test
Thread-Debugging
time-wait
tmux
typescript
update
User Story
vim
vim-github-dashboard
vim-plugin
vue
vue.js
web-cache
webapp
WebSocket
weekly

위로