RedisTimeSeries는 Redis에 네이티브 시계열 데이터 구조를 제공하는 Redis 모듈입니다. 기존에 Sorted Sets나 Redis Streams 위에 구축하던 시계열 솔루션은 RedisTimeSeries가 제공하는 대용량 삽입, 저지연 읽기, 유연한 쿼리 언어, 다운샘플링 등의 강력한 기능을 활용해 한층 더 발전시킬 수 있습니다.
일반적으로 시계열 데이터 자체는 (상대적으로) 단순합니다. 하지만 실제 운영 환경에서는 다음과 같은 특성도 함께 고려해야 합니다.
- 데이터 속도: 초당 수천 개의 디바이스에서 발생하는 수백 가지 메트릭을 상상해 보세요.
- 볼륨(빅데이터): 몇 달, 심지어 몇 년에 걸쳐 누적되는 데이터를 생각해 보세요.
따라서 RedisTimeSeries 같은 데이터베이스는 전체 솔루션의 일부일 뿐입니다. 데이터를 어떻게 수집(인제스트)하고, 처리한 후, RedisTimeSeries로 전송할지도 고민해야 합니다. 진정으로 필요한 것은 생산자(Producer)와 소비자(Consumer)를 분리(decouple)하고 버퍼 역할을 할 수 있는 확장 가능한 데이터 파이프라인입니다.
바로 이 지점에서 Apache Kafka가 빛을 발합니다! Kafka는 핵심 브로커 외에도 Kafka Connect(본 아티클의 솔루션 아키텍처에 포함됨), 다양한 언어의 클라이언트 라이브러리, Kafka Streams, Mirror Maker 등 풍부한 생태계를 갖추고 있습니다.
이 글에서는 Apache Kafka와 함께 RedisTimeSeries를 활용해 시계열 데이터를 분석하는 실전 예제를 살펴보겠습니다.
관련 코드는 GitHub 저장소 https://github.com/abhirockzz/redis-timeseries-kafka 에서 확인할 수 있습니다.
먼저 사용 사례부터 살펴보겠습니다. 이 시나리오는 설명의 편의를 위해 단순화했으며, 이후 섹션에서 더 자세히 다룹니다.
시나리오: 디바이스 모니터링
여러 위치(Location)에 각각 여러 디바이스가 있고, 이 디바이스들의 메트릭을 모니터링해야 하는 임무를 맡았다고 가정해 봅시다. 우선 온도(temperature)와 압력(pressure) 두 가지 메트릭을 고려하겠습니다. 이 메트릭들은 당연히 RedisTimeSeries에 저장되며, 키 네이밍 규칙은 <메트릭 이름>:<위치>:<디바이스> 형식을 따릅니다. 예를 들어 위치 5의 디바이스 1 온도는 temp:5:1로 표현됩니다. 각 시계열 데이터 포인트에는 metric, location, device라는 레이블(키-값 쌍)도 함께 부여되어, 뒤에서 확인하게 될 유연한 쿼리가 가능해집니다.
TS.ADD 명령으로 데이터 포인트를 추가하는 방법을 몇 가지 예시로 살펴보겠습니다.
# 위치 3의 디바이스 2 온도와 레이블:
TS.ADD temp:3:2 * 20 LABELS metric temp location 3 device 2
# 위치 3의 디바이스 2 압력:
TS.ADD pressure:3:2 * 60 LABELS metric pressure location 3 device 2
솔루션 아키텍처
상위 수준에서 솔루션은 다음과 같이 구성됩니다.
구성 요소별로 나누어 살펴보겠습니다.
소스(로컬) 컴포넌트
- MQTT 브로커(mosquitto): MQTT는 IoT 사용 사례의 사실상 표준 프로토콜입니다. 본 시나리오는 IoT와 시계열의 결합 사례입니다.
- Kafka Connect: MQTT 소스 커넥터를 사용하여 MQTT 브로커에서 Kafka 클러스터로 데이터를 전송합니다.
Azure 서비스
- Azure Cache for Redis Enterprise 계층: Enterprise 계층은 Redis社의 상용 버전인 Redis Enterprise를 기반으로 합니다. RedisTimeSeries 외에 RediSearch와 RedisBloom도 지원합니다. 고객은 Enterprise 계층의 라이선스 구매를 직접 걱정할 필요가 없습니다. Azure Marketplace 오퍼를 통해 라이선스를 손쉽게 확보하고 결제할 수 있습니다.
- Confluent Cloud on Azure: Azure에서 Confluent Cloud로 통합 프로비저닝 계층을 제공하는 완전 관리형 Apache Kafka 서비스입니다. 크로스 플랫폼 관리 부담을 줄이고 Azure 인프라 위에서 Confluent Cloud를 일관된 경험으로 사용할 수 있게 해주므로, Azure 애플리케이션과의 통합이 매우 용이합니다.
- Azure Spring Cloud: Azure Spring Cloud 덕분에 Spring Boot 마이크로서비스를 Azure에 배포하기가 훨씬 쉬워졌습니다. 인프라 관련 부담을 덜어주고, 구성 관리, 서비스 검색, CI/CD 통합, 블루-그린 배포 등을 제공합니다. 서비스가 무거운 작업을 모두 처리하므로 개발자는 코드에만 집중하면 됩니다.
참고로, 일부 서비스는 설명을 단순하게 하기 위해 로컬에서 호스팅했습니다. 프로덕션급 배포에서는 이들 역시 Azure에서 실행하는 것이 좋습니다. 예를 들어 Kafka Connect 클러스터와 MQTT 커넥터를 Azure Kubernetes Service에서 운영할 수 있습니다.
요약하면 엔드투엔드 흐름은 다음과 같습니다.
- 스크립트가 시뮬레이션된 디바이스 데이터를 생성해 로컬 MQTT 브로커로 전송합니다.
- MQTT Kafka Connect 소스 커넥터가 이 데이터를 수집하여 Azure에서 실행 중인 Confluent Cloud Kafka 클러스터의 토픽으로 보냅니다.
- Azure Spring Cloud에 호스팅된 Spring Boot 애플리케이션이 데이터를 추가로 처리한 뒤 Azure Cache for Redis 인스턴스에 영속화합니다.
이제 실습을 시작해 볼까요? 그 전에 다음 준비물을 확인하세요.
사전 준비 사항
- Azure 계정 — 무료로 생성할 수 있습니다.
- Azure CLI 설치
- JDK 11 (예: OpenJDK)
- 최신 버전의 Maven과 Git
인프라 구성 요소 설정
RedisTimeSeries 모듈이 포함된 Azure Cache for Redis(Enterprise 계층)를 문서를 참고해 프로비저닝합니다.
Azure Marketplace에서 Confluent Cloud 클러스터를 프로비저닝합니다. 그리고 Kafka 토픽(mqtt.device-stats)을 생성하고, 나중에 클러스터에 안전하게 연결할 때 사용할 자격 증명(API 키와 시크릿)을 만들어 둡니다.
Azure Spring Cloud 인스턴스는 Azure Portal을 통해 만들거나 Azure CLI로 생성할 수 있습니다.
az spring-cloud create -n <Azure Spring Cloud 서비스 이름> -g <리소스 그룹 이름> -l <지역 입력, 예: southeastasia>
다음 단계로 넘어가기 전에 GitHub 저장소를 클론하세요.
git clone https://github.com/abhirockzz/redis-timeseries-kafka
cd redis-timeseries-kafka
로컬 서비스 설정
필요한 구성 요소는 다음과 같습니다.
- Mosquitto MQTT 브로커
- MQTT 소스 커넥터가 설치된 Kafka Connect
- 대시보드로 시계열 데이터를 추적할 Grafana
MQTT 브로커
Mac에서 mosquitto 브로커를 로컬에 설치하고 실행했습니다.
brew install mosquitto
brew services start mosquitto
사용 중인 OS에 맞는 절차를 따르거나 Docker 이미지를 활용해도 좋습니다.
Grafana
Grafana 역시 Mac에 로컬로 설치하고 실행했습니다.
brew install grafana
brew services start grafana
OS에 맞게 설치하거나 Docker 이미지를 사용할 수 있습니다.
docker run -d -p 3000:3000 --name=grafana -e "GF_INSTALL_PLUGINS=redis-datasource" grafana/grafana
Kafka Connect
방금 클론한 저장소에서 connect-distributed.properties 파일을 찾아 bootstrap.servers, sasl.jaas.config 등의 속성 값을 환경에 맞게 교체하세요.
먼저 Apache Kafka를 로컬에 다운로드하고 압축을 해제합니다.
로컬 Kafka Connect 클러스터 시작:
export KAFKA_INSTALL_DIR=<kafka 설치 디렉터리, 예: /home/foo/kafka_2.12-2.5.0>
$KAFKA_INSTALL_DIR/bin/connect-distributed.sh connect-distributed.properties
MQTT 소스 커넥터를 수동으로 설치하려면:
- 링크에서 커넥터/플러그인 ZIP 파일을 내려받고,
- Connect 워커의 plugin.path 구성 속성에 나열된 디렉터리 중 하나에 압축을 해제합니다.
Confluent Platform을 로컬에서 사용 중이라면 Confluent Hub CLI를 이용하는 것이 간단합니다: confluent-hub install confluentinc/kafka-connect-mqtt:latest
MQTT 소스 커넥터 인스턴스 생성
mqtt-source-config.json 파일을 확인하세요. kafka.topic에는 올바른 토픽 이름을 입력하고 mqtt.topics는 그대로 두면 됩니다.
curl -X POST -H 'Content-Type: application/json'
https://localhost:8083/connectors -d @mqtt-source-config.json
# 커넥터 상태 확인 전 잠시 기다리세요
curl https://localhost:8083/connectors/mqtt-source/status
디바이스 데이터 프로세서 애플리케이션 배포
방금 클론한 GitHub 저장소의 consumer/src/resources 폴더에서 application.yaml 파일을 찾아 다음 값들을 교체합니다.
- Azure Cache for Redis 호스트, 포트 및 기본 액세스 키
- Confluent Cloud on Azure API 키와 시크릿
애플리케이션 JAR 파일을 빌드합니다.
cd consumer
export JAVA_HOME=<절대 경로 입력, 예: /Library/Java/JavaVirtualMachines/zulu-11.jdk/Contents/Home>
mvn clean package
Azure Spring Cloud 애플리케이션을 생성하고 JAR 파일을 배포합니다.
az spring-cloud app create -n device-data-processor -s <Azure Spring Cloud 인스턴스 이름> -g <리소스 그룹 이름> --runtime-version Java_11
az spring-cloud app deploy -n device-data-processor -s <Azure Spring Cloud 인스턴스 이름> -g <리소스 그룹 이름> --jar-path target/device-data-processor-0.0.1-SNAPSHOT.jar
시뮬레이션 디바이스 데이터 생성기 시작
클론한 저장소의 스크립트를 사용할 수 있습니다.
./gen-timeseries-data.sh
참고 — 이 스크립트는 mosquitto_pub CLI 명령으로 데이터를 전송하는 것이 전부입니다.
데이터는 device-stats MQTT 토픽(Kafka 토픽이 아님)으로 전송됩니다. CLI 구독자로 확인할 수 있습니다.
mosquitto_sub -h localhost -t device-stats
Confluent Cloud 포털에서 Kafka 토픽을 확인하세요. Azure Spring Cloud의 디바이스 데이터 프로세서 앱 로그도 함께 점검하면 좋습니다.
az spring-cloud app logs -f -n device-data-processor -s <Azure Spring Cloud 인스턴스 이름> -g <리소스 그룹 이름>
Grafana 대시보드 감상하기!
localhost:3000으로 Grafana UI에 접속합니다.
Grafana용 Redis Data Source 플러그인은 Azure Cache for Redis를 포함한 모든 Redis 데이터베이스와 작동합니다. 이 블로그 포스트의 안내에 따라 데이터 소스를 구성하세요.
클론한 저장소의 grafana_dashboards 폴더에 있는 대시보드를 가져옵니다(대시보드 가져오기 방법은 Grafana 문서를 참조).
예를 들어 다음 대시보드는 위치 1의 디바이스 5에 대한 평균 압력(30초 집계)을 보여줍니다(TS.MRANGE 사용).
다음 대시보드는 위치 3의 여러 디바이스에 대한 최대 온도(15초 집계)를 보여줍니다(역시 TS.MRANGE 덕분입니다).
RedisTimeSeries 명령어 직접 실행해 보기
redis-cli를 실행하고 Azure Cache for Redis 인스턴스에 연결합니다.
redis-cli -h <Azure Redis 호스트명, 예: myredis.southeastasia.redisenterprise.cache.azure.net> -p 10000 -a <Azure Redis 액세스 키> --tls
간단한 쿼리부터 시작해 봅시다.
# 위치 1의 디바이스 5 압력
TS.GET pressure:1:5
# 위치 4의 디바이스 5 온도
TS.GET temp:4:5
위치로 필터링하여 모든 디바이스의 온도와 압력을 조회합니다.
TS.MGET WITHLABELS FILTER location=3
특정 시간 범위 내 하나 이상의 위치에 있는 모든 디바이스의 온도와 압력을 추출합니다.
TS.MRANGE - + WITHLABELS FILTER location=3
TS.MRANGE - + WITHLABELS FILTER location=(3,5)
'– +'는 처음부터 최신 타임스탬프까지의 전체 범위를 의미하지만, 원한다면 더 구체적인 범위를 지정할 수도 있습니다.
MRANGE가 바로 우리에게 필요한 명령입니다! 특정 위치의 특정 디바이스로 필터링하고, 온도 또는 압력으로 더 좁혀갈 수도 있습니다.
TS.MRANGE - + WITHLABELS FILTER location=3 device=2
TS.MRANGE - + WITHLABELS FILTER location=3 metric=temp
TS.MRANGE - + WITHLABELS FILTER location=3 device=2 metric=temp
이 모든 조건은 집계(aggregation)와 결합할 수 있습니다.
# 모든 온도 데이터 포인트가 필요하지 않을 수 있습니다. 개별 값 대신 평균(또는 최댓값)은 어떨까요?
TS.MRANGE - + WITHLABELS AGGREGATION avg 10000 FILTER location=3 metric=temp
TS.MRANGE - + WITHLABELS AGGREGATION max 10000 FILTER location=3 metric=temp
또한 이런 집계를 규칙(rule)으로 만들어 별도의 시계열에 저장하는 것도 가능합니다.
작업을 마쳤다면 불필요한 비용이 발생하지 않도록 리소스 삭제를 잊지 마세요.
리소스 삭제
- 문서의 안내에 따라 Confluent Cloud 클러스터를 삭제합니다 — Confluent 조직만 삭제하면 됩니다.
- 마찬가지로 Azure Cache for Redis 인스턴스도 삭제해야 합니다.
로컬 머신에서는 다음을 수행합니다.
- Kafka Connect 클러스터 중지
- mosquitto 브로커 중지 (예: brew services stop mosquitto)
- Grafana 서비스 중지 (예: brew services stop grafana)
지금까지 Redis와 Kafka를 활용해 시계열 데이터를 수집, 처리, 쿼리하는 데이터 파이프라인을 살펴보았습니다. 다음 단계로 나아가 프로덕션급 솔루션을 구축할 때는 몇 가지 사항을 추가로 고려해야 합니다.
추가 고려 사항
RedisTimeSeries 최적화
- 보존 정책(Retention policy): 시계열 데이터 포인트는 기본적으로 잘리거나 삭제되지 않으므로 반드시 고민해야 합니다.
- 다운샘플링 및 집계 규칙: 데이터를 영원히 저장하고 싶지는 않겠죠? 적절한 규칙을 구성해 이를 처리하세요 (예: TS.CREATERULE temp:1:2 temp:avg:30 AGGREGATION avg 30000).
- 중복 데이터 정책: 중복 샘플을 어떻게 처리할지 결정하세요. 기본 정책(BLOCK)이 실제로 필요한 정책인지 확인하고, 아니라면 다른 옵션을 고려하세요.
이 외에도 다양한 구성 옵션이 있으니 자세한 내용은 RedisTimeSeries 문서를 참고하세요.
장기 데이터 보존은 어떻게?
시계열 데이터를 포함해 데이터는 소중한 자산입니다! 추가 처리(예: 머신러닝을 활용한 인사이트 도출, 예지 보전 등)를 원할 수도 있습니다. 이를 위해서는 데이터를 더 오랜 기간 보존해야 하며, 비용 효율적이고 효과적으로 수행하려면 Azure Data Lake Storage Gen2(ADLS Gen2) 같은 확장 가능한 오브젝트 스토리지 서비스를 활용하는 것이 좋습니다.
이를 위한 커넥터도 준비되어 있습니다! Confluent Cloud용 완전 관리형 Azure Data Lake Storage Gen2 Sink Connector를 활용해 기존 데이터 파이프라인을 확장하면, ADLS에 데이터를 저장하고 Azure Synapse Analytics나 Azure Databricks로 머신러닝을 수행할 수 있습니다.
확장성
시계열 데이터 볼륨은 늘어날 수만 있습니다. 따라서 솔루션의 확장성은 매우 중요합니다.
- 핵심 인프라: 관리형 서비스를 활용하면 Redis, Kafka 같은 복잡한 분산 시스템이나 스트리밍 플랫폼의 구축·유지보수보다 솔루션 자체에 집중할 수 있습니다.
- Kafka Connect: 데이터 파이프라인 관점에서 안심해도 됩니다. Kafka Connect 플랫폼은 본질적으로 스테이트리스(stateless)하며 수평 확장이 가능하기 때문입니다. Kafka Connect 워커 클러스터의 아키텍처와 크기를 설계할 때 선택지가 매우 많습니다.
- 커스텀 애플리케이션: 본 솔루션에서처럼 Kafka 토픽의 데이터를 처리하는 커스텀 애플리케이션을 만들었다면, 다행히 동일한 확장성 특성이 적용됩니다. 수평 확장은 보유한 Kafka 토픽 파티션 수에 의해서만 제한됩니다.
통합: Grafana만 있는 게 아닙니다! RedisTimeSeries는 Prometheus와 Telegraf와도 통합됩니다. 다만 이 글을 작성하는 시점에는 Kafka 커넥터가 없는데, 있다면 훌륭한 추가 요소가 될 것입니다!
결론
그렇습니다, Redis는 시계열 워크로드를 포함해 (거의) 모든 용도로 활용할 수 있습니다! 시계열 데이터 소스부터 Redis, 그리고 그 이후까지 이어지는 데이터 파이프라인과 통합의 엔드투엔드 아키텍처를 반드시 고민해 보시기 바랍니다.