Redis에서 대량의 데이터 입력하기

진크·2022년 6월 18일

이것이 레디스다

목록 보기
4/5
post-thumbnail

레디스 서버에 대량의 데이터를 입력하는 방법 3가지

1. 레디스 클라이언트 라이브러리를 사용하여 하나씩 순차적으로 데이터를 입력하는 방법

가장 접근하기 쉽고, 안전한 방법이지만, 라이브러리를 사용하여 하나씩 순차적으로 입력하는 방법은 데이터 입력에 걸리는 시간이 다른 방법에 비하여 현저하게 느림

2. 레디스 파이프라인을 사용하여 한꺼번에 입력하는 방법

파이프라인을 사용하면 데이터를 한 건씩 전송하는 것이 아니라 한 번에 모든 데이터를 전송하기 때문에 단일 데이터 입력 처리에 비하여 더 빠르게 대량의 데이터를 입력할 수 있음

3. 레디스 스냅샷 파일에 직접 기록하는 방법

스냅 샷 파일에 대량의 데이터를 기록하고 나서 레디스 서버를 재시작하여 적용하는 방법이기에 운영 측면에서는 권장하지 않음

제디스(Jedis)를 사용한 입력

https://github.com/redis/jedis

제디스(Java + Redis): Redis용 Java 클라이언트

테스트 PC 정보

MacBook Air (M1, 2020), RAM 16GB

천만 건의 데이터를 전송하는 예제

package me.jincrates.redis.example;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;

//천만 건의 데이터를 전송하는 예제
public class RedisInsertTest {
    //전체 데이터 건수
    private static final float TOTAL_OP = 10000000f;

    public static void main(String[] args) {
        JedisPool pool = new JedisPool("127.0.0.1", 6379);
        Jedis jedis = pool.getResource();
        String key, value;
        long start = now();

        for (int i = 1; i <= TOTAL_OP; i++) {
            //레디스에 저장할 키와 값을 12자리로 고정하기 위해 아래와 같이 설정
            key = value = String.valueOf("key" + (100000000 + i));
            jedis.set(key, value);
        }

        long elapsed = now() - start;
        System.out.println("초당 처리 건수 " + (TOTAL_OP / elapsed * 1000f));
        System.out.println("소요 시간 " + (elapsed / 1000f) + "초");
        jedis.disconnect();
    }

    private static long now() {
        return System.currentTimeMillis();
    }
}

싱글 스레드 테스트

천만 건의 데이터를 다중 스레드로 전송하는 예제

package me.jincrates.redis.example;

import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;

import java.util.Map;

//천만 건의 데이터를 다중 스레드로 전송하는 예제
public class JedisThreadTest {
    //전체 데이터 건수
    private static final float TOTAL_OP = 10000000f;
    private static final float THREAD = 50;  //프로그램 실행시 시작할 스레드 개수를 지정

    public static void main(String[] args) {
        GenericObjectPoolConfig jedisPoolConfig = new GenericObjectPoolConfig();
        jedisPoolConfig.setMaxTotal(500); //poll에서 할당 할 수 있는 최대 연결 수

        JedisPool pool = new JedisPool(jedisPoolConfig, "127.0.0.1", 6379, 10000);

        final long start = now();
        //자바 프로그램이 종료될 때 실행되는 이벤트 스레드를 등록
        Runtime.getRuntime().addShutdownHook(new Thread() {
            @Override
            public void run() {
                long elapsed = now() - start;

                //전체 실행시간과 초당 전송 건소를 출력
                System.out.println("스레드 개수 " + THREAD + "개");
                System.out.println("초당 처리 건수 " + (TOTAL_OP / elapsed * 1000f) + "개");
                System.out.println("소요 시간 " + (elapsed / 1000f) + "초");
            }
        });

        JedisThreadTest test = new JedisThreadTest();
        for (int i = 0; i < THREAD; i++) {
            //지정된 스레드 개수만큼 스레드를 생성한다. 생성하는 스레드에 연결 출과 인덱스를 인자로 지정
            test.makeWorker(pool, i).start();
        }
    }

    private Thread makeWorker(final JedisPool pool, final int idx) {
        //실제 데이터를 전송할 스레드를 생성
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                String key, value;
                Jedis jedis = pool.getResource();

                //지정된 스레드 인덱스에 해당하는 키를 생성하고 아니면 실행하지 않음
                //이 부분은 각 스레드가 천만 개의 요청을 공평하게 나누어 전송하게 한다.
                for (int i = 1; i <= TOTAL_OP; i++) {
                    if (i % THREAD == idx) {
                        key = value = String.valueOf("key" + (100000000 + i));
                        jedis.set(key, value);
                    }
                }

                pool.close();
            }
        });

        return thread;
    }

    private static long now() {
        return System.currentTimeMillis();
    }
}

스레드 개수에 따른 성능 증가

단일 스레드에 비해 멀티 스레드를 사용하는 것이 성능이 더 향상됐다.
스레드의 개수가 증가하면서 성능이 지속적으로 증가하다가 50개를 정점으로 성능이 하락한다.
즉, 무조건 스레드가 많다고 좋은 것은 아니며, 멀티 스레드에 대한 임계치가 존재한다는 것을 알 수 있다.

스레드 개수초당 처리 건수소요 시간
147095.617212.334
5103389.7196.713
50116319.1785.977
100109738.16491.126
50080028.164124.956

제디스와 파이프라인

제디스의 단일 명령 전송 부분을 제디스의 파이프라인 클래스를 사용하도록 변경한 예제

package me.jincrates.redis.example;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.Pipeline;

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;

//제디스와 파이프라인
public class PipelineDataJedis {
    //작성할 데이터 건수: 천만 건
    private static final int TOTAL_OPERATIONS = 10000000;

    public static void main(String[] args) throws IOException {
        Jedis jedis = new Jedis("127.0.0.1", 6379);
        jedis.connect();

        long start = System.currentTimeMillis();
        
        String key, value;
        //제디스가 제공하는 파이프라인 객체를 생성
        Pipeline p = jedis.pipelined();
        for (int i = 0; i <= TOTAL_OPERATIONS; i++) {
            key = value = String.valueOf("key" + (100000000 + i));
            p.set(key, value);
        }
        //파이프라인의 응답을 서버로부터 모두 수신하여 제디스의 응답 객체로 변환
        p.sync();
        
        jedis.disconnect();
        
        long elapsed = now() - start;
        System.out.println("초당 처리 건수 " + (TOTAL_OPERATIONS / elapsed * 1000f));
        System.out.println("소요 시간 " + (elapsed / 1000f) + "초");
    }

    private static long now() {
        return System.currentTimeMillis();
    }
}

11초....?

참고자료

정경석, 이것이 레디스다 (한빛미디어, 2015), 174-186.

profile
철학있는 개발자 - 내가 무지하다는 것을 인정할 때 비로소 배움이 시작된다.

0개의 댓글