[MLOps] Multi-Model 서빙을 위한 RedisAI Cluster 구축하기 2편 - How to build RedisAI Cluster?

jaehyeong.an_·2022년 7월 1일
0

MLOps

목록 보기
2/2
post-thumbnail

지난 글에서 RedisAI가 무엇인지 그리고 RedisAI와 FastAPI를 활용한 간단한 추론 서버를 구성해보았습니다. 하지만 운영환경에서 언제 늘어날지 모를(정말 언제 늘어날지 모른다고 한다..🥹) 트래픽을 감당하기 위해서는 확장성을 고려한 스케일 인/아웃이 가능해야 합니다.

기본적으로 Redis는 이러한 확장성을 위한 클러스터 구성이 가능합니다. 이러한 Redis Cluster는 여러 master-replica 구성을 통한 스케일 아웃이 가능하기에 이를 활용한다면 RedisAI 또한 클러스터 구성이 가능하리라 생각하였습니다.
이번 글에서는 Docker를 기반으로 간단한 Redis Cluster를 구성해보고, RedisAI 모듈을 연동하는 방법에 대해 소개합니다.

본 포스팅의 데모 예제는 github를 참고해주세요.


1. Redis Cluster

레디스 클러스터는 다수의 레디스 노드로 구성된 그룹입니다. 이 노드의 그룹은 유기적으로 연결되어 있으며 어떤 노드끼리는 일련의 상하관계를 갖기도 합니다.
이러한 레디스 클러스터는 데이터를 자동으로 다수의 노드에 적절히 분배해주는 역할을 할 뿐만 아니라, 특정 노드에 문제가 생기더라도 다른 노드로 대체함으로써 전체 프로세스를 문제 없이 운영되도록 하는 중요한 역할을 수행합니다.

1.1. Data Sharding

기본적으로 레디스 클러스터는 sharding을 통해 데이터를 각 노드에 적절하게 분배해주며, 이를 레디스에서는 Hash Slot이라는 개념으로 표현합니다. Hash Slot에는 총 16,384개의 슬롯이 존재하며 각 레디스 노드는 이러한 hash slot의 subset입니다.
예를 들어, 3개의 노드로 구성된 클러스터가 있다고 가정할 때, 아래와 같이 hash slot이 구성됩니다.

Node A -> 0 ~ 5500 hash slots.
Node B -> 5501 ~ 11000 hash slots.
Node C -> 11001 ~ 16383 hash slots.

레디스 클러스터에서는 이러한 hash slot기능으로 인해 노드를 줄이거나 늘리는 것이 손쉽게 이루어집니다. 예를 들어 새로운 노드 D를 추가했다고 가정했을 때, 기존 노드 A,B,C의 hash slot을 노드 D로 일부 이관하면 됩니다. 반대로 노드 A를 제거한다고 했을 때, 노드 A의 hash slot을 전부 B, C 로 이관하면 됩니다. 이러한 resharding의 과정은 클러스터 운영의 종료 없이 이루어지기 때문에 서비스가 다운될 걱정은 안해도 되며 이로 인해 확장성(scaliability)를 보장받을 수 있습니다.

1.2. Failover on master-replica model

하지만, 만약 A, B, C 노드 중 B노드가 갑자기 fail된다면 어떻게 될까요? 5501 ~ 11000 해시슬롯을 더이상 사용할 수 없기 때문에 클러스터 또한 다운될 것 입니다.
이를 위해 Redis Cluster는 master-replica 모델을 채용하고 있습니다. 만약 위와 같은 상황에서 B의 해시슬롯을 그대로 복제한 B1이라는 replica 노드가 있다면 B가 다운되더라도 B1이 master로 승격되기 때문에 클러스터도 정상적으로 유지가 될 것 입니다. 이로 인해 높은 고가용성(High Availability)을 보장받을 수 있습니다.

레디스 클러스터는 이러한 고가용성 보장을 위해 Automatic Failover를 지원합니다. 레디스 클러스터는 내부에서 모든 노드가 서로를 감시하며 내부적으로 통신하고 있기 때문에 사용자의 개입이 필요없습니다. 그렇기 때문에 만약 특정 노드가 다운된다면 레디스 클러스터 자체에서 이를 인지하고 다른 노드로 대체하게 됩니다.

1.3. Client Redirection

앞서 말한 data sharding으로 인해 데이터들은 여러 개의 노드로 분산되어 저장이 됩니다. 그렇다면 어떤 데이터가 어떤 노드에 있는지 알고 데이터를 요청할 수 있을까요?
사용자 단에서 특정 노드에 있는 데이터를 요청하기 위해 노드의 주소값을 일일이 변경해줄 필요가 없습니다. redis cluster가 client가 요청한 키값이 있는 노드가 아닌 다른 주소로 요청을 하더라도 올바른 노드 주소를 redirection해주기 때문입니다.

위 그림을 예로들어, slot 1에 위치한 'foo'라는 키값을 조회한다는 시나리오가 있습니다. 해당 키값은 실제로는 1번 노드에 있지만, client는 해당 키가 어떤 노드에 위치해있는지 모르는 상태이기 때문에 0번 노드로 요청을 보냈습니다. 하지만 레디스 클러스터에서는 'foo'의 키값이 1번 노드에 있다는 걸 알기 때문에 client에게 실제 해당 키값이 존재하는 1번 노드 주소를 redirection해주게 됩니다. client는 반환 받은 redirection 주소를 통해 올바른 노드의 주소로 데이터를 재요청하여 값을 반환받게 됩니다.

아래는 redis-cli를 통한 예시입니다.

$ redis-cli -c -p 7000
redis 127.0.0.1:7000> set foo bar
-> Redirected to slot [12182] located at 127.0.0.1:7002
OK
redis 127.0.0.1:7002> set hello world
-> Redirected to slot [866] located at 127.0.0.1:7000
OK
redis 127.0.0.1:7000> get foo
-> Redirected to slot [12182] located at 127.0.0.1:7002
"bar"
redis 127.0.0.1:7002> get hello
-> Redirected to slot [866] located at 127.0.0.1:7000
"world"

'foo'의 경우 7002포트로 저장되며 7002 포트로 redirection된 것을 확인할 수 있으며, 다시 'hello'를 저장할 떄 7000포트로 redirection되는 것을 확인할 수 있습니다. 이렇게 sharding 기능을 통해 자동으로 데이터가 여러 노드로 분배가 되어 저장되고,
다시 데이터를 요청할 때는, 해당 데이터가 존재하는 특정 포트들로 redirection하며 데이터를 반환받는 것을 확인할 수 있습니다.

2. Redis Cluster by docker-compose

해당 섹션에서는 docker-compose를 통해 3 master - 3 replica구조의 Redis Cluster를 구축하는 예시를 소개해드리겠습니다.
Redis docker image의 경우 bitnami/redis를 기반으로 하며, RedisAI의 경우 미리 빌드된 redisai 모듈을 redis-server 실행 시 로드하는 방식으로 구동합니다.

해당 데모의 세부 코드와 동작방법은 github을 참조해주세요.

2.1. redis.conf

redis.conf 파일을 작성합니다. 해당 파일에는 클러스터 설정을 위한 파라미터를 설정하며, loadmodule 파라미터에 redisai모듈 경로를 넣어줌으로써 해당 모듈을 로드합니다.

redis.conf

loadmodule /usr/lib/redis/modules/redisai.so
cluster-enabled yes 
cluster-config-file nodes.conf 
cluster-node-timeout 5000 
appendonly yes

2.2. redisai/backends module

redisai모듈의 경우 사실 redislabs/redisai 이미지를 사용할 경우 추가로 준비할 필요는 없으나, 본 예제에서는 bitnami/redis 이미지를 사용하기 때문에, 추가로 redisai모듈을 로드하는 방식을 사용합니다.
bakcends 모듈의 경우 pt, tf, onnx 등의 딥러닝 백엔드 모듈입니다.

redisai 및 백엔드 모듈의 경우 데모용 github의 해당 링크를 사용해도 되고 직접 빌드하고자 할 경우, redisai 공식 github에서 예제를 참고하여 직접 빌드할 수 있습니다. (빌드될 경우 redisai.so 모듈을 활용하면 됩니다.)

2.3. docker-compose.yml

본 데모에서는 docker compose를 통해 3 master - 3 replica 구조의 클러스터를 구성합니다.
아래는 docker-compose.yml 코드 입니다.

docker-compose.yml

version: '3'
services:
  redis-master-1:
    image: bitnami/redis
    user: root # run a Bitnami non-root container image as a root container image
    volumes:
      - ./redis.conf:/usr/local/etc/redis/redis.conf
      - ./redisai.so:/usr/lib/redis/modules/redisai.so
      - ./backends:/usr/lib/backends
    ports:
     - 7001:7001
     - 17001:17001
    network_mode: "redis-cluster-net"
    environment:
      - REDIS_REPLICATION_MODE=master
      - ALLOW_EMPTY_PASSWORD=yes
    command: redis-server /usr/local/etc/redis/redis.conf --port 7001 --loadmodule /usr/lib/redis/modules/redisai.so BACKENDSPATH /usr/lib/backends --protected-mode no
    
  redis-replica-1:
    image: bitnami/redis
    user: root # run a Bitnami non-root container image as a root container image
    volumes:
      - ./redis.conf:/usr/local/etc/redis/redis.conf
      - ./redisai.so:/usr/lib/redis/modules/redisai.so
      - ./backends:/usr/lib/backends
    ports:
      - 7002:7002
      - 17002:17002
    network_mode: "redis-cluster-net"
    environment:
      - REDIS_REPLICATION_MODE=slave
      - REDIS_MASTER_HOST=redis-master-1
      - ALLOW_EMPTY_PASSWORD=yes
    command: redis-server /usr/local/etc/redis/redis.conf --port 7002 --loadmodule /usr/lib/redis/modules/redisai.so BACKENDSPATH /usr/lib/backends --protected-mode no
    depends_on:
      - redis-master-1


  redis-master-2:
    image: bitnami/redis
    user: root # run a Bitnami non-root container image as a root container image
    volumes:
      - ./redis.conf:/usr/local/etc/redis/redis.conf
      - ./redisai.so:/usr/lib/redis/modules/redisai.so
      - ./backends:/usr/lib/backends
    ports:
     - 7003:7003
     - 17003:17003
    network_mode: "redis-cluster-net"
    environment:
      - REDIS_REPLICATION_MODE=master
      - ALLOW_EMPTY_PASSWORD=yes
    command: redis-server /usr/local/etc/redis/redis.conf --port 7003 --loadmodule /usr/lib/redis/modules/redisai.so BACKENDSPATH /usr/lib/backends --protected-mode no

  redis-replica-2:
    image: bitnami/redis
    user: root # run a Bitnami non-root container image as a root container image
    volumes:
      - ./redis.conf:/usr/local/etc/redis/redis.conf
      - ./redisai.so:/usr/lib/redis/modules/redisai.so
      - ./backends:/usr/lib/backends
    ports:
      - 7004:7004
      - 17004:17004
    network_mode: "redis-cluster-net"
    environment:
      - REDIS_REPLICATION_MODE=slave
      - REDIS_MASTER_HOST=redis-master-2
      - ALLOW_EMPTY_PASSWORD=yes
    command: redis-server /usr/local/etc/redis/redis.conf --port 7004 --loadmodule /usr/lib/redis/modules/redisai.so BACKENDSPATH /usr/lib/backends --protected-mode no
    depends_on:
      - redis-master-2


  redis-master-3:
    image: bitnami/redis
    user: root # run a Bitnami non-root container image as a root container image
    volumes:
      - ./redis.conf:/usr/local/etc/redis/redis.conf
      - ./redisai.so:/usr/lib/redis/modules/redisai.so
      - ./backends:/usr/lib/backends
    ports:
     - 7005:7005
     - 17005:17005
    network_mode: "redis-cluster-net"
    environment:
      - REDIS_REPLICATION_MODE=master
      - ALLOW_EMPTY_PASSWORD=yes
    command: redis-server /usr/local/etc/redis/redis.conf --port 7005 --loadmodule /usr/lib/redis/modules/redisai.so BACKENDSPATH /usr/lib/backends --protected-mode no

  redis-replica-3:
    image: bitnami/redis
    user: root # run a Bitnami non-root container image as a root container image
    volumes:
      - ./redis.conf:/usr/local/etc/redis/redis.conf
      - ./redisai.so:/usr/lib/redis/modules/redisai.so
      - ./backends:/usr/lib/backends
    ports:
      - 7006:7006
      - 17006:17006
    network_mode: "redis-cluster-net"
    environment:
      - REDIS_REPLICATION_MODE=slave
      - REDIS_MASTER_HOST=redis-master-3
      - ALLOW_EMPTY_PASSWORD=yes
    command: redis-server /usr/local/etc/redis/redis.conf --port 7006 --loadmodule /usr/lib/redis/modules/redisai.so BACKENDSPATH /usr/lib/backends --protected-mode no
    depends_on:
      - redis-master-3

3. Build Redis Cluster

제가 미리 작성해놓은 데모 github을 기준으로 설명드립니다.

3.1. Setup

  1. Git clone
git clone https://github.com/jaehyeongAN/RedisAI-demo.git
  1. Move to dir
cd RedisAI-demo/redis-cluster-by-docker-compose

3.2. Create Docker Network

redis cluster를 위한 network를 'redis-cluster-net'이라는 이름으로 생성하였습니다.

docker network create redis-cluster-net

3.3. Docker build

docker-compose up -d 

docker 빌드 후 정상적으로 컨테이너가 동작 중인지 확인합니다.

docker ps 
CONTAINER ID   IMAGE           COMMAND                  CREATED          STATUS         PORTS                                                                                                NAMES
e4d7aa428df8   bitnami/redis   "/opt/bitnami/script…"   9 seconds ago    Up 7 seconds   0.0.0.0:7006->7006/tcp, :::7006->7006/tcp, 6379/tcp, 0.0.0.0:17006->17006/tcp, :::17006->17006/tcp   bb8_redis-replica-3_1
ec8b62a9fcaa   bitnami/redis   "/opt/bitnami/script…"   9 seconds ago    Up 8 seconds   0.0.0.0:7004->7004/tcp, :::7004->7004/tcp, 6379/tcp, 0.0.0.0:17004->17004/tcp, :::17004->17004/tcp   bb8_redis-replica-2_1
89b27e18db2b   bitnami/redis   "/opt/bitnami/script…"   10 seconds ago   Up 8 seconds   0.0.0.0:7002->7002/tcp, :::7002->7002/tcp, 6379/tcp, 0.0.0.0:17002->17002/tcp, :::17002->17002/tcp   bb8_redis-replica-1_1
9046e000b996   bitnami/redis   "/opt/bitnami/script…"   10 seconds ago   Up 9 seconds   0.0.0.0:7001->7001/tcp, :::7001->7001/tcp, 6379/tcp, 0.0.0.0:17001->17001/tcp, :::17001->17001/tcp   bb8_redis-master-1_1
3e5a7be44d4b   bitnami/redis   "/opt/bitnami/script…"   10 seconds ago   Up 9 seconds   0.0.0.0:7003->7003/tcp, :::7003->7003/tcp, 6379/tcp, 0.0.0.0:17003->17003/tcp, :::17003->17003/tcp   bb8_redis-master-2_1
8ded224a87fc   bitnami/redis   "/opt/bitnami/script…"   10 seconds ago   Up 8 seconds   0.0.0.0:7005->7005/tcp, :::7005->7005/tcp, 6379/tcp, 0.0.0.0:17005->17005/tcp, :::17005->17005/tcp   bb8_redis-master-3_1

3.4. Inspect IpAddress of containers in RedisCluster Network.

Docker로 구성된 redis의 클러스터를 구축할 경우 docker container ip주소를 알아야 합니다.

we need to use command “docker network inspect rediscluster” to find out the IP assigned to each container and use that to create the Redis cluster.

docker network inspect redis-cluster-net

docker network inspect 명령어를 통해 redis-cluster-net 상의 컨테이너 ip주소를 확인할 수 있습니다. (IPv4Address를 확인합니다.)

[
    {
        "Name": "redis-cluster-net",
        "Id": "11551db87e1ff61a5c103d00904b6864b1d89884ac53a8986594c8d07a844298",
        "Created": "2022-06-27T06:18:09.087045089Z",
        "Scope": "local",
        "Driver": "bridge",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": {},
            "Config": [
                {
                    "Subnet": "172.22.0.0/16",
                    "Gateway": "172.22.0.1"
                }
            ]
        },
        "Internal": false,
        "Attachable": false,
        "Ingress": false,
        "ConfigFrom": {
            "Network": ""
        },
        "ConfigOnly": false,
        "Containers": {
            "3e5a7be44d4b933377b884f440fc2451c4600e6b23598109f321265859bb7fb9": {
                "Name": "bb8_redis-master-2_1",
                "EndpointID": "ded51f76d16ac27bd890b04082cf2a43aa55dd3412af387588c9b78160268336",
                "MacAddress": "02:42:ac:16:00:04",
                "IPv4Address": "172.22.0.4/16",
                "IPv6Address": ""
            },
            "89b27e18db2bc7960b8ccd1b7518778aba0aed4723ed85a3bea7c75a6f7399d1": {
                "Name": "bb8_redis-replica-1_1",
                "EndpointID": "ad6419b620676cde246a248f7cb14df3a878aa4b624b5c2019fd8dce257faf2f",
                "MacAddress": "02:42:ac:16:00:05",
                "IPv4Address": "172.22.0.5/16",
                "IPv6Address": ""
            },
            "8ded224a87fc0719e94c9bf672ad44dd04954adbd3a1981b95a14559e948411f": {
                "Name": "bb8_redis-master-3_1",
                "EndpointID": "940784b968511e8ee3e27e855649e89f0f5ff2a225a2bc7a1e1cbf85d1ab27df",
                "MacAddress": "02:42:ac:16:00:03",
                "IPv4Address": "172.22.0.3/16",
                "IPv6Address": ""
            },
            "9046e000b9961fffe963cad4d85d194784e57ff485d469a721491a36ca17c485": {
                "Name": "bb8_redis-master-1_1",
                "EndpointID": "2136e2ef4f1022e9115bc82ca93df147fb90ff6513a6cf21721889e599bc8907",
                "MacAddress": "02:42:ac:16:00:02",
                "IPv4Address": "172.22.0.2/16",
                "IPv6Address": ""
            },
            "e4d7aa428df83a8fee3372a21a8adb3beb3848bd5ef66e42236af6410228ccd3": {
                "Name": "bb8_redis-replica-3_1",
                "EndpointID": "5785e36363f81386692b07bcf5e3301b6deba4a3252bb1ba0b490c26f0df790c",
                "MacAddress": "02:42:ac:16:00:07",
                "IPv4Address": "172.22.0.7/16",
                "IPv6Address": ""
            },
            "ec8b62a9fcaa0b1ebdd07685dbfe08dd8ba1e7e165ca38b9a22e2281da8f1dd5": {
                "Name": "bb8_redis-replica-2_1",
                "EndpointID": "93fedee81ba49bf6ac4d51be14bb126ec2fd630e2729b6477385bd7c274be65c",
                "MacAddress": "02:42:ac:16:00:06",
                "IPv4Address": "172.22.0.6/16",
                "IPv6Address": ""
            }
        },
        "Options": {},
        "Labels": {}
    }
]

3.5. Create Redis Cluster Group

redis-cli 명령어를 통해 클러스터 그룹을 생성합니다. 위에서 살펴본 레디스 컨테이너들의 host주소를 입력합니다.

redis-cli --cluster create 172.22.0.2:7001 172.22.0.5:7002 172.22.0.3:7003 172.22.0.6:7004 172.22.0.4:7005 172.22.0.7:7006

클러스터 그룹이 잘 생성되었는지, redis-cli로 진입 후 확인합니다.

redis-cli -c -p 7001

> cluster nodes
> cluster info


cluter nodes 명령어를 통해 클러스터 그룹으로 구성된 컨테이너 정보를 확인할 수 있습니다.

이것으로 RedisAI의 클러스터 구성이 완료되었습니다.🎉

3.6. Test Redis Cluster

마지막으로 redis의 기본 명령어와 RedisAI의 명령어가 잘 동작하는지 확인하고, 또 sharding이 정상 동작하는지도 확인합니다.

  1. Basic Redis Commands
> set a 123
> set b 456

> get a 
> get b

  1. RedisAI Commands
> AI.TENSORSET my_bool_tensor1 BOOL 2 2 VALUES 0 1 0 1
> AI.TENSORSET my_bool_tensor2 BOOL 2 2 VALUES 0 1 0 1

> AI.TENSORGET my_bool_tensor1
> AI.TENSORGET my_bool_tensor2


Conclusion

지난 글에서 RedisAI가 무엇인지 알아본 후 본 글에서는 RedisAI 클러스터의 기본적인 개념과 docker-compose를 통한 클러스터 구성 방법에 대해 알아보았습니다.
RedisAI의 빠른 추론 퍼포먼스, 모델 매니지먼트, 클러스터를 통한 확장성은 ML 운영에 있어 많은 부분에서 큰 장점이 되리라 생각됩니다.

하지만 RedisAI의 커뮤니티가 아직까지 활성화되지 않았고 이용자 수가 적어 업데이트가 더딘 부분은 단점으로 작용하는 것 같습니다. 좀 더 이용자 수도 많아지고 다른 MLOps 프레임워크와의 연동도 강화된다면 분명 MLOps의 강력한 프레임워크 중 하나가 되리라 생각됩니다.

References

profile
🌒 Don't be a knew-it-all, Be a Learn-it-all

0개의 댓글