본문 바로가기
IT

레디스(Redis)와 MySQL 동기화 방법 4가지 알아보기 ( 예제 포함 )

by developer's warehouse 2024. 5. 3.

Redis를 캐시로 이용할 때 레디스와 MySQL 또는 Postgresql등의 RDBMS 와의 데이터 동기화를 위한 몇 가지 전략을 정리하였습니다. 

레디스(Redis)와 MySQL 동기화 방법 4가지 알아보기 ( 예제 포함 ) 썸네일

1. 레디스의 키 스페이스 알람을 이용한 전략 ( Keyspace Notifications in Redis )

레디스의 키 스페이스 알람(notification)을 이용한 동기화 전략에 대해서 알아보겠습니다. 

동기화 방법

Redis의 키에 HSET, HMSET, HDEL, DEL과 같은 명령을 Subscribe 등록 합니다. 
키가 삭제되거나 해시값이 설정되거나 제거될 때마다 알림을 받게 됩니다.

Redis keyspace notifications 보러 가기

 

예제 코드

레디스의 키스페이스 알림을 활용하여 MySQL과 동기화하는 것을 예제를 통해 설명드리겠습니다. 예제에서는 HSET, HMSET, HDEL, DEL 명령에 대한 알림을 받아 MySQL 데이터베이스와 동기화하는 과정을 보여드립니다.

 

이 예제에서는 레디스의 키스페이스 알림 기능을 사용하여 레디스 데이터의 변경 사항을 실시간으로 감지하고, 이를 MySQL 데이터베이스와 동기화하는 방법을 보여줍니다.

 

알림 채널 구독

레디스 클라이언트에서 PSUBSCRIBE 명령을 사용하여 __keyspace@0__:*, __keyevent@0__:hset, __keyevent@0__:hmset, __keyevent@0__:hdel, __keyevent@0__:del 채널을 구독합니다. 이를 통해 키 공간 이벤트와 키 이벤트에 대한 알림을 받을 수 있습니다.

 

Jedis를 이용한 코드는 다음과 같이 구현할 수 있습니다. 

jedis.psubscribe(new JedisPubSub() {
    @Override
    public void onPMessage(String pattern, String channel, String message) {
        // 알림 처리 로직
        processNotification(pattern, channel, message);
    }
}, "__keyspace@0__:*", "__keyevent@0__:hset", "__keyevent@0__:hmset", "__keyevent@0__:hdel", "__keyevent@0__:del");

 

psubscribe 메서드의 두 번째 인자부터는 구독할 채널 패턴을 지정합니다. 이 예제에서는 다음과 같은 채널 패턴을 구독하고 있습니다:

  • __keyspace@0__:*: 키 공간 이벤트를 구독합니다. 이는 키가 생성, 수정, 삭제될 때 알림을 받습니다. [res_idx]
  • __keyevent@0__:hset: HSET 명령이 실행될 때 알림을 받습니다.
  • __keyevent@0__:hmset: HMSET 명령이 실행될 때 알림을 받습니다.
  • __keyevent@0__:hdel: HDEL 명령이 실행될 때 알림을 받습니다.
  • __keyevent@0__:del: DEL 명령이 실행될 때 알림을 받습니다.

이렇게 구독한 채널 패턴에 따라 레디스에서 발생하는 이벤트에 대한 알림을 받을 수 있습니다. 이 알림을 처리하는 로직은 onPMessage 메서드에서 구현됩니다.

 

알림 처리 로직

알림이 도착하면 processNotification 메서드에서 이를 처리합니다. 이 메서드에서는 알림 유형(키 공간 이벤트 또는 키 이벤트)과 키 이름을 확인하고, MySQL 데이터베이스와 동기화하는 로직을 구현합니다.

private void processNotification(String pattern, String channel, String message) {
    if (channel.startsWith("__keyspace@0__:")) {
        // 키 공간 이벤트 처리
        String key = channel.substring("__keyspace@0__:".length());
        processKeyspaceEvent(key, message);
    } else if (channel.startsWith("__keyevent@0__:")) {
        // 키 이벤트 처리
        String event = channel.substring("__keyevent@0__:".length());
        String key = message;
        processKeyEvent(event, key);
    }
}

private void processKeyspaceEvent(String key, String event) {
    // MySQL 데이터베이스와 동기화하는 로직 구현
    if (event.equals("set")) {
        // HSET, HMSET 이벤트 처리
        syncWithMysql(key, getHashFromRedis(key));
    } else if (event.equals("del")) {
        // DEL 이벤트 처리
        deleteFromMysql(key);
    }
}

private void processKeyEvent(String event, String key) {
    // MySQL 데이터베이스와 동기화하는 로직 구현
    if (event.equals("hset")) {
        // HSET 이벤트 처리
        syncWithMysql(key, getHashFromRedis(key));
    } else if (event.equals("hmset")) {
        // HMSET 이벤트 처리
        syncWithMysql(key, getHashFromRedis(key));
    } else if (event.equals("hdel")) {
        // HDEL 이벤트 처리
        syncWithMysql(key, getHashFromRedis(key));
    } else if (event.equals("del")) {
        // DEL 이벤트 처리
        deleteFromMysql(key);
    }
}

private void syncWithMysql(String key, Map<String, String> hash) {
    // MySQL 데이터베이스와 동기화하는 로직 구현
    // 예: UPSERT 쿼리를 사용하여 MySQL 테이블에 데이터 저장
}

private void deleteFromMysql(String key) {
    // MySQL 데이터베이스에서 데이터 삭제하는 로직 구현
    // 예: DELETE 쿼리를 사용하여 MySQL 테이블에서 데이터 삭제
}

private Map<String, String> getHashFromRedis(String key) {
    // 레디스에서 해시 데이터를 가져오는 로직 구현
    // 예: HGETALL 명령을 사용하여 해시 데이터 조회
    return jedis.hgetAll(key);
}

고려 및 주의사항

만약 알람을 놓치면 SCAN 명령으로 데이터를 확인하여 MySQL에서 키를 업데이트 해야하는지 체크해야합니다. 

이 접근 방식은 실시간 동기화를 보장하지만 누락된 알림에 대한 추가 처리가 성능 저하나 문제를 유발할 수 있습니다.

 

2. 두 개의 분리된 구조로 타임스탬프를 관리하여 동기화

이 방법은 타임스탬프를 관리하여 갱신된 것들만 주기적으로 검색후 Mysql과 동기화 하는 방법입니다. 다음과 같은 절차로 동기화 할 수 있습니다. 

 

동기화 방법

  1. 업데이트의 타임스탬프를 기준으로 정렬된 모든 값의 해시 및 ZSET(정렬된 집합)을 사용합니다.
  2. 두 구조 모두에서 원자적으로 작동하는 Lua 스크립트(Insert/Update/Delete)를 작성합니다.
  3. 주기적으로 ZSET에서 마지막 동기화 작업보다 높은 타임스탬프가 있는 요소를 쿼리하고 업데이트된 키를 검색하여 MySQL과 동기화합니다.
레디스 Lua 스크립트 
레디스에서 Lua 스크립트는 원자적으로 작동할 수 있습니다.
레디스의 Lua 스크립트는 RDBMS에서는 프로시저라고 생각할 수 있습니다.
Redis는 내장된 Lua 인터프리터를 사용하여 EVAL 명령을 통해 Lua 스크립트를 실행할 수 있습니다. Lua 스크립트 내에서 Redis 명령어를 사용할 수 있으며, 복잡한 작업을 효율적으로 수행할 수 있습니다.
Redis는 Lua 스크립트를 단일 명령어로 실행하므로, 스크립트 내의 모든 작업이 원자적으로 수행됩니다. 이를 통해 트랜잭션 내에서 발생할 수 있는 경쟁 상태(race condition) 문제를 해결할 수 있습니다.
레디스에서는 간단하게 EVAL 명령을 사용하여 Lua 스크립트를 실행할 수 있습니다.
EVAL 명령의 첫 번째 인자는 Lua 스크립트 코드이며, 두 번째 인자는 스크립트에서 사용할 키의 개수, 그 이후의 인자는 키 이름입니다. 더 자세한 사항은 아래의 레디스 공식 EVAL 명령어 페이지를 참고하세요.

 

레디스 공식 EVAL 명령어 페이지

 

예제 코드

실제로 코드상에서 Lua script를 호출하고 사용하는 것은 아래와 같이 할 수 있습니다. 

아래는 실제로 Java로  Spring Data Redis를 사용하여 Lua Script를 호출하는 예제입니다. 

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Service;

import java.util.Arrays;
import java.util.List;

@Service
public class RedisService {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    public void executeRedisLuaScript(String key, String value) {
        // Lua 스크립트 정의
        String luaScript = """
            -- 인자로 받은 key와 value를 사용하여 해시에 값을 설정
            redis.call('HSET', 'myHash', KEYS[1], ARGV[1])
            
            -- 인자로 받은 key를 사용하여 정렬된 집합에 타임스탬프 추가
            local timestamp = os.time()  -- Get the current timestamp
            redis.call('ZADD', 'myZSet', timestamp, KEYS[1])
        """;

        // Lua 스크립트 실행
        RedisScript<List<Object>> script = RedisScript.of(luaScript, List.class);
        List<Object> result = redisTemplate.execute(script, Arrays.asList(key), value);

        // 결과 처리
        System.out.println("Lua 스크립트 실행 결과: " + result);
    }
}


이후에 주기적으로 동기화 할때, Redis에서 타임스탬프를 검색해서 처리합니다. 

아래는 java에서 Jedis를 이용해서 마지막으로 동기화 한 이후의 갱신된 값들만 검색하는 예시입니다. 

        // 레디스 연결
        Jedis jedis = new Jedis("localhost");

        // 마지막 동기화 타임스탬프 (예시)
        long lastSyncTimestamp = 1630368000L;

        // 타임스탬프가 lastSyncTimestamp보다 큰 키 조회
        Set<String> updatedKeys = jedis.zrangeByScore("myZSet", "("+lastSyncTimestamp, "+inf");

        // 업데이트된 키 출력
        System.out.println("Updated keys: " + updatedKeys);
        
        //updatedKeys를 이용해서 Mysql과 동기화 하는 코드 작성

 

 

데이터베이스 복제 툴 ( Database Replication Tools )

Tapdata와 같은 데이터베이스 복제 도구를 활용하여 MySQL과 Redis 간에 데이터를 실시간으로 효율적으로 동기화할 수 있습니다. 

아래의 두 가지 Redis와 MySQL 간의 복제 도구에 대해서는 각 링크에서 자세히 확인하실 수 있습니다. 

Tapdata

Tapdata는 실시간 데이터 통합 플랫폼으로, 다양한 시스템(데이터베이스, SaaS 서비스, 애플리케이션, 파일 등) 간의 데이터를 실시간으로 동기화할 수 있습니다. 

Efficient MySQL to Redis Data Sync with Database Replication Tool (tapdata.io)

 

Efficient MySQL to Redis Data Sync with Database Replication Tool

Explore efficient methods for real-time MySQL to Redis data synchronization using a database replication tool.

tapdata.io

Airbyte

Airbyte는 다음과 같은 특징을 가진 오픈소스 데이터 통합 플랫폼입니다:
Airbyte는 다양한 데이터 소스를 지원하며, 300개 이상의 오픈소스 구조화 및 비구조화 데이터 소스 카탈로그를 활용할 수 있습니다.
또한, 커넥터 빌더를 통해 10분 이내에 새로운 커스텀 커넥터를 구축할 수 있습니다. 

How to Connect & Load Data from MySQL to Redis? (airbyte.com)

 

How to Connect & Load Data from MySQL to Redis?

Integrate MySQL to Redis in minutes with Airbyte. Extract, transform, and load data from MySQL to Redis without any hassle.

airbyte.com

 

이러한 도구는 원활한 동기화, 유연성 및 사용 편의성을 제공합니다.

 

Canal과 Kafka를 통한 Redis와 MySQL 동기화 하기 

Canal과 Kafka는 일반적으로 ETL 솔루션에 사용되지만, MySQL과 Redis 간에 데이터를 동기화할 수도 있습니다.

 

Canal은 MySQL 데이터베이스의 실시간 동기화를 위한 오픈 소스 도구입니다. 이 도구는 MySQL 마스터 데이터베이스에서 변경된 데이터를 실시간으로 감지하고, 해당 변경 사항을 슬레이브 데이터베이스로 전달하여 동기화합니다.

 

Apache Kafka는 실시간으로 기록 스트림을 게시, 구독, 저장 및 처리할 수 있는 분산형 데이터 스트리밍 플랫폼입니다. 여러 소스에서 데이터 스트림을 처리하고 여러 사용자에게 전달하도록 설계되었습니다.

canal + kafka 아키텍처
canal + kafka 아키텍처

 

아키텍처 다이어그램을 통해 사용해야 하는 구성 요소인 MySQL, Canal, Kafka, Redis 등을 명확하게 알 수 있습니다.

Canal의 작동 방식

  1. Canal은 MySQL 슬레이브의 대화형 프로토콜을 시뮬레이션하여 MySQL 슬레이브인 것처럼 가장하고 덤프 프로토콜을 MySQL 마스터로 보냅니다.
  2. MySQL 마스터는 덤프 요청을 수신하고 바이너리 로그를 슬레이브(즉, 캐널)에 푸시하기 시작합니다.
  3. Canal은 바이너리 로그 개체(원래 바이트 스트림)를 구문 분석하여 MySQL, Kafka, Elastic Search 등과 같은 스토리지 대상으로 전송합니다.

Kafka Consumer

canal을 이용한 Kafka Consumer는 아래 github에 구현되어있으니 사용하시면 됩니다. 

 

mysql-redis-canal-kafka-sync Consumer 보러 가기

 

【 참고자료 】
GitHub Project: https://github.com/alibabacloud-howto/solution-mysql-redis-canal-kafka-sync
Canal:https://github.com/alibaba/canal
Kafka-Redis-Writer:https://github.com/alibabacloud-howto/solution-mysql-redis-canal-kafka-sync/tree/master/source/

Canal+Kafka, a Fancy Way To Sync MySQL to Redis in Real-Time - DEV Community

Redis + MySQL 동기화 정리

오늘은 레디스를 분산캐시로 사용할 때, MySQL을 동기화하는 방법에 대해서 알아보았습니다. 직접 구현하는 방식과 레디스의 Lua Script를 이용해서 구현하는 방식이 있었습니다. 그리고 다른 복제 툴을 사용하는 방법과 마지막으로 오픈소스 Kafka를 이용한 동기화 방법을 알아보았습니다. 

 

프로젝트 요구 사항과 성능 요구 사항에 가장 적합한 전략을 선택할 수 있습니다.

접근 방식을 선택할 때는 데이터 일관성, 장애 복구 시나리오, 구체적인 사용 사례를 다각도로 고려해서 잘 선택하고 구현해야합니다. 

성공적으로 구축하길 바랍니다. 

facebook twitter kakaoTalk kakaostory naver band shareLink