하루 2.5억 트윗을 저장하는 트위터의 새로운 저장 스토어

Overview

트위터는 하루 평균 2.5억 건의 트윗을 저장한다고 합니다. 과거 트위터는 “날짜 기준으로 데이터를 분할 관리”하여 저장을 하였고, 대략 3주에 한번씩 서버를 추가하여 Scale-out 하였습니다.

하지만 이 방식에는 다음과 같은 문제가 있었습니다.

  1. 부하 분산
  2. 고비용
  3. 복잡한 프로세스

문제를 해결하기 위해서 트위터에서 New Tweet Store를 고안했다고 합니다.

자, 그럼 기존 문제점부터 차근차근 알아보도록 합시다^^;

Problems

  • 부하 분산(Load Balancing)
    날짜 기준으로 데이터를 나눠서 분산 저장 및 관리하기 때문에, 시간이 지날수록 과거 데이터 조회 건수는 비약적으로 낮아집니다. 특히 대부분의 데이터 조회 요청은 현재 시각 기준으로 들어오기 때문에, 데이터 읽기 HOTSPOT이 발생할 수 밖에 없습니다.

    Load Balancing Problem
    Load Balancing Problem
  • 고 비용(Expensive)
    데이터는 나날이 쌓여만 가고 이를 위해서는, 매 3주에 걸쳐서 신규 노드를 추가해줘야 합니다. 신규 노드는 서버 한 대가 아닌 마스터/슬레이브로 구성되기 때문에 상당한 비용이 소요됩니다.

    Expensive Problem
    Expensive Problem
  • 절차 복잡성(Logistically complicated)
    무엇보다 가장 큰 문제는 신규 노드를 추가하는 것이 상당히 어렵다는 것입니다. 매 3주에 걸쳐서 신규 노드를 도입하는 것은 DBA 팀 입장에서는 상당히 부담되고 고된 일이었다고 합니다.

    Logistically Complicated Problem
    Logistically Complicated Problem

NEW Tweet Store

트위터는 당면한 문제를 해결하기 위해 데이터 분산 도구로는 Gizzard, 데이터 저장소로는 MySQL, 그리고 UUID 생성에는 SnowFlake등으로 새롭게 데이터 분산 재구성 하였습니다.

Twitter's New Tweet Store
Twitter's New Tweet Store

결과적으로 기존의 과거 날짜 기반의 데이터 축적에서 점진적으로 데이터가 축적되는 효과를 가져왔으며, 3주에 한번씩 고된 업무를 기획하고 이행해야했던 복잡한 절차도 사라졌다고 합니다.

전체적인 트위터 동작에 관한 내용을 하단 그림으로 표현해봅시다.

New Tweet Store Architect
New Tweet Store Architect

트윗이 저장되는 저장소를 T-Bird, 보조 인덱스가 저장되는 저장소를 T-flock라고 내부적으로 지칭합니다. 두 시스템 모두 Gizzard 기반으로 이루어져 있으며, Unique ID는 Snowflake로 생성을 합니다.

그러면 New Tweet Store에 적용된 Gizzard, MySQL, Snowflake에 관해서 알아보도록 하겠습니다.

  • Gizzard in New Tweet Store
    Gizzard는 데이터를 분산 및 복제해주는 프레임워크입니다. 클라이언트와 서버 사이에 위치하며 모든 데이터 요청을 처리합니다. 데이터 분산복제 관리 뿐만 아니라 데이터 이관, 장애 대처등 다양한 역할을 수행합니다.

    Gizzard Architect
    Gizzard Architect

    가장 큰 특징은 Write연산에 관해서는 “멱등성” 및 “가환성” 원칙에 의해 적용된다는 점입니다. 기본적으로 Gizzard는 데이터가 저장되는 순서를 보장하지 않습니다. 이 말은 곧 언제든지 예전 Write 연산이 중복되어 다시 수행될 수 있음을 의미하는데, 중복 적용될 지라도 최종적인 데이터 일관성을 보장합니다.

  • MySQL in New Tweet Store
    MySQL이 저장소로 사용됩니다. 물론 Gizzard에서 다른 다양한 저장소를 지원하지만, 트위터에서는 일단 MySQL로 스토리지 엔진은 InnoDB로 선택해서 사용하였습니다. InnoDB 스토리지엔진을 사용한 이유는 행단위 잠금과 트랜잭션을 지원하기 때문데, 다른 스토리지 엔진 대비 데이터가 망가질 확률이 훨씬 적기 때문이 아닐까 추측해 봅니다.각 MySQL 서버에 관한 하드웨어 사양과 데이터 사이즈는 다음과 같습니다.MySQL Server SpecMySQL 관련된 가장 특이한 사항은 정말 단순 데이터 저장 용도로만 사용된다는 점입니다. 일반적으로 MySQL에서 데이터 복구와 이중화를 위해서 Binary Log를 사용하지만, Gizzard와 함께 사용되는 MySQL은 이 기능마저 사용하지 않습니다. 게다가 데이터 복제를 위해서 MySQL 고유의 Replication 기능마저 사용하지 않는다는 점에서, MySQL은 new tweet store에서는 단순한 데이터 저장 수단인 것을 알 수 있었습니다.
    그 이유는 Gizzard가 데이터 분산 및 복제 모두를 수행하기 때문입니다.
  • Snowflake in New Tweet Store
    Snowflake는 New Tweet Store에서 고유 아이디 값(Unique ID)을 할당하는 역할을 합니다. 총 8Byte로 구성되어 있으며 시간 순으로 정렬 가능한 값입니다.

    Snowflake
    Snowflake

    Time – 41 bits : millisecond이며 앞으로 최대 69년 사용할 수 있음
    Machine id – 10 bits : 1024 대의 머신에 할당 가능
    Sequence number – 12 bits : 머신당 같은 millisecond에 4096개 생성 가능

마치며..

작년 Amazon RDS에 서비스를 올리기 위해서 아래와 같이 구상을 한 적이 있습니다. Amazon RDS 상 제약사항을 회피하고자 한 것으로, 기본적으로 공통으로 필요한 정보만 공유하자는 컨셉이었습니다. 변경 로그는 멱등성을 가지며 주기적(1초)으로 각 서비스 DB에서 변경 로그들을 Pulling하여 데이터 일관성을 유지하자는 내용이었습니다.

Datastore Concept
Datastore Concept

트위터 사례는 참으로 많은 점을 느끼게 하였습니다. 결과적으로 새롭게 데이터 분산을 재구성하여 점진적인 데이터 증가와 더불어 DBA 팀 자체 업무가 크게 줄었다는 사실에서 깊은 찬사를 보냅니다.

감사합니다.

참고자료)
How Twitter Stores 250 Million Tweets A Day Using MySQL
Gizzard: a library for creating distributed datastores
Snowflake

MySQL Replication 이해(2) – 구성

Overview

MySQL Replication 개념에 이어, 이번에는 실 구성에 관한 내용입니다.
각 서버 구성 방법은 “리눅스에 MySQL 설치하기” 편을 참고하시기 바랍니다.

시작에 앞서서 Server_id는 다른 숫자로 설정하세요^^.

Replication 구성은 다음 세 단계를 거쳐서 수행됩니다.

  1. DB 유저 생성
  2. DB 데이터 동기화(셋 중 택 1)
    – DB Data File Copy
    – MySQL Dump (All Lock)
    – Export/Import (Single Transaction)
  3. 리플리케이션 시작

1. DB 유저 생성

복제 데이터 전송을 위한 리플리케이션 권한의 DB 유저를 마스터에 생성합니다. 각 슬레이브 IO 쓰레드들은 추가된 DB 유저를 통해 데이터를 받습니다.

[mysql@master] $ mysql -uroot -p비밀번호
 mysql> GRANT REPLICATION SLAVE ON *.*
     -> TO repl IDENTIFIED BY 'repl';

2. DB 데이터 동기화

  • DB Data File Copy
    DB 서버 데몬을 내린 상태에서 데이터 파일 자체를 복사하는 방식입니다.
    데이터 파일 복사 과정만 수행하면 되기 때문에 대용량 서버에서 슬레이브 추가 시 유용한 방식입니다.

    ## MySQL 데몬 중지
    [mysql@master] $ /etc/init.d/mysqld stop
    [mysql@slave] $ /etc/init.d/mysqld stop
    
    ## 슬레이브에 데이터 복사
    [mysql@slave] $ scp -r mysql@master:/data/mysql/mysql-data /data/mysql
    
    ## 마스터 바이너리 로그 파일 확인
    [mysql@master]$ cd /data/mysql/mysql-binlog
    [mysql@master ]$ ls -alh
    합계 21M
    drwxr-xr-x. 2 mysql DBA 4.0K 13:34 .
    drwxrwx---. 7 mysql DBA 4.0K 16:34 ..
    -rw-rw----. 1 mysql DBA 21M  13:34 mysql-bin.000006
    -rw-rw----. 1 mysql DBA 126 13:34 mysql-bin.000007
    -rw-rw----. 1 mysql DBA 126  13:34 mysql-bin.index

    DB가 재시작되면 기본적으로 새로운 바이너리 로그를 생성됩니다.
    * 로그 파일명 :  mysql-bin.000007
    * 로그 포지션:  106

  •  MySQL Dump (All Lock)
    DB 전체에 READ LOCK을 걸고 데이터를 Export하는 방식입니다. 트랜잭션이 지원 안되는 스토리지 엔진이 섞여 있고, 데이터량이 작은 경우 사용하면 되겠습니다. “MySQL Table Lock에 관한 이해”와 같이 뜻하지 않은 Dead Lock이 발생할 수 있습니다.
    Lock을 걸고 데이터를 Export 후 슬레이브 장비에서 다시 Import하는 방식입니다. 백업하는 도중에는 데이터 변경 작업은 수행 불가하며, MyISAM의 경우 백업 수행 시간 동안 Dead Lock이 발생할 수 있습니다.
    데이터량과 트랜잭션이 작은 경우 사용할 수 있는 방식입니다.

    MySQL Full Backup
    MySQL Full Backup
    <마스터 세션1> 
    ## READ LOCK을 걸어서 데이터 변경을 방지합니다.
    mysql> FLUSH TABLES WITH READ LOCK;
    Query OK, 0 rows affected (0.00 sec)
    
    mysql> show master status\G
    *********** 1. row ***********
    File: mysql-bin.000008
    Position: 456730
    1 row in set (0.00 sec)
    
    <마스터 세션2> 
    ## 다른 세션에서 전체 데이터를 백업합니다.
    [mysql@master]$ export -uroot -p비밀번호 --all-databases > /data/mysql/mysql-dump/dump.sql
    
    <마스터 세션1> 
    ## 원래 세션에서 READ LOCK을 해제합니다.
    mysql> UNLOCK TABLES;
    Query OK, 0 rows affected (0.00 sec)
    
    ## 슬레이브에 데이터 이관
    [mysql@slave] $ scp -r mysql@master:/data/mysql/mysql-dump/dump.sql /data/mysql/mysql-dump
    [mysql@slave] $ mysql -uroot -p비밀번호 --force < /data/mysql/mysql-dump/dump.sql
  • MySQL Dump (Single Transaction)
    서비스 중지가 불가하고, 테이블이 트랜잭션을 지원하는 경우에만 사용할 수 있는  방법으로, 트랜잭션 고립 (Isolation)을 특성을 활용하는 방식입니다. 즉, Database 가 InnoDB로만 이루어진 경우 많이 쓰이는 방식이죠.^^

    ## 시점 데이터 생성
    [mysql@master ~]$ export -uroot -p비밀번호 --single-transaction --master-data=2 --all-databases > /data/mysql/mysql-dump/dump.sql
    
    ## 슬레이브에 데이터 이관
    [mysql@slave] $ scp -r mysql@master:/data/mysql/mysql-dump/dump.sql /data/mysql/mysql-dump
    [mysql@slave] $ head -n 22 full_backup.sql | tail -n 1
    -- CHANGE MASTER TO MASTER_LOG_FILE='mysql_bin.000008', MASTER_LOG_POS=456730;
    [mysql@slave] $ mysql -uroot -p비밀번호 --force < /data/mysql/mysql-dump/dump.sql

3. 리플리케이션 시작

데이터 통신 용도로는 별도 네트워크에 구성해야 NIC 간섭을 최소화할 수 있습니다. 그리고 앞서서 기록을 해놓은 마스터 Binlog 파일과 포지션을 세팅 후 슬레이브 서버를 구동하면 되겠습니다.

## 슬레이브에서 실행
mysql> CHANGE MASTER TO
    -> MASTER_HOST='master-pri',
    -> MASTER_USER='repl',
    -> MASTER_PASSWORD='repl',
    -> MASTER_PORT=3306,
    -> MASTER_LOG_FILE='mysql-bin.000008',
    -> MASTER_LOG_POS=456730,
    -> MASTER_CONNECT_RETRY=5;
## 슬레이브 시작
mysql> START SLAVE;

## Slave_IO_Running, Slave_SQL_Running 상태 확인
mysql> show slave status\G
*********************** 1. row ***********************
               Slave_IO_State: Waiting for master to..
                  Master_Host: gisselldb01-pri
                  Master_User: repl
                  Master_Port: 3306
                Connect_Retry: 5
              Master_Log_File: mysql-bin.000008
          Read_Master_Log_Pos: 456730
               Relay_Log_File: mysql-relay.000001
                Relay_Log_Pos: 251
        Relay_Master_Log_File: mysql-bin.000008
             Slave_IO_Running: Yes
            Slave_SQL_Running: Yes
            ..중략..
                 Skip_Counter: 0
          Exec_Master_Log_Pos: 456730
              Relay_Log_Space: 547
              Until_Condition: None
            ..중략..
        Seconds_Behind_Master: 0
Master_SSL_Verify_Server_Cert: No
                Last_IO_Errno: 0
                Last_IO_Error:
               Last_SQL_Errno: 0
               Last_SQL_Error:
1 row in set (0.00 sec)

다음 편에는 실제 활용할 수 있는 분야에 관해서 정리하도록 하겠습니다.
긴 글 읽으시느라 수고하셨어요^^

MySQL Table Lock에 관한 이해

Overview

Table Lock 스토리지 엔진 사용 시 반드시 알아야할 사항을 정리 드리겠습니다.

근래에는 물론 InnoDB가 아~주 많이 사용되고 있겠지만, 여전히 서비스에서는 MyISAM이 쓰이고 있습니다. MyISAM은 MySQL의 대표적인 스토리지 엔진이면서 내부적으로는 Table Lock으로 동작합니다.

관련 스토리지 엔진에 관한 설명은 MySQL특성을 정리한 반드시 알아야할 MySQL 특징 세 가지 포스팅을  참고하시면, 간단한 비교를 하실 수 있습니다. 자 그럼 Table Lock 스토리지 엔진 사용 시 반드시 알아야할 사항을 정리 드리겠습니다.

Table Lock 이해

MySQL에서 Table Lock은 다음 기준에 의해서 부여됩니다.

Write Lock
아무런 Lock이 없으면, 해당 테이블에 Write Lock을 걸어서 데이터 읽기 또는 변경 작업을 수행하지 못하게 합니다. 만약 Read 혹은 Write Lock이 존재하면 Write Lock 큐에 Lock을 넣어서 해당 Lock이 풀릴 때까지 대기합니다.

Read Lock
아무런 Write Lock이 없으면, Read Lock을 걸어서, 데이터 변경 작업을 수행하지 못하도록 합니다. 만약 Write Lock이 있으면 Read Lock 큐에 Lock을 넣고 데이터 변경 작업이 종료될 때까지 대기합니다.

기본적으로 Write Lock이 Read Lock보다 우선 순위가 높지만,
다음과 같이 LOW_PRIORITY 로 변경 가능합니다.
Example)
mysql> INSERT INTO LOW_PRIORITY table_name…
mysql> DELETE LOW_PRIORITY FROM table_name…
mysql> UPDATE LOW_PRIORITY table_name SET…

참고) Internal Locking Methods

위를 다시 간단하게 정리하자면, Write Lock 상태에서는 다른 세션이 해당 테이블 접근이 불가한 상태이고, Read Lock 상태는 다른 세션이 데이터를 Read까지만 가능하다고 볼 수 있습니다.

그러나! 만약에 Write 또는 Read 수행이 오래 걸리는 경우는 어떨까요? Read Lock은 다른 Read 세션에 영향을 미치지 않을 것으로 보이지만, 때로는 Dead Lock을 유발하는 요소가 될 수도 있습니다.

Example

다음과 같은 경우를 예를 들어보겠습니다. 테이블은 MyISAM 엔진입니다.

Session 1

## 수행 시간이 오래 걸리는 조회 쿼리 발생
mysql> SELECT * FROM tab01 WHERE  sleep(1000);

이 경우 다른 세션에서도 tab01 테이블에서 얼마든지 데이터 조회가 가능합니다.

Session 2

## Read Lock 상태 테이블에 데이터 변경
mysql> UPDATE tab01 SET c1 = '' WHERE i = 4;

Update 쿼리는 Session1의 Select 쿼리가 종료될 때까지 대기합니다.

Session 3

## Read Lock 상태이고, Write Lock이 대기 상태에서 Select 수행
mysql> SELECT * FROM tab01 LIMIT 10;

Session 2 의 Write Lock에 의해 Read 불가한 상태로 빠집니다.

프로세스 현황을 확인해보면 아래와 같습니다.
Table Lock Process List

정상적이라면 Select 수행되는 동안 다른 세션에서도 Select가 수행되어야 하는데, 쿼리 우선 순위에 의해서 Select 세션이 Lock 상태로 빠진 것을 확인할 수 있습니다.

Conclusion

MySQL Replication 사용 시 Slave 서버에서 Dead Lock은 위와 같은 상황에서 얼마든지 발생할 수 있기 때문에, 반드시 알고 있어야 합니다. (통계성 SQL이 실행되는 테이블에 Update 발생 시 다른 세션에서는 해당 테이블 데이터 조회 불가)

MyISAM 스토리지 엔진이 트랜잭션이 많은 경우 부적합한 가장 큰 이유입니다.^^