Category Archives: Research

gywndi’s 연구

PMM 이야기 1편 – INTRO

Overview

정말 오랜만에 글을 써봅니다. 은행이 오픈한지도 어언 8개월째를 훌쩍 접어들었네요. 여전히 MySQL 서버군에는 이렇다할 장애 없이, 무난(?)하게 하루하루를 지내고 있습니다.. (아.. 그렇다고 놀고만 있지는 않았어요!!)

사실 그동안의 경험과 삽질을 바탕으로, 필요성을 느꼈던 다양한 부분을 중앙 매니저에 최대한 녹여보았고, 그 집대성의 결과가 지금 뱅킹 MySQL시스템입니다. MHA 관리, 스키마 관리, 파티션 관리, 패스워드 관리, 백업/복구 관리..아.. 또 뭐있더라.. -_-;; 암튼, 귀찮은 모든 것들은 최대한 구현을 해놓았지요.

그러나, 예전부터 늘 부족하다고 생각해왔던 한가지 분야가 있는데.. 그것은 바로 모니터링입니다. 시스템에 대한 가장 정확한 최신 정보는 바로 모니터링 지표입니다. 만약, 제대로된 모니터링 시스템 환경 속에서, 실제 서비스의 영속성과 시스템의 매니지먼트를 “모니터링 지표”를 통해서 제대로된 “에코 시스템”을 구축할 수 있다면? 등골이 오싹할 정도의 선순환 작용으로 엄청 견고한 시스템을 구축할 수 있겠죠.

그래서, 한동안 관심가지는 분야가 바로 모니터링, 그중 Percona에서 오픈소스로 제공하는 PMM 요 녀석입니다. 우선은 서두인만큼.. 간단하게 PMM 소개를 해보고, 무엇이 부족한지 썰을 풀어보도록 하겠습니다.

PMM?

PMM(Percona Monitoring and Management)은 Prometheus 기반의 모니터링 솔루션입니다. 바로 몇년전 Percona 의 블로그에 이 관련해서 내용이 포스팅된 적이있었고, 한번 해보고 “와~” 하고 넘어갔었는데.. (은행 구축단계라..쿨럭)
(https://www.percona.com/blog/2016/02/29/graphing-mysql-performance-with-prometheus-and-grafana/)

Prometheus의 여러 프로젝트를 Percona 엮어 재구성 하고, 필요한 것들은 추가로 구현하요 Docker 이비지로 배포하고 있는 녀석이 바로 PMM입니다. ^^

pmm-diagram

pmm-diagram

뭔가 굉장히 복잡해 보입니다만.. 여기서 중요한 것 몇가지만 추려본다면..??

1. node_exporter, mysqld_expoter

모니터링 대상 노드(서버)에 서버에 데몬 형태로 구동되는 에이전트입니다. Prometheus에서 에이전트 포트로 매트릭 요청을 하면 즉각 서버의 현 상태를 보내는 역할을 합니다. Percona에서 Prometheus의 exporter를 (아마도) 포트떠서 구현해놓은 듯 하군요.

<Prometheus 프로젝트>
https://github.com/prometheus/node_exporter
https://github.com/prometheus/mysqld_exporter

<Percona 프로젝트>
https://github.com/percona/node_exporter
https://github.com/percona/mysqld_exporter

node_exporter쪽은 제대로 보지 않았지만, percona의 mysqld_expoter는 아래와 같이 크게 세 가지로 나눠서 매트릭을 제공한다는 점에서 기존 Prometheus와는 큰 차이가 있습니다. (참고로, Prometheus는 Parameter 전달 형식으로 매트릭을 선별하여 제공합니다.)

  • mysql-hr (High-res metrics, Default 1s)
    Global Status / InnoDB Metrics
  • mysql-mr (Medium-res metrics, Default 5s)
    Slave Status / Process List / InnoDB Engine Status.. etc
  • mysql-lr (Low-res metrics, Default 1m)
    Global Variables, Table Schema, Binlog Info.. etc

자세한 것은 하단 깃헙 142 라인부터 확인을 해보세요. ㅎㅎ
https://github.com/percona/mysqld_exporter/blob/master/mysqld_exporter.go

참고로, 저기 나열되어있다고, 모두 활성화해서 올라오지 않습니다. exporter의 구동 파라메터에 따라 선별되어서 수집이 됩니다.. (예를들어 Query Digest 정보는 기본 수집되지 않습니다.) /etc/init.d 하단에 위치한 에이전트 구동 스크립트를 보시면 될 듯..^^

2. pmm-admin

각종 exporter들을 구동 및 제어를 하는 녀석입니다. pmm-admin은 노드 관리를 효과적으로 하기 위해, PMM에 포함된 Consul 서버에 exporter 에이전트 접속 정보(아이피와 포트)를 등록합니다. Prometheus는 매 수집주기마다,  Consul 에 등록된 에이전트 리스트를 가져와서 모니터링 매트릭을 수집합니다.

3. pmm-mysql-queries

DB 혹은 OS의 매트릭 정보들은 Prometheus에서 수집/저장을 합니다. 오로지 매트릭만.. -_-;; Prometheus의 동작과는 별개로, DB 서버에서 pmm-server로 데이터를 던지는 녀석이 있는데, 이것은 유입 혹은 슬로우 쿼리에 대한 정보입니다. QAN API로 데이터를 던지는데.. pmm-server의 80포트 /qan-api에 던지면, nginx가 이 데이터를 받아서, 127.0.0.1:9001로 토~스 하여 최종적으로 쿼리를 수집하는 과정을 거칩니다.

한마디로, 쿼리 수집을 위해서는 “[ DB ] –80–> [ PMM ] 형태로 포트 접근이 가능해야한다” 합니다. ㅎㅎ

물론, Prometheus로만으로도 얼마든지 수집이 가능합니다만.. Percona에서 제공하는 QAN 플러그인을 활용할 수 없다는.. -_-;; 쿨럭 사실.. QAN 하나만으로도 블로그 한두개 나올 정도이니.. 나중에 얘기를 해볼께요.

4. Prometheus

타임시리즈 기반의 저장소로, 사실상 PMM의 핵심입니다. 데이터 수집을 비롯해서 저장 그리고 쿼리 질의, Alert까지.. 에이전트의 포트에 접근해서, 모니터링 시스템의 매트릭을 Pull 방식으로 끌어오는 역할을 하지요.
https://prometheus.io/docs/introduction/overview/

앞에서 간단하게 Consul에 대해서 얘기를 했었는데.. pmm-admin이 pmm의 consul에 모니터링이 될 대상들의 아이피와 포트 번호를 넣어주면, Prometheus는 이 정보를 바탕으로 등록된 scrape_interval마다 각 DB서버 에이전트에 접근하여 모니터링 지표를 수집합니다.

- job_name: mysql-hr
  scrape_interval: 1s
  scrape_timeout: 1s
  metrics_path: /metrics-hr
  scheme: http
  consul_sd_configs:
  - server: localhost:8500
    datacenter: dc1
    tag_separator: ','
    scheme: http
    services:
    - mysql:metrics

이것 외에도 Grafana에서 지표를 보여줄 데이터 저장소 역할도 하며, AlertManager 기능도 제공합니다.  Prometheus에 대한 조금더 자세한 설명은.. 훗날(?) 다시 얘기해보도록 하겠습니다. (이거 하나로 책 한권 사실 나옵니다 -_-;; 아놔)

5. Consul

pmm-admin이 consul에 모니터링 대상이 될 장비를 등록하면, 하단과 같이 consul에 저장되고, prometheus는 이 에이전트에 접속하여 매트릭을 수집합니다.

consul

consul

6. Grafana

데이터의 핵심이 Prometheus였다면, Visualization의 핵심은 바로 Grafana입니다. PMM이 강력한 이유가 바로 이미 만들어져 있는 다양한 Grafana 대시보드라고 생각합니다. 적어도 지표를 보여주는 부분에서는 타의 추종을 불허합니다. ^^ 특히나, 내가 원하는 그래프를 내 뜻대로 추가로 마음껏 만들 수 있다는 점에서는 더욱 매력적이죠.

grafana

grafana

Grafana 에서도 나름의 Alert을 설정하고 보낼 수 있습니다만.. Template Variable을 사용할 수 없기 때문에.. 각 지표마다, 각 서버마다 모두 수작업으로 한땀한땀 등록해야하는 노고가.. (향후 Template Variable을 사용할 수 있게 개선된다고는 하는데..흠..)
https://www.percona.com/blog/2017/02/02/pmm-alerting-with-grafana-working-with-templated-dashboards/

상단 링크를 읽어보면, 어떤 상황인지가 확 와닿을듯.^^

7. Orchestrator

MySQL의 Mater/Slave 토폴리지를 그려주는 녀석으로.. 아직까지 주의깊게 구성해보지는 않았습니다. 수십, 수백대 관리를 고려해봤을때.. 왜 해봐야할지 당위성을 가질 수 없더군요. 그래서 팻패스

Problem

PMM의 구성요소들을 쭉 열거해보았습니다만.. 사실.. 저것 요소 하나하나가 굉장히 큰 꼭지예요. ㅠㅠ 가장 만만한 exporter 하나하나도 해당 시스템 세세한 부분까지 수집하는 큰 프로젝트입니다. ㅠㅠ

아무튼, 지금까지 이것저것 해본 결과에 따라 현재 대단히 부족한 점 몇 꼭지를 콕 찝어서 얘기를 해보도록 하겠습니다.

1. 알람 발송

냉정하게 얘기하자면, 현재 PMM에서는 시스템 문제 시 알람을 효과적으로 발송할 수 없습니다. 나름 Grafana의 Alerting 기능을 통해서 흉내 정도 낸 것같은데.. 실무 적용에는 턱없이..아니 절대 불가합니다. 앞서 얘기한 것 처럼, 모든 지표와 인스턴스에 따른 임계치를 한땀한땀 등록하면 가능하겠지만.. 이건 인간이 할 일은 아니겠죠?

가장 좋은 방안은 Prometheus의  AlertManager를 활용하는 것인데.. ㅎㅎ

2. 수집 주기

수집 주기를 유연하게 변경할 수 있는 Docker 파라메터가 부족합니다. PMM에서 기본 수집 주기는 linux/mysql-hr은 1초, mysql-mr은 5초, mysql-lr은 1분입니다. 사실 10대 미만만 모니터링한다면, 이 수집 주기에 큰 이슈는 없습니다만.. PMM 서버 한대에 20대 이상의 모니터링 대상들을 붙이기 시작하면, 대 재앙이 펼쳐집니다. (로드 20,30,40 펑! ㅋㅋㅋ)

문제는 Docker 이미지로만 제공되는 PMM에서, Docker 컨테이너 초기화 시 옵션 제어가 제한적이라는 점에 있습니다. 실제 PMM Docker 컨테이너의 /opt/entrypoint.sh를 확인해보면.. 그나마 1초 수집주기 항목만 1~5초 사이로 조정할 수 있는 정도로만 구현해놓았습니다. -_-;; 뷁!

# Prometheus
if [[ ! "${METRICS_RESOLUTION:-1s}" =~ ^[1-5]s$ ]]; then
    echo "METRICS_RESOLUTION takes only values from 1s to 5s."
    exit 1
fi
sed -i "s/1s/${METRICS_RESOLUTION:-1s}/" /etc/prometheus.yml

3. 익명 로그인

이건 당췌 이유를 알 수 없습니다. PMM에 포함된 Grafana 기본은 익명 사용자도 로그인 없이 지표 확인이 가능하게 되어 있습니다.

https://github.com/percona/pmm-server/blob/master/playbook-install.yml 의 209 라인을 보면 분명 의도적으로 로그인 없이도 접근 가능하도록 설정합니다. (왜?)

- name: Grafana                    | Enable Anonymous access
  when: not grafana_db.stat.exists
  ini_file:
    dest: /etc/grafana/grafana.ini
    section: auth.anonymous
    option: enabled
    value: true

4. 진짜 대시보드

진정한 의미의 대시보드가 없습니다. 특히나, 장애 알람 기능이 현재 전무하기 때문에, 현 시점 이상이 있는 시스템을 재빠르게 캐치할 수 있는 방안이 없습니다. 즉, 지표 보기는 굉장히 최적화가 되어 있습니다만, 문제 발생 시 빠른 감지 및 정보 제공 측면에서는 굉장히 취약한 상태라고 봐야겠지요, ㅜㅜ

참고로, 제가 원하는 대시보드는 아래와 같은 모습이며, 특정 서버 문제 시 바로 현황 파악이 가능한 모습입니다.

dashboard

dashboard

5. Grafana Sqlite

PMM에 포함된 Grafana는 모든 메타 정보(대시보드를 포함한 지표 정보)를 SQLite에 저장합니다. 사실, 지표에 별다른 수정없이 기본값 그대로 사용한다면 큰 문제는 없지만.. 그렇지 않다면 생각보다 꽤나 많은 부분을 변경해야합니다. ㅠㅠ JSON 스트링 그대로 SQLite TEXT컬럼에 저장되어 있기 때문에.. 의외로 대량 편집에 상당 부분 노고가 필요합니다. 게다가.. 데이터 잘못 삭제 시.. 복구 방안은?? -_-;;

PMM안에도 mysql 이 기본 설치되기 때문에.. 이왕이면 이 녀석을 잘 활용했으면 하는 바램이;; 쿨럭 (무엇보다, 이럴꺼면, mysql5.7의  json 타입을 활용해보면 어떨까 생각도 합니다.)

Conclusion

Percona는 PMM을 Docker 이미지로 배포합니다. 사실 Docker로 배포되지 않는다고 한다면, 나름 커스터마이징이 용이할 것 같다는 생각은 듭니다만.. Percona에서 기능 구현 후 새로운 Docker를 배포한다고 생각해봤을 때, 자칫 잘못하면 그간의 노고가 한방에 날라갈 수 있겠습니다.

즉, 개인적인 생각이기는 하지만.. Docker 컨테이너를 임의로 수정하기 보다는, 최대한 PMM 의 흐름에 맞춰서 필요한 부분을 취하는 것이 현명할 것이라 생각합니다. 물론 필요하다면, Percona도 설득도 해봐야겠죠. ^^

우선, 당장은 PMM을 실무에 바로 적용하기에는 무리가 있습니다..만.. 이 부족한 부분을  기존 소스 흐름에서 크게 벗어나지 않는 수준으로 어떻게 대안을 찾아가고 있는지.. 다음 블로그부터 하나하나 정리를 해보도록 하겠습니다.
(참고로, 앞선 문제점들은 대나름의 해결 방안을 만들어놓았습니다. ㅎㅎ)

일단, PMM 서버 구성을 해봐야겠죠? ^^

MySQL_5.7의 n-gram 전문 검색을 이상하지 않게 써보아요.

Overview

MySQL5.6부터는 InnoDB에서도 전문검색이 가능하기는 하였습니다만.. 아쉽게도 여전히 공백 기준으로 단어들이 파싱이 되는 “MeCab Full-Text Parser Plugin” 방식으로 동작합니다. 즉, 한국말처럼 공백만으로 단어를 파싱할 수 없는 언어의 경우에는 크게 매력적이지는 않습니다. InnoDB에서 전문검색 인덱싱이 가능하다는 것은 Transaction이 전제로 이루어지는 것이라고 볼 수 있기에.. 리플리케이션 및 시점 백업/복구 측면에서는 혁신으로 볼 수 있습니다.

반드시 Limit로 끊어서 가져오고자 한다면, “Order By”로 정렬을 하세요~ 이 관련해 버그가 있고 조만간 픽스될 예정이기는 합니다. (n-gram 처리 시  스토리지 엔진에서  limit이 영향을 미쳐 제대로된 결과 도출 혹은 최악의 경우 크래시까지 발생할 수 있어요.)

그런데 MySQL 5.7부터는 InnoDB에서도 n-gram 방식의 전문 검색 인덱스를 지원하면서, 한국어/중국어/일본어에서도 효율적인 전문 검색이 가능하게 되었습니다. 물론 mroonga와 같은 Third party 스토리지 엔진을 설치해서 사용을 할 수 있겠지만.. 백업/복구가 늘 이슈가 늘 따라다니고는 했습니다.

참고로, 전문 검색이란.. LIKE ‘%검색어%’ 과 동일한 역할을 한다고 보시면 되겠습니다. (조금 의미는 다르지만.. ㅋ)

N-GRAM?

자세한 내용은 하단 링크를 쭉 읽오보시면 되겠습니다만..

https://dev.mysql.com/doc/refman/5.7/en/fulltext-search-ngram.html

간단하게 저 메뉴얼 내용을 인용하자면, 컨텐츠의 인덱스를 아래와 같이 첫글자/두글자..(1그램,2그램,3그램..) 등등으로 나누어서 파싱하여 전문검색 인덱스를 만드는 것이라고 생각하면 되겠습니다.

n=1: 'a', 'b', 'c', 'd'
n=2: 'ab', 'bc', 'cd'
n=3: 'abc', 'bcd'
n=4: 'abcd'

그리고, 기본적으로 InnoDB에서 제공해주는 ngram의 최소 토큰 사이즈(ngram_token_size)는 2이고, 위에서 “n=2″ 부터 토큰을 만들게 됩니다. 당연한 이야기겠지만, n-수치가 낮을수록 토큰 수가 많아질 것이기에, 모든 검색어들이 3글자부터 시작된다면 이 수치를 3으로 상향 조정하는 것도 인덱싱 관리 및 사이즈 안정성에 도움이 되겠습니다.

Simple Test

mysql> CREATE TABLE `articles` (
    ->  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
    ->  `body` text,
    ->  PRIMARY KEY (`id`),
    ->  FULLTEXT KEY `ftx` (`body`) WITH PARSER ngram
    ->) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
mysql> insert into articles (body) values ('east');
mysql> insert into articles (body) values ('east area');
mysql> insert into articles (body) values ('east job');
mysql> insert into articles (body) values ('eastnation');
mysql> insert into articles (body) values ('eastway, try try');
mysql> insert into articles (body) values ('try try');

“WITH PARSER ngram” 부분을 명시적으로 적지 않으면, 기존 전문검색 인덱스로 구성되니 유의하시기 바랍니다. 자, 이 상태에서 ‘st’가 들어있는 데이터를 검색해볼까요?

mysql> SELECT * FROM articles WHERE MATCH(body) AGAINST('st' IN BOOLEAN MODE); 
+----+------------------+
| id | body             |
+----+------------------+
|  7 | east area        |
|  6 | east             |
|  8 | east job         |
|  9 | eastnation       |
| 10 | eastway, try try |
+----+------------------+

참고로, 위 정렬 순서는 단어 매칭 수가 많은 데이터 순서로 자동 정렬이 됩니다.

STOPWORD Problem

우연찮게 동료가 자신의 이름을 검색을 하다 발견했던 이슈인데.. 위 상태에서 “ea”를 포함하는 데이터를 검색해봅니다. 분명히 데이터가 존재함에도, 아래와 같이 한 건도 나오지 않는 점을 확인할 수 있습니다.

mysql> SELECT * FROM articles WHERE MATCH(body) AGAINST('ea' IN BOOLEAN MODE); 
Empty set (0.00 sec)

mysql> SELECT * FROM articles WHERE MATCH(body) AGAINST('eas' IN BOOLEAN MODE); 
Empty set (0.01 sec)

아무래도 5.7에서 새롭게 들어온 n-gram방식이라서 그런지요. INNODB_FT_DEFAULT_STOPWORD에 의존적으로 n-gram 도 동작을 하게 됩니다. 기존 전문검색인덱스에서는 공백 기준으로 토큰화되기 때문에, 영문에서 자주 쓰이는 단어들은 인덱스로 분류되지 않도록 지정을 합니다. 이 단어들이 모여있는 테이블이 바로 information_schema 에 위치한 INNODB_FT_DEFAULT_STOPWORD 테이블이죠.

mysql> SELECT * FROM INFORMATION_SCHEMA.INNODB_FT_DEFAULT_STOPWORD;
+-------+
| value |
+-------+
| a     |
| about |
| an    |
| are   |
| as    |
| at    |
..중략..
| who   |
| will  |
| with  |
| und   |
| the   |
| www   |
+-------+

그런데 참 재미있는 것이.. 단어 파싱의 기준은 n-gram으로 하면서, 실제 룰 일부는 기존의 “MeCab” 기준으로 적용한다는 사실이죠. 위 예제에서는 위에 포함이 되어 있는 “a” 단어로 인하여 “east”문자는 “e”, “st” 이렇게 파싱이 되고, 결과적으로 “ea” 검색 결과에서 누락이 되는 상황이 되어버리고 만 것입니다.

INNODB_FT_DEFAULT_STOPWORD에 대한 조금더 상세한 얘기는 하단 매뉴얼을 참고하시면 되겠습니다.

https://dev.mysql.com/doc/refman/5.6/en/fulltext-stopwords.html

Workaround

현재로서는 “information_schema” 하단에 위치한 이 테이블의 데이터를 조작하거나, 변경할 수 있는 방안은 없습니다.

오라클/Percona에서 어떻게 대응해줄지는 모르겠지만, 일단 당장 이 이슈가 해결될 것 같지는 않기에, 조금 트릭으로 풀어야겠습니다.  그래서 아래와 같이 데이터가 전혀 없는 ngram_stopwords 테이블을 만들고, 이 테이블을 INNODB_FT_DEFAULT_STOPWORD 대용으로 사용하겠다고 파라메터 지정하는 것인데요.

mysql> CREATE TABLE mysql.ngram_stopwords(value VARCHAR(18)) ENGINE = INNODB;
mysql> SET GLOBAL innodb_ft_server_stopword_table='mysql/ngram_stopwords';

기존에 이미 n-gram 전문검색인덱스가 존재한다면, 반드시 테이블 재구성을 하시기 바랍니다. (모든 데이터를 다시 파싱을 해야하기 때문이죠.)

mysql> alter table articles engine = innodb;

이제 아까처럼 다시 “ea”로 검색을하면, 아래와 같이 예~쁜 결과가 나오게 됩니다. ^____^

mysql> SELECT * FROM articles WHERE MATCH(body) AGAINST('ea' IN BOOLEAN MODE); 
+----+------------------+
| id | body             |
+----+------------------+
|  7 | east area        |
|  6 | east             |
|  8 | east job         |
|  9 | eastnation       |
| 10 | eastway, try try |
+----+------------------+

단, 이렇게 트릭을 사용한 이상.. 사이드이펙트가 무엇일지 생각을 해봐야겠지요? stopword와 관련된 테이블을 여러개를 가질 수 없다는 점입니다. 즉, MeCab 파서에도 영향을 줄 것이므로, 반드시 이 점 인지시기 바랍니다. ^^ (가장 좋은 해결책은 n-gram 파서 경우에는 INNODB_FT_DEFAULT_STOPWORD를 적용하지 않는 것일텐데.. ㅎㅎ)

이 부분은 조만간 Percona 에서는 파라메터로 제어할 수 있도록 해준대요~ 기다리세요. ㅋㅋ

Conclusion

역시. 신기능은 조금 기다려야했나.. 버그 이슈로 마음고생 많이 했습니다.

그렇지만.. 어찌어찌 해결이 되었고. 아마도 다음 릴리즈에서는 Percona에서는 픽스해서 GA로 오픈할 듯 하네요. 안정화를 더 해봐야하고, 성능 튜닝도 이루어져야할 것이겠지만.. 점차 좋은 기능들이 InnoDB로 들어오게 되면서, 리플 안정성 및 데이터 백업/복구 측면에서 대단한 발전이 있는 것은 사실입니다.

물론. Elastic Search와 같은 솔루션으로 해결해볼 수 있겠으나, 적절한 데이터 사이즈에서는 충분히 MySQL의 쿼리캐시(Result Cache)를 같이 조합해보면 대단한 효과를 낼 수 있다고 판단합니다.

데이터는 데이터일뿐.. ㅋㅋ 좋은밤 되세요.^^

세상만사 귀찮은 MySQL DBA를 위한 자동 복구 시나리오

Overview

안녕하세요. 요새 창고 대방출! 그동안 미뤄 두었던 얘기들을 연달아 공유합니다. (마스터 스크립트를 만들어야하는 수고를 덜기 위해.. 해당 스크립트 제거 및 스크립트 수정하였습니다.)

MySQL을 사용하는 이상, 리플리케이션 활용에서 벗어나기 쉽지 않은데요. 그 말은 곧 다수의 동일한 데이터를 가진 여러개의 서버를 운영관리 해야한다는 말과 같고.. 장비가 많아진다는 것은 그만큼 데이터 복구가 많다는 이야기이기도 하지요. 특히나 샤딩 환경으로 데이터 폭증을 대비해두었다면 더욱 그렇습니다.

게다가 복구 시 새벽 백업을 사용한다는 말은 곧 새벽 이후로 저장이된 변경 이력을 일괄 적용을 해야하고.. 이 내용이 많으면 데이터 동기화 시간도 적지않게 소모되고.. (횡설수설~)

이런 환경 속에서 세상 만사가 귀찮은 DBA를 위해.. 복구 자동화 방안에 대해 간단하게 얘기를 해봅니다.

Prepare

아.. 크게 중요한 것은 아니고.. 제가 구성을 한 환경입니다. 뭐.. “Linux+MySQL+Xtrabackup”이 되는 환경이면 큰 이슈는 없다는 생각이.. ^^ (단, Xtrabackup은 MySQL5.7 데이터를 백업 가능해야합니다.)

  • CentOS 6.8
  • MySQL 5.7.14
  • Xtrabackup-2.4.3
  • pv

그리고 일단 저는 소심하기 때문에..  GTID가 아닌 기존 Binlog Position 기반의 리플리케이션 기준으로 데이터 복제를 하도록 구성하였습니다. :-)

그리고 문제없이 데이터를 땡겨오는 수단으로.. SCP를 활용할 것인데.. 이 말은 곧 인증 관련하여 패스워드 없이 데이터를 땡겨올 수 있도록 미리미리 id_rsa.pub 키를 리플리케이션 노드끼리 페어링해놓도록 합시다.  즉.. 패스워드 없이 아래와 같이 서버 접근이 되도록 준비해야한다는 것이죠. (이 부분은 패스~)

server1$ ssh mysql@server2
server2$ ssh mysql@server1

Restore Scenario

서비스의 특성 혹은 개개인의 선호도에 따라 다르겠지만.. 제가 가장 중요시한 가치는 일순위는 역시 복구 효율성입니다. 물론, 효율성 이전에 서비스 영향도는 거의 없어야 하겠죠. ㅜㅜ 그렇다면, 당연한 이야기겠지만.. 데이터 전송 트래픽에 대한 조절 방안도 필요하겠죠.

일반적으로 복구를 하는 케이스라면, 두 가지 경우를 생각해볼 수 있습니다.

  1. restore from master data
    마스터의 데이터로 슬레이브를 추가하는 케이스로.. 받아온 서버의 Binlog 포지션이 슬레이브 구성의 정보가 됩니다.
  2. restore from slave data
    슬레이브의 데이터로 슬레이브를 구성하는 케이스로.. 현재 슬레이브와 동일한 슬레이브를 하나 더 추가하는 케이스입니다. 데이터를 받아오는 슬레이브의 Slave Status 정보가 복구 슬레이브의 구성 정보가 됩니다.

진행하기에 앞서서 양해의 말씀을 드리자면.. 이 자리는 어디까지나 자동 복구 시나리오를 설명하는 공유의 장이지.. 현재 날코딩으로 제가 꾸역꾸역 구현한 스크립트를 공유하는 자리는 아니랍니다. ^^ 뭐. 너저분한 코드 정리를 하게 되고.. 이것저것 엮인 것들을 심플하게 풀어놓을 수 있다면 얘기가 달라지겠지만.. 전 귀찮아하는 DBA이기에.. Validation Check 혹은 패스워드 암호화 같은 설명은 모두 스킵하였습니다. ㅋㅋ

그리고 데이터 복구 사전 지식으로.. Xtrabackup의 하단 매뉴얼을 먼저 읽어보심 큰 도움이 되리라 확신합니다.
https://www.percona.com/doc/percona-xtrabackup/2.4/howtos/setting_up_replication.html

자. 그럼 이야기를 풀어볼까요?

case 1) restore from master data

앞서 얘기한대로.. 마스터로부터 데이터를 받아서, 바로 하단에 슬레이브를 구성하는 케이스로.. 하단 이미지와 같은 모습으로 데이터를 구성하는 케이스입니다.

Restore_From_Master_Data

Restore_From_Master_Data

1) 데이터 끌어오기

Xtabackup의 마지막 과정 중.. InnoDB가 아닌 데이터를 카피하는 과정이 있는데.. “–no-lock” 옵션을 주지 않으면, 데이터 정합성 보장을 위해 데이터 카피하는 동안 글로벌 Lock을 걸고 수행하게 됩니다. 즉.. 서비스에 영향을 준다는 말이겠지요.

innobackupex 부분을 바로 쉘에 넣어서 한방에 수행하도록 아래와 같이 작성합니다.(로컬 백업 습관으로.. 대충 변경했더니.. 생각이 없었네요. ㅋㅋ) 스트리밍으로 전송되는 백업 데이터를 pv로 제어(초당 50메가, 바쁜 서버는 더욱 약하게)하고..

ssh -o StrictHostKeyChecking=no mysql@${TARGET_HOST} \
 "innobackupex \
 --host="127.0.0.1" \
 --user=backupuser \
 --password="backuppass" \
 --no-lock \
 --stream=xbstream \
 /data/backup | pv --rate-limit 50000000" 2> innobackupex.log \
 | xbstream -x 2> xbstream.log

innobackupex.log와 xbstream.log 두 곳의 로그를 보고, 받아온 백업 데이터 유효성을 검수할 수 있습니다. innobackupex.log에는 “completed OK!” 로 끝이 나야 정상적으로 데이터를 받아온 것이고, xbstream.log결과에는 아~무런 내용도 없어야 제대로 스트리밍을 풀어냈다고 볼 수 있어요.

2) 리두로그 적용하기

앞선 과정이 정상적으로 수행되었다는 가정 하에 진행하는 것으로..  데이터가 백업 및 전송되는 동안 변경된 데이터를 적용하는 과정입니다. 이 역시 innobackupex.log의 마지막이 “completed OK!” 로 끝이 나야 정상적으로 로그가 적용된 것으로 볼 수 있습니다.

innobackupex --apply-log . 2>> innobackupex.log

3) 슬레이브 구성하기

마지막으로.. 실제 슬레이브로 구성할 마스터의 포지션을 추출하는 과정으로.. apply log 이후 생성이 되는 “xtrabackup_binlog_pos_innodb” 에서 위치 정보를 아래와 같이 추출합니다. 보통은 아래와 같이 탭으로 구분하여 마스터의 바이너리 로그 포지션이 기록되어 있는데..

mysql-bin.000001     481

저는 아래와 같이 sed 명령을 통해서 조금 있어보이게(?) 로그 포지션을 정의하였습니다. awk든 뭐든.. 편한 방법으로.. ^^

MASTER_LOG_FILE=`sed -r "s/^(.*)\s+([0-9]+)/\1/g" xtrabackup_binlog_pos_innodb`
MASTER_LOG_POS=`sed -r "s/^(.*)\s+([0-9]+)/\2/g" xtrabackup_binlog_pos_innodb`

자.  이제, 마스터 서버도 정해졌고, 리플리케이션 구성을 위한 바이너리 로그 포지션도 정해졌으니.. 아래와 같이 슬레이브를 구성해보도록 해봅시다.

echo "change master to
 master_host='${TARGET_HOST}',
 master_user='repl',
 master_password='replpass',
 master_log_file='${MASTER_LOG_FILE}',
 master_log_pos=${MASTER_LOG_POS}; 
 start slave;"\
| mysql -uroot -pxxxxxx

이렇게 하면 큰 무리 없이 아래와 같이 마스터 데이터를 활용하여 신규 슬레이브를 구성 완료입니다.

Restore_From_Master_Data_Final

Restore_From_Master_Data_Final

case 2) restore from slave data

두번째 케이스.. 현재 위치한 슬레이브 서버의 데이터를 활용하여 신규 슬레이브 서버를 추가하는 케이스인데.. 아래와 같은 데이터 흐름을 가집니다. 정상적으로 구동 중인 슬레이브 서버로부터 데이터를 가져와서, 해당 슬레이브의 슬레이브 정보를 바탕으로 동일한 위상의 추가 슬레이브를 구성하는 시나리오죠. (말이 참 어렵죠? ㅋ)

Restore_From_Slave_Data

Restore_From_Slave_Data

1) 데이터 끌어오기

이번에는 슬레이브로부터 받아온 백업 스트리밍 데이터를 바로 xbstream으로 풀어버리는 과정으로, 마찬가지로 pv로 전송량 제어를 하였습니다. 어차피 슬레이브이니.. 서비스 투입된 장비가 아니라면.. 기가비트로 땡겨와도 문제 없으니.. 굳이 안써도 될 것 같지만..;;

앞 게이스와 다르게 슬레이브 포지션을 받아올 목적으로 아래와 같이 ” –slave-info” 옵션을 지정해줍니다. (이 옵션을 줘야, 슬레이브 상태를 기록합니다.)

ssh -o StrictHostKeyChecking=no mysql@${TARGET_HOST} \
 "innobackupex \
 --host="127.0.0.1" \
 --user=backupuser \
 --password="backuppass" \
 --slave-info \
 --stream=xbstream \
 /data/backup | pv --rate-limit 50000000" 2> innobackupex.log \
 | xbstream -x 2> xbstream.log

마찬가지로 innobackupex.log와 xbstream.log 두 곳의 로그를 보고, 받아온 백업 데이터 유효성을 검수할 수 있습니다. innobackupex.log에는 “completed OK!” 로 끝이 나야 정상적으로 데이터를 받아온 것이고, xbstream.log결과에는 아~무런 내용도 없어야 제대로 스트리밍을 풀어낸 것이죠.

2) 리두로그 적용하기

데이터가 백업 및 전송되는 동안 변경된 데이터를 적용을 이전과 마찬가지로 동일하게 수행을 합니다. 이 역시 innobackupex.log의 마지막이 “completed OK!” 로 끝이 나야 정상적으로 처리된 것입니다.

innobackupex --apply-log . 2>> innobackupex.log

3) 슬레이브 구성하기

슬레이브 포지션은 이제.. 앞서 마스터에서 추출한 것과는 조금 다르게 정의를 해야하는데.. 먼저 바이너리 로그 포지션은 “xtrabackup_slave_info”에서 찾아내야 합니다.

마지막으로.. 실제 슬레이브로 구성할 마스터의 포지션을 추출하는 과정으로.. apply log 이후 생성이 되는 “xtrabackup_binlog_info” 에서 위치 정보를 아래와 같이 추출합니다. 보통은 아래와 같이 탭으로 구분하여 마스터의 바이너리 로그 포지션이 기록되어 있는데.. 실제 아래와 같은 형태로 저장이 되어 있지요.

CHANGE MASTER TO MASTER_LOG_FILE='mysql-bin.000001', MASTER_LOG_POS=481

아래와 같이 정규식으로 로그 파일과 포지션을 가져와 보겠습니다.

MASTER_LOG_FILE=`sed -r "s/(.*)MASTER_LOG_FILE='(.*)', MASTER_LOG_POS=([0-9]+)/\2/g" xtrabackup_slave_info`
MASTER_LOG_POS=`sed -r "s/(.*)MASTER_LOG_FILE='(.*)', MASTER_LOG_POS=([0-9]+)/\3/g" xtrabackup_slave_info`

여기까지 바이너리 로그 포지션을 가져오는 것은 큰 무리 없이 진행을 하였는데.. 문제는 실제로 리플리케이션을 맺어야하는 서버에 대한 정보를 어디서 가져와야하냐는 것에 있습니다.  물론 이것을 별도의 모니터링 툴에서 추출을 하거나.. 혹은 주기적으로 어딘가에 마스터 정보를 밀어넣거나 할 수 있겠지만.. 제가 택한 방식은 데이터를 땡겨온 타겟 슬레이브 장비에 들어가서 직접 Slave Status를 조회해서, 현재 바라보고 있는 마스터 서버 정보를 가져오는 방식이었습니다.

MASTER_HOST=`mysql -urepl -preplpass -h ${TARGET_HOST} -e 'show slave status\G' | grep 'Master_Host: ' | sed -r 's/\s*(Master_Host: )//g'|sed -r 's/\s*//g'`

한가지 불만은.. Xtrabackup에서 xtrabackup_binlog_info 파일을 생성을 할 때, 왜 슬레이브에서 바라보고 있는 마스터 정보를 기록해주지 않는가..이지만.. 이건 뭐 솔루션이고.. 그들만의 철학이 있다고 생각으로~ 제가 조금 더 귀찮아짐을 택하기로 했습니다.

자.  이제, 마스터 서버도 알아냈고, 리플리케이션 구성을 위한 바이너리 로그 포지션도 정해졌으니.. 아래와 같이 슬레이브를 구성해보도록 해봅시다. 실제 마스터의 호스트가 타겟 호스트가 아닌, 마스터 호스트(앞에서 알아낸) 정도가 변경되었습니다.

echo "change master to
 master_host='${MASTER_HOST}',
 master_user='repl',
 master_password='replpass',
 master_log_file='${MASTER_LOG_FILE}',
 master_log_pos=${MASTER_LOG_POS}; 
 start slave;"\
| mysql -uroot -pxxxxxx

자~ 여기까지 되면.. 아래와 같이 슬레이브로부터 데이터를 받아와서~ 동일한 레벨의 슬레이브 구성 성공입니다.

Restore_From_Slave_Data_Final

Restore_From_Slave_Data_Final

Conclusion

지금까지, 현재 돌고 있는 데이터를 활용하여 슬레이브를 구성하는 방법에 대해서 알아보았습니다. 시작하기 전에 양해의 말씀을 드린 것처럼, 스크립트를 모두 풀어서 보여드릴 수는 없었습니다. 물론 몇백라인정도밖에 안되는 간단하다면 간단한 스크립트이기는 하나.. 이것이 정답일 수 없기 때문이지요. (세트로 묶어서.. 범용적으로 조금 사용할 수준이 되면.. 공개는 그때 해보도록 할께요. ^^)

SCP로 데이터를 땡겨오지 않고, nc 유틸을 통해 받아올 수도 있는 것이고.. 고전적인 Binlog 포지션이 아닌.. GTID를 통해 더욱 심플하게 해결할 수도 있겠죠.

제 방안은 한 사례일 뿐.. 그 이상도 이하도 아닙니다. 그래서 편하게 제가 생각하는 이 데이터 흐름을 다른 세상만사 귀찮은 DBA분들이 더욱 완성도 있는 그림을 그려주면 좋겠다는 바램으로.. 공유하였습니다. 쿨럭~
(근데.. 이것 시스템화 해놓고 보니.. 정말 시간 많이 잡아먹고.. 손 많이 가는 노가다 작업이 줄어서.. 일도 많이 줄어버렸네요~ 크항~!)

긴~글, 늦은밤 이만 마무리 하겠습니다. ^^