레이블이 mysql인 게시물을 표시합니다. 모든 게시물 표시
레이블이 mysql인 게시물을 표시합니다. 모든 게시물 표시

20130818

Tumblr에서는 MySQL로 어떻게 대용량 데이터를 관리하였을까?

Overview

트위터의 새로운 분산 관리 라이브러리 Gizzard를 소개합니다.“를 알아보던 당시 부사수 “임창선”님과 진행했던 또 다른 해외 사례 “Tumblr”를 정리해보았습니다.
Tumblr는 국내에서는 사용자가 많지는 않지만, Twitter 정도의 트래픽을 자랑하는 Micro Blog 서비스입니다. 하루 평규 5억 이상의 PV가 나오고, 초당 4만 건 이상 Request가 나오며, 하루 평균 3TB 이상의 데이터를 쌓는다고 하니 엄청난 서비스인 것은 틀림 없습니다.
이정도 데이터를 관리하기 위해서 수 천대 이상의 서비스를 운영한다고 하는데, 데이터 관리를 MySQL을 활용하여 제공한다고 합니다. 그렇다면 MySQL로 어떻게 대용량 데이터를 멋지게 다뤘을까요?

Tumblr?

데이터 저장에 사용하고 있는 MySQL머신 수는 약 175대 이상(지금은 더욱 많아졌겠죠^^)이며, Master 데이터 용량만 약 11TB 이상(단일 데이터 건수 250억 건 이상)이라고 합니다. 어마어마한 데이터 용량이죠.
1년 동안 Tumblr 상황을 비교한 표입니다. 엄청난 데이터를 약 20명 정도의 인력으로 관리하고 있다니, 감탄을 금치 않을 수 없네요.^^
(정확히 언제 1년 전/후 인지는 파악은 안됩니다. ㅋ)
Tumblr 현황 비교

Shard Automation

Tumblr에서는 Shard를 자동화 구성을 위해 다음같은 것들을 구현하였습니다.
  1. 모든 Shard에 관한 위상 정보 수집
  2. 서버 설정을 조작하기 위한 Unix 명령 및 DB 쿼리 실행
  3. 여러 서버로 대용량 파일을 복사
  4. 임의의 데이터셋에 대한 Import/Export
  5. 단일 Shard를 대중 Shard로 분리할 수 있는 기능
Shard 목표는 다음과 같습니다.
  1. 지나치게 큰 Shard 조각을 새로운 N개의 Shard로 재배치한다.
  2. Lock이 없어야하며 어플리케이션 로직도 필요 없어야 한다.
  3. 5시간 안에 800GB 데이터 파일을 2개로 나누어야 한다.
  4. 전체적으로 서비스에 전혀 지장이 없어야 한다.
Shard 자동화는 다음 원칙하에 이루어집니다.
  1. 모든 테이블은 InnoDB  Storage Engine을 사용한다.
  2. 모든 테이블은 Shard Key로 시작하는 Index를 가진다.
  3. Shard Schema는 Range-based로 한다.
  4. Shard 과정 중 스키마 변경은 없다
  5. 디스크 사용률이 2/3 미만이거나, 별도 디스크가 있어야 한다.
  6. Shard Pool 마다 2 대의 대기 Slave 서버가 존재해야 한다.
  7. Master와 Slave 사이의 균일한  MySQL 설정 한다.
    (log-slave-updates, unique server-id, generic logbin and relay-log names, replication user/grants)
  8. Master Slave 사이에 데이터 동기화에 지연이 있으면 안된다.
  9. 잘못된 Shard에 일시적인 중복된 행은 서비스에 문제가 안된다.

Shard Process

Shard는 다음과 같은 프로세스 대로 자동화 구현합니다.
  1. 기존 Shard Pool에서 새로운 Slave N개 생성
  2. 신규 Slave에 기존 Master 데이터 분할 저장
  3. READ를 신규 분할 Master(신규 Slave)로 이동
  4. Write를 신규 분할 Master(신규 Slave)로 이동
  5. 기존 Master와 신규 Master 서버 간 Replication 끊기
  6. 신규 Master 데이터 보정

1) 기존 Shard Pool에서 새로운 Slave N개 생성

커다란 데이터 파일을 N개의 Slave 서버로 빠르게 재비치 하는 것을 목표로 합니다.
Tumblr Slave Data Clone
Tumblr Slave Data Clone
pigz(Parallel gzip)을 사용 빠르게 압축하고, 그와 동시에 유닉스 명령어인 nc로 신규 Slave에 파일을 바로 전송합니다.
Slave4(신규 장비)에 nc로 포트 개방 및 압축 해제하도록 구성합니다.
1
[mysql@Slave4~]$ nc -l 10000 | pigz -d | tar xvf -
Slave3(신규 장비)에 tee와 fifo를 활용하여 받음과 동시에 데이터를 Slave4로 보내도록 구성합니다.
1
2
3
[mysql@Slave3~]mkfifo myfifo
[mysql@Slave3~]$ nc Slave4 1234 <myfifo &
[mysql@Slave3~]$ nc -l 10000 | tee myfifo | pigz -d | tar xvf -
Slave2(기존 장비)에서 압축 후 nc 로 바로 신규 데이터를 전송하도록 합니다.
1
[mysql@Slave2~]tar cv ./* | pigz | nc Slave3 10000
Slave2 -> Slave3 -> Slave4 로 동시에 순차적으로 데이터가 전송됩니다. 결과적으로 세 대 Slave 장비는 CPU, Memory, Disk, Network 등을 효율적으로 사용하게 되죠.^^ 순차적으로 복사하거나 단일 소스에서 병렬로 복사하는 것보다 훨씬 성능이 좋습니다.
위 그림처럼 텀블러가 Slave 장비를 서비스에 투입하지 않고 Standby 상태로 구성하는 이유는 pigz의 사용시 발생되는 리소스 부하로 인한 서비스의 영향도 때문으로 추측됩니다. 실제 테스트를 해보니 서버 리소스 영향이 있었습니다.
1
2
3
4
5
6
7
8
9
17:16:54  CPU  %user  %nice  %system  %iowait  %idle
17:16:55  all  53.80   0.00     3.55     3.05  39.60
17:16:56  all  67.00   0.00     4.25     2.38  26.38
17:16:57  all  40.11   0.00     2.68     5.24  51.97
17:16:58  all  70.75   0.00     4.36     2.12  22.78
17:16:59  all  63.27   0.00     3.81     3.69  29.23
17:17:00  all  64.57   0.00     4.30     2.06  29.07
17:17:01  all  51.00   0.00     3.62     4.50  40.88
17:17:02  all  57.96   0.00     4.37     2.87  34.79

2) 신규 Slave에 기존 Master 데이터 분할 저장

신규 Slave에 기존 데이터를 N개로 나눠서 저장합니다.
신규 Slave 데이터 분할 저장
그런데 데이터를 분할하는 방식이 참으로 재미 있습니다.
먼저 “Select .. Into Outfile”로 데이터를 추출하고, 테이블을 Drop 및 Create 한 후 추출한 데이터를 “Load Data Infile”로 다시 넣는 것입니다.
과연 무엇이 더 빠른 것인지 판단이 정확하게 서지는 않지만, Tumblr에서는 기존 데이터를 날리고 Bulk Insert 하는 것이 훨씬 빠르다고 판단한 것 같습니다.
Load Data가 정상적으로 마무리되면, 신규 Slave 밑에 각각 두 개 Slave를 붙입니다. 아래 그림과 같은 형상이 되겠죠.^^
Slave 최종 형상
아래는 데이터를 Export/Import 시 주의할 사항이라고 합니다.
  1. import 속도를 위해 바이너리 로그를 비활성화 한다.
  2. 쿼리를 Kill하는 스크립트는 사용하지 않는다.
    (SELECT … INTO OUTFILE 과 LOAD DATA INFILE는 KILL하지 않도록 함)
  3. 어느 정도 import/export가 가능하지를 벤치마크하여 파악한다.
  4. 속도가 다른 디스크 여러 개를 동시 사용하는 경우 디스크 I/O 스케줄러 선택을 주의한다.
한 가지 의문이 가는 사항은 정상적인 Replication 상태라면 분명 신규 Slave에서 Replication Fail이 발생했을 것 같다는 것입니다. 아무래도 Slave 서버에서 모든 Error를 무시하는 설정을 해놨다는 생각이 드네요.
또한 Binlog Log 는 아무래도 Row 포멧이 위와 같은 경우에서는 조금 더 유리하지 않을까요? 그냥 추측 두 가지를 해봅니다.^^

3) READ를 신규 분할 Master(신규 Slave)로 이동

어플리케이션에서 READ를 기존  Master에서 신규 Slave(앞으로 Master로 구성될 서버)로 이동을 하여 서비스를 진행합니다.
Move Read to New Slave
만약 어플리케이션 서버에서 업데이트가 동시에 완료되지 않은 상태에서 갑자기 Read/Write 포인트가 이동한다면, 데이터 일관성 문제가 발생할 것입니다.
동시에 Write까지 이동되었다고 가정해봅시다. A가 새로운 DB 형상을 받고 200 게시물을 작성하였으나, B는 여전히 예전 형상을 바라보고 기존 Master 서버에서 데이터를 읽어오면 게시물을 찾을 수 없겠죠.
그렇기 때문에 기존 Master와 데이터 복제를 유지하면서 Read 포인트만 먼저 이동하는 것입니다.

4) Write를 신규 분할 Master(신규 Slave)로 이동

모든 DB 구성에 관한 형상이 업데이트 된 후에 Write 포인트를 변경합니다. 옮긴 후에도 기존 Master DB의 바이너리 로그 기록이 정지하기 전까지는 절대 기존 Master/Slave 구성에 손을 대면 안되겠죠.^^
Move Write to New Slave

5) 기존 Master와 신규 Master 서버 간 Replication 끊기

기존 Master의 바이너리 로그 기록이 중지되었다면, 이제 필요없는 DB들을 제거하도록 합니다.
Remove Previous DB Servers
이제 모든 데이터는 새로운 Slave 아니 새로운 Master에서 Read/Write 서비스되겠죠. ^^

6) 신규 Master 데이터 보정

분할된 Shard된 조각에서 잘못 복제된 데이터를 제거하는 작업이 필요합니다.
그러나 절대적으로 하나의 커다란 단위의 Delete 작업은 피하도록 합니다. Master/Slave 간 데이터 동기화 지연을 최소화하기 위한 방안이죠.
그러므로 Delete 작업도 조금씩 끊어서 수행하도록 유도합니다.

Conclusion

정리를 하자면, “서비스에 투입하지 않은 Slave에서 빠르게 복제 서버를 두 대 추가하고, Master 데이터를 각각 반으로 나눠서 분리 저장한다” 고 보면 되겠습니다. Tumblr에서는 내부적으로 조금더 상세한 내용을 공유하지 않았지만, 적어도 데이터를 Shard하는 동안에는 서비스 Downtime이 없다는 것입니다.
Scale-Out 방안이 다양하지 않은 MySQL에서 이러한 방식으로 신속하게 Scale Out할 수 있었던 기법에서 많은 점을 배운 것 같네요.^^
대용량 데이터 관리를 위한 어플리케이션 개발도 필요하겠지만, 전체적인 데이터 흐름을 아는 것이 무엇보다 중요하다고 생각합니다.


auto_increment in innodb

Overview

MySQL에서는 시퀀스 개념이 없지만, 테이블 단위로 움직이는 Auto_Increment라는 강력한 기능이 있습니다. Auto_Increment 속성은 숫자 형 Primary Key를 생성하는 데 많이 사용됩니다.
특히나 InnoDB 경우에는 Primary Key 사이즈가 전체 인덱스 사이즈에 직접적인 영향을 미치기 때문에, 저도 테이블 설계에 많이 권고하는 사항이기도 합니다.
그러나 InnoDB에서 Auto_Increment가 동작하는 방식을 정확하게 알지 못하고 사용하면, 대형 장애 상황으로도 치닫을 수 있습니다.
오늘은 간단한 사례를 바탕으로 관련 내용을 공유할까 합니다. ^^

Auto_Increment In InnoDB

Auto_Increment는 스토리지 엔진 별로 다르게 동작합니다. 파일 기반의 스토리지 엔진인 MyISAM 경우에는 현재 Auto_Increment값이 파일에 일일이 기록되는 방식으로 관리됩니다. 그러나 메모리 기반의 스토리지 엔진인 InnoDB에서는 조금 다른 방식으로 관리됩니다.
InnoDB에서는 MyISAM과는 다르게 Auto_Increment 값이 변경될 때마다 기록하지 않습니다. “메모리 상에서 Auto_Increment 값을 관리”하는 것이죠. DB가 처움 구동되면 다음과 같이 Auto_Increment 속성이 있는 테이블은 모두 초기화됩니다.
1
SELECT MAX(ai_col) FROM t for UPDATE
만약 결과 값이 NULL이면 Auto_Increment_Offset으로 대체되거나, 1로 초기화됩니다. 그리고 Auto_Increment_Increment만큼 증가되어 Auto_Increment 가 관리되는 것이죠. 이런 상황에서 어떤 문제가 발생할 수 있을까요?

Problem Case

인지하고 있어야 하는 부분은 바로 위에서 Auto_Increment값이 초기화되는 부분입니다. 각 테이블의 Auto_Increment값을 최대값을 기준으로 초기화하기 때문에, 서버 재시작 시 올바른 Auto_Increment 값이 설정되지 않을 가능성이 있는 것입니다.
그렇다면 테스트를 해볼까요? 다음과 같이 테이블을 생성합니다.
1
2
3
4
5
CREATE TABLE `test` (
  `i` int(11) NOT NULL AUTO_INCREMENT,
  `j` char(1) DEFAULT NULL,
  PRIMARY KEY (`i`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
그리고 10 건의 데이터를 넣고, 현재 Auto_Increment 값을 확인해봅니다.
1
2
3
4
5
6
7
8
9
10
## 10건 데이터 Insert
mysql> insert into test (j) values ('1');
 
## 테이블 스키마 조회
mysql> show create table test\G
CREATE TABLE `test` (
  `i` int(11) NOT NULL AUTO_INCREMENT,
  `j` char(1) DEFAULT NULL,
  PRIMARY KEY (`i`)
) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8
이 상황에서 모든 데이터를 지우고 다시 한번 Auto_Increment값을 확인해봅니다.
1
2
3
4
5
6
7
8
9
10
mysql> delete from test;
Query OK, 10 rows affected (0.00 sec)
 
## 테이블 스키마
mysql> show create table test\G
CREATE TABLE `test` (
  `i` int(11) NOT NULL AUTO_INCREMENT,
  `j` char(1) DEFAULT NULL,
  PRIMARY KEY (`i`)
) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8
여전히 Auto_Increment 값은 11로 변동이 없습니다.
그렇다면 여기서 DB를 재시작 후 확인해보면 어떨까요? DB를 재시작 후 다시 한번 스키마를 확인해 봅니다.
1
2
3
4
5
CREATE TABLE `test` (
  `i` int(11) NOT NULL AUTO_INCREMENT,
  `j` char(1) DEFAULT NULL,
  PRIMARY KEY (`i`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
분명 11로 설정되어 있어야할 값이 마치 테이블이 처음 생성된 것처럼 조회가 됩니다. 이 상태에서 한 건의 데이터를 넣고 다시 한번 테이블 스키마를 확인해 봅니다.
1
2
3
4
5
6
7
8
mysql> insert into test (j) values ('1');
 
mysql> show create table test\G
CREATE TABLE `test` (
  `i` int(11) NOT NULL AUTO_INCREMENT,
  `j` char(1) DEFAULT NULL,
  PRIMARY KEY (`i`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8
Auto_Increment 값이 11에서 2로 변경되는 어이없는 현상이 발생했습니다. 이 같은 현상은 파일 기반 스토리지 엔진인 MyISAM에서는 발생하지 않습니다. 비록 Delete가 된다고 하더라도 그 값은 디스크에 기록을 하기 때문이죠.

Conclusion

MyISAM테이블을 성능 및 안정성 이슈로 InnoDB로 전환 후 서버 재시작 시 매번 Primary Key 중복 오류가 발생한 사례가 있습니다. 결과적으로 Delete 스케줄링이 문제가 되었고, 관련 로직을 제거함으로써 해결하게 되었죠. Auto_Increment의 가장 최근 데이터를 삭제 처리하는 로직만 없다면 아~무런 문제가 없습니다.
InnoDB에서 Auto_Increment를 사용하고 있다면 이와 같은 특성을 반드시 이해하고 예기치 않는 장애 사항을 사전에 예방하시기 바랍니다. ^^
이직 후 적응 기간을 거쳐 오랜만에 포스팅 합니다. ^^
감사합니다.


Twitter Gizzard

Overview

바로 이전 “하루 2.5억 트윗을 저장하는 트위터의 새로운 저장 스토어” 포스팅에서 트위터의 새로운 저장 스토어에 관해서 전반적으로 설명 드렸는데요, 이번에는 그 중 Gizzard에 관해서 심층 분석(?)을 해볼까합니다.
Gizzard는 트위터에서 데이터를 분산 및 복제 관리하기 위한 자체 개발 프레임워크입니다. 클라이언트와 서버 사이에 위치하며 모든 데이터 요청을 처리하는 구조입니다. Gizzard 관련 몇 가지 키워드는 아래와 같습니다.
  1. 분산 관리(Sharding)- 분할(Partitioning)
    - 복제(Replication)
  2. 부하분산(Load-Balancing)
  3. 장애복구(Fail-Over)
  4. 멱등성(idempotent) / 가환성(commutative)- 멱등성 : 연산을 여러 번 적용하더라도 결과가 달라지지 않는 성질
    - 가환성 : 연산의 순서를 바꾸어도 그 결과가 변하지 않는 일

분산 관리(Sharding)이란?

과거에는 서비스 성능 저하가 발생하면 곧바로 해당 서버에 CPU또는 Memory 사이즈를 증설하여 성능 이슈를 해결하였습니다. 하지만, 최근 Web 서비스에서 데이터 사이즈가 급증하여, 더 이상은 서버 성능 고도화만으로는 한계가 있기 때문에, 다수 장비에 데이터를 분산 위치(Data Sharding)하여 데이터를 처리하는 움직임이 일반화되고 있습니다.
Scale up VS Scale out
Scale up VS Scale out
분산 처리는 Hadoop, Cassandra, MongoDB 등 NOSQL 분야에서 유행처럼 번져나가고 있으며, 최근에는 기존 RDBMS 를 활용한 분산 관리 기법도 방안이 모색되고 있습니다. 사실 RDBMS의 대표적인 선두 주자인 Oracle도 예전부터 Grid Control 원칙 하에 데이터 분산 관리에 초점을 맞추고 있습니다.
데이터 분산 즉 Data Sharding이란 데이터를 조각으로 관리하자는 것인데,  Sharding을 크게 Partitioning(분할) 과 Replication(복제) 두 가지로 나눕니다.
“Partitioning”은 데이터를 조각화하여 다수 서버에 위치하는 것입니다. 물론 각각의 데이터 조각들은 서버에 저장할 수 있도록 충분히 작아야 하고, 데이터를 효과적으로 관리하고 질의가 가능해야 하겠죠.
“Replication”은 여러 서버에 데이터 복사본을 위치하는 것입니다. 이 기술은 각각의 장비에서 동작하고 질의 처리를 할 수 있으며, 데이터 복사본 추가만으로 “READ” 관련 트래픽을 효과적으로 처리할 수 있습니다. 또한 다른 Node 어딘가에는 복사본 데이터가 존재하기 때문에, 특정 데이터에 장애가 발생하거나 유실이 되어도 쉽게 데이터 복구를 할 수 있다는 이점이 있습니다.

Gizzard란 무엇일까요?

Gizzard는 데이터를 분할하여 관리하는 프레임워크입니다. 트위터에서 자체 개발한 또 다른 분산 데이터 저장소이고, 데이터를 쉽게 구성하고 관리하기 위한 미들웨어입니다. 장애 발생에 유연하게 대처할 수 있고, 분산 데이터를 쉽게 관리할 수 있으며, Scala기반으로 만들어져있기 때문에 Config가 쉽다고 합니다.(저는 아직 Scala를 써보지는 않아서.. 써보고 말씀드릴께요.^^)
사실 많은 오픈 소스 분산 데이터 관리 시스템이 많이 나오고는 있지만, 효과적으로 데이터 구조를 정의하고 시스템 장애에 유연하게 대처하거나 데이터 일관성 유지 문제를 해결하는데에는 많은 어려움이 있는 것은 사실입니다.
Gizzard Architect
Gizzard Architect
Gizzard는 트위터에 최적화된 오픈 소스 분산 관리 프레임워크입니다. 물론 앞선 문제를 모든 시스템 상황과 사용자 요구사항을 충족하지는 못하지만, 대부분 데이터 저장소에 존재하는 문제점을 “어느정도” 유연하게 대처합니다.
Gizzard는 데이터를 분할/복제 관리하고, 네트워크 상에서 어플리케이션(PHP/JAVA/Ruby)와 저장소(MySQL/ Lucene/Redis) 사이에 위치합니다.
Partitioning 구성 정보는 데이터 저장 위치를 정의하는 선행 맵핑 테이블에 저장이 됩니다. 그리고 Partition 내부적으로 데이터 조각에 관한 Replication(복제)를 유지하는데, 이것은 “Replicate Tree”로 제어됩니다. 또한 쉬운 Migration과 Load-balancing이 가능하고, 장애 시 유연하게 대처합니다. 멱등성과 가환성 원칙에 의해서 복제 데이터 간 일관성을 유지한다는 특징도 있습니다.

Gizzard를 자세히 살펴봅시다.

  • Gizzard는 네트워크 서비스 미들웨어입니다.클라이언트(PHP/Ruby/JAVA)와 데이터 저장소(Partitioning과 Replication으로 구성) 사이에 위치하고 모든 데이터 질의는 오직 Gizzard를 통해서 이루어집니다. 특별한 인스턴스는 형태가 없는 구조이고,  JVM위에서 실행되며, 상당히 효율적입니다. 트위터에서 사용하고 있는 Gizzard는 머신 당 초당 10,000 쿼리를 수용할 수 있다고 합니다.
  • Gizzard는 어떤 종류의 데이터 저장소에도 적용할 수 있습니다.
    Gizzard는 애초에 네트워크를 통해서 데이터 저장소에 복제하도록 디자인되었습니다. 그렇기 때문에 데이터 저장소를 SQL Server, Lucene, Redis 등 어떠한 솔루션을 선택해도 큰 문제가 없습니다. 그리고 Gizzard는 모든 쓰기 작업을 “멱등성” 및 “가환성”의 원칙에 의거하여 동작하기 때문에, 저장소 자체에 관한 제약을 걸지는 않습니다. 즉, 저장 시스템 자체에 존재하는 제약 사항에는 여전히 의존한다고 볼 수 있습니다. 예를 들어 RDBMS 에서 Unique 조건이 있는 경우 Gizzard에서는 정상적으로 Writing을 시도하나, 데이터베이스 내부에서는  제약사항에 걸려 데이터 입력이 막히게 됩니다.
    Gizzard에 관한 중요한 사실은 순차적으로 데이터가 적용된다는 것을 보장하지 않는다는 것입니다. 그렇기 때문에 쓰기 연산 적용 순서와 관계없이 데이터 일관성을 유지할 수 있도록  설계하는 것이 가장 중요합니다.
  • Gizzard는 데이터 맵핑 선행 테이블에서 데이터 조각을 관리합니다.
    Gizzard는 특정 Shard를 관리를 데이터 범위 관리를 통해서 수행합니다. 선행 테이블의 맵핑 정보 안에는 특정 데이터가 어떤 범위의 구역에 저장이 되어 있는 것에 대한 정보가 숫자 범위로 저장이 되어 있습니다.
    Gizzard Partitioning
    Gizzard Partitioning
    조금 더 자세하게 풀자면, 특정 데이터에 관한 키 값을 Hash 함수로 돌리고 결과 값을 Gizzard에 전달하면, Gizzard는 해당 데이터가 어떤 구역에 속할 지 숫자 정보를 생성합니다. 이러한 함수는 프로그래밍 가능하기 때문에 사용자 구미에 따라서 지역성 혹은 균형성 면에서 최적화할 수 있습니다.
  • Gizzard는 “Replication Tree”로 복제 데이터를 관리합니다.
    선행 테이블에서 지칭하고 있는 각 데이터 조각들은 물리 혹인 논리적인 형태로 구현될 수 있습니다. 물리적인 형태는 특정 데이터 저장소를 의미하고, 논리적인 형태는 데이터 조각에 관한 Tree를 의미합니다. 트리 안의 각 Branch들은 데이터의 논리적인 변형을 나타내고, 각 Node들은 데이터 저장소를 의미합니다. 예를 들어서 아래와 같이 두 단계의 “Replicating Tree”가 있습니다. 하지만 이것들은 선행 테이블에 지칭을 해서 관리되는 오직 하나의 파티션에 저장될 뿐입니다.
    Gizzard Replication
    Gizzard Replication
    위 그림에서 Replicate라는 Branch 역할은 간단합니다. 단지 자신의 모든 자식 Node에 쓰기만 반복하고, 읽기 균형을 유지한다는 단순한 전략 하에 동작합니다. 데이터 저장소 요구사항에 맞게 추가적인 트랜잭션 혹은 Node 개수 전략을 추가하여 데이터 조각을 재구성할 수 있습니다. 그러나 Gizzard는 기본적으로 Replicating, Write-Only, Read-Only, Blocked 등 몇 가지 전략을 제공합니다.
    복제 구성에 관한 세부 형상은 Partition에 따라 다양합니다. 자주 사용되는 데이터에는 더욱 많은 복제를 추가하면 될 것이고, 간헐적으로 사용되는 데이터에는 적은 데이터 복제를 가질 수 있도록 유도하면 됩니다. 한 데이터에 관해서 Primary/Secondary/Tertiary 유지할 수도 있고, 복제를 유지하지 않아도 되는 데이터인 경우에는 Striping Partitioning 구성하여 복제를 하나도 유지하지 않을 수도 있습니다.
  • Gizzard는 장애 상황에 강한 시스템입니다.분산 시스템에서 장애 대처는 가장 큰 이슈입니다. 다수 컴퓨터가 동시에 동작하는 구조이기 때문에, 특정 장비에서 예기치 않게 언제든지 장애가 발생할 수 있습니다. Gizzard는 어떠한 장애 상황에서도 유연하게 대처할 수 있도록 디자인되었습니다. 만약 특정 파티션 내에 있는 복제 데이터가 유실되었을 지라도, Gizzard는 읽기/쓰기 요청을 남은 다른 정상적인 복제 데이터로 전환합니다.
    Gizzard Failover
    Gizzard Failover
    위와 같이, 데이터 조각 안의 “특정 복제 데이터 불능”이 발생하면  Gizzard는 최대한 빠르게 다른 정상적인 복제 데이터에 읽기/쓰기 작업을 시도할 것입니다. 그리고 문제가 발생했던 복제 데이터가 정상으로 돌아오면, 변경 이력을 다시 적용을 합니다. 여기서 가장 기본적인 원칙은 트랜잭션 단위 기록을 견고하게 구현한다는 것입니다. 모든 데이터 복제는 비 동기로 이루어지겠지만, 최대한 지연이 없이 진행됩니다.
    Gizzard Failover
    Gizzard Failover
    만약 “모든 복제 데이터 불능 상태”이 빠지면 해당 데이터에만 읽기 요청이 불가할 뿐 다른 정상적인 데이터에는 영향을 미치지 않습니다. 데이터 조각 안의 모든 복제 데이터가 불능에 빠지면, 일단은 “Error Queue”라는 곳에 상태를 기록하고 버퍼에 변경 내용을 쌓습니다. 그리고 파티션 정상화가 되면, 변경된 데이터를 다시 적용합니다. 최종 데이터 일관성은 “멱등성”과 ”가환성” 원칙 하에 유지되기 때문에 이전 실패가 적용되기 전에 최근 변경 사항이 먼저 적용된다고 하더라도 결과적으로는 아무런 문제가 없습니다.
  • Gizzard는 데이터 이관 방안을 제시합니다.
    경우에 따라서 데이터를 다른 장비로 데이터 이관 작업이 필요합니다. 로드발란싱을 위한 경우도 있고, 데이터가 적은 장비로 옮기는 경우도 있으며, 하드웨어 장애로 인한 경우도 있습니다. Gizzard에서 논리적인 Shard들을 다른 장비로 이관하는 방식을 설명하겠습니다.
    Gizzard Data Migration
    Gizzard Data Migration
    Original에서 Target로 이관한다고 가정했을 때, “Replicate Shard”가 Original 와 Target 사이에, “WriteOnly Shard”는 Target 앞에 위치할 것입니다. 그리고 Original 에서 Target으로 데이터가 복사됩니다. WriteOnly Shard는 Target이 사용 가능한 시점까지 유지하며, 전체 데이터가 복사되기 전까지 Target에서는 어떠한 데이터 조회도 불가합니다. 쓰기 작업이 순서에 관계없이 중복으로 발생할 수 있기 때문에 모든 쓰기 작업은 반드시 멱등성 및 가환성 원칙 하에 동작해야 합니다.
  • 데이터 일관성은 멱등성 및 가환성 원칙 하에 유지합니다.
    동일 데이터를 동시에 변경하려고 하면 데이터 충돌(Confliction) 이 발생합니다. Gizzard는 데이터는 적용 순서를 보장하지 않기 때문에 모델링 시 반드시 이러한 점을 염두 해야 합니다.
    Gizzard Concurrent
    Gizzard Concurrency
    높은 가용성과 데이터가 저장되는 순서를 보장하는 것보다 훨씬 간단한 방식입니다. 예를 들어, Rowz에서는 Timestamp를 기준으로 새로운 데이터 변경 요청 여부를 구분합니다.

타 시스템과 비교

  • GIZZARD Replicate VS MySQL ReplicationMySQL Replication과 가장 큰 차이는 Gizzard는 데이터가 저장되는 순서를 보장하지 않는다는 점입니다. 모든 데이터 일관성은 멱등성 및 가환성에 의거하에 보장되며, 일시적인 데이터 불일치는 허용합니다. 물론 일시적인 데이터 불일치를 허용한다는 점은 MySQL Replication도 마찬가지지만, 데이터를 순차적으로 적용한다는 점은 확연하게 다릅니다. 만약 데이터가 순차적으로 수행되다가 실패를 한다면, 그 이후 적용되어야하는 데이터는 실패했던 연산 처리 후 처리될 수 있습니다.
    Gizzard VS MySQL
  • GIZZARD VS Cassandra
    트위터에서 사용하고 있는 두 데이터 저장 스토어를 간단하게 비교해보았습니다. Gizzard는 데이터 일관성을 멱등성/가환성에 의거하여 유지합니다. 하지만 Cassandra는 데이터 일관성을 Gossip 통신과 Timestamp로 최신 데이터 결정한다는 가장 큰 차이가 있습니다. Cassandra에서 CorrencyLevel 설정 값에 따라서 동시성 레벨을 조정할 수 있는데, 서비스 특성 및 사용자 요구 사항에 따라서 다양하게 설정할 수 있습니다.
    Gizzard VS Cassandra

마치며..

최근 이슈가 되는 대용량 데이터 시스템을 보면 공통적인 부분이 데이터 분산 처리입니다. 이제는 과거 기가바이트 단위가 아닌 테라바이트 단위로 데이터가 증가하기 때문에, 분산 처리에 관한 기술은 지속적인 습득이 필요하겠네요. “하루 2.5억 트윗을 저장하는 트위터의 새로운 저장 스토어” 포스팅을 작성을 하면서 많은 생각을 하게 만든 새로운 경험이었습니다.^^
읽어주셔서 감사합니다.

하루 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 팀 자체 업무가 크게 줄었다는 사실에서 깊은 찬사를 보냅니다.
감사합니다.


20130815

mysql indexing tip

kth 데이터지능팀 성동찬

안녕하세요. 오늘은 MySQL을 사용할 때 지켜야할 사항 몇 가지 정리합니다.
나름 혼자서 정리를 해 본 것들인데, MySQL로 서비스를 준비 중이라면 한 번쯤은 고려를 해봤으면 하는 내용입니다.^^

테이블 설계 시 유의 사항

1. 반드시 Primary Key를 정의하고 최대한 작은 데이터 타입을 선정한다.

  • 로그 성 테이블에도 기본적으로 PK 생성을 원칙으로 함
  • InnoDB에서 PK는 인덱스와 밀접한 관계를 가지므로 최대한 작은 데이터 타입을 가지도록 유지

2. 테이블 Primary Key는 auto_increment를 사용한다.

  • InnoDB에서는 기본 키 순서로 데이터가 저장되므로, Random PK 저장 시 불필요한 DISK I/O가 발생 가능
  • InnoDB의 PK는 절대 갱신되지 않도록 유지
    (갱신 시 갱신된 행 이후 데이터를 하나씩 새 위치로 옮겨야 함)

3. 데이터 타입은 최대한 작게 설계한다.

  • 시간정보는 MySQL데이터 타입 date/datetime/timestamp 활용
  • IP는 INET_ATON(‘IP’), INET_NTOA(int) 함수를 활용
  • 정수 타입으로 저장 가능한 문자열 패턴은 최대한 정수 타입으로 저장

4. 테이블 내 모든 필드에 NOT NULL 속성을 추가한다.

  • NULL을 유지를 위한 추가 비용 발생
    (NULL 허용 칼럼을 인덱싱 할 때 항목마다 한 바이트 씩 더 소요)

5. Partitioning을 적절하게 고려하여 데이터를 물리적으로 구분한다.

  • 데이터 및 인덱스 파일이 커질수록 성능이 저하되므로Partitioning 유도
  • PK 존재 시 PK 내부에 반드시 Partitioning 조건이 포함되어야 함

인덱스 설계 시 유의 사항

1. 인덱스 개수를 최소화 한다.

  • 현재 인덱스로 Range Scan이 가능한지 여부를 사전에 체크
  • 인덱스도 서버 자원을 소모하는 자료구조이므로 성능에 영향을 줌

2. 인덱스 칼럼은 분포도를 고려하여 선정한다.

  • 인덱스 칼럼 데이터의 중복이 줄어들수록 인덱스는 최대의 효과를 가짐
  • 하단 쿼리 결과 값이 1에 가까울수록(0.9이상 권고) 인덱스 컬럼으로 적합함
    1
    2
    
    SELECT count(distinct INDEX_COLUMN)/count(*)
    FROM TABLE;

3. 커버링 인덱스(Covering Index)를 활용한다.

4. 스토리지 엔진 별 INDEX 특성을 정확히 인지한다.

  • InnoDB에서 데이터는 PK 순서로 저장되고, 인덱스는 PK를 Value로 가짐
  • MyISAM은 PK와 일반 인덱스의 구조는 동일하나, Prefix 압축 인덱스를 사용
    (MyISAM 엔진에서 ORDER BY 시 DESC는 가급적 지양)

5. 문자열을 인덱싱 시 Prefix 인덱스 활용한다.

  • 긴 문자열 경우 Prefix 인덱스(앞 자리 몇 글자만 인덱싱)를 적용
    1
    
    CREATE INDEX IDX01 ON TAB1(COL(4), COL(4))
  • Prifix Size는 앞 글자 분포도에 따라 적절하게 설정
    (하단 결과가 1에 가까울 수록 최적의 성능 유지, 0.9이상 권고)
    1
    2
    
    SELECT count(distinct LEFT(INDEX_COLUMN,3))/count(*)
    FROM TABLE;

6. CRC32함수 및 Trigger를 활용하여 인덱스 생성한다.

  • URL/Email같이 문자 길이기 긴 경우 유용
  • INSERT/UPDATE 발생 시 Trigger로 CRC32 함수 실행 결과 값을 인덱싱
  • CRC32 결과값을 저장할 칼럼 추가 및 인덱스 생성
    1
    2
    
    alter table user_tbl add email_crc int unsigned not null;
    create index idx01_email_crc on user_tbl (email_crc);
  • Insert Trigger 생성
    1
    2
    3
    4
    5
    6
    
    create trigger trg_user_tbl_insert
    before insert on user_tbl
    for each row
    begin
    set new.email_crc = crc32(lower(trim(new.email)));
    end$$
  • Update Trigger 생성
    1
    2
    3
    4
    5
    6
    7
    8
    
    create trigger trg_user_tbl_update
    before update on user_tbl
    for each row
    begin
    if old.email<> new.email then
    set new.email_crc = crc32(lower(trim(new.email)));
    end if;
    end$$
  • 검색 쿼리
    1
    2
    3
    4
    
    select *
    from user_tbl
    where email_crc = crc32(lower(trim('mail@domain.com')))
    and email= 'mail@domain.com'
    CRC32 결과가 중복되어도, email값을 직접 비교하는 부분에서 중복이 제거됩니다.

7. 중복 인덱스 생성 회피


  • MySQL은 동일한 인덱스를 중복 생성해도 에러를 발생하지 않음
  • Primary Key로 구성된 칼럼과 동일한 인덱스를 생성하지 않도록 주의


출처 : http://dev.kthcorp.com/2012/05/15/mysql-table-index-design-tip/

Articles