MariaDB로 누가 접속 했는지 어떠한 행동을 했는지 확인을 위해서 Audit를 테스트 진행해 보았다.


진행 방식은 다음과 같다.


1. 지속적으로 로그인 성공 및 시간 조회 / 로그인 암호 실패 

2. 지속적으로 Sysbench를 이용하여 10000000 건의 테이블에 대해 select 진행 

3. 이 부분에 대해 cpu / Disk IO / QPS 등을 체크


처음에는 이와 같은 방식으로 지속적으로 체크를 한다.


이후 Audit을 on으로 한 이후에 (로그를 남김) 해당 부하들의 변화률을 체크 하기로 하자.




참고로 Audit 은 아래 블로그를 참고해서 플러그인을 설치하자. 

직접 올려도 되지만 더 자세하게 잘 알려준 블로그가 오히려 좋을 듯 싶다.



1. 지속적으로 로그인 아웃을 하는 스크립트는 파이썬을 이용했다. Thread를 이용하면 좋겠지만...간단하게 개발하기 위해서..내 실력이 안되기에..ㅠ


#-*- coding:utf-8 -*-

import pymysql
import time

def LoginSucess(tp=''):
db = pymysql.connect(host='192.168.0.1', port=int(3306), user='root', passwd='root', db='mysql',
autocommit=True, use_unicode=True, charset="utf8")
if tp.lower() == 'dict':
cursor = db.cursor(pymysql.cursors.DictCursor)
else:
cursor = db.cursor()
query = """
SELECT now();
"""
cursor.execute(query)
row = cursor.fetchone()
print(row)

def LoginFail(tp =''):
try:
db = pymysql.connect(host='192.168.0.1', port=int(3306), user='root', passwd='1234', db='mysql',
autocommit=True, use_unicode=True, charset="utf8")
if tp.lower() == 'dict':
cursor = db.cursor(pymysql.cursors.DictCursor)
else:
cursor = db.cursor()

except Exception as e:
print(str(e))


if __name__ == "__main__" :
while True :
LoginSucess()
LoginFail()
time.sleep(1)


- 여기서는 지속적으로 돌면서 로그인 아웃을 시도한다....mysql close하는 부분이 없네.......OTL


2. Sysbench를 이용해서 다른 서버에서 해당 서버로 스트레스 테스트를 진행하는 것이다.


- 데이터 추가를 먼저 진행하자
sysbench --test=/usr/local/Cellar/sysbench/1.0.6/share/sysbench/tests/include/oltp_legacy/oltp_simple.lua --num-threads=8 \
--mysql-host=db1 --mysql-user=root --mysql-password='root' \
--mysql-table-engine=innodb --db-driver=mysql \
--mysql-engine-trx=yes \
--oltp-table-size=10000000 \
--oltp-test-mode=complex --max-requests=10000 \
--oltp-read-only=off \
prepare

- 데이터 실행 (while문으로 계속 실행)

while true; do
sysbench --test=/usr/local/Cellar/sysbench/1.0.6/share/sysbench/tests/include/oltp_legacy/oltp_simple.lua --num-threads=8 \
--mysql-host=db1 --mysql-user=root --mysql-password='root' \
--mysql-table-engine=innodb --db-driver=mysql \
--mysql-engine-trx=yes \
--oltp-table-size=10000000 \
--oltp-test-mode=complex --max-requests=10000 \
--max-time=4 \
--oltp-read-only=off \
--db-ps-mode=disable \
run
sleep 1

done


주석을 달아 놨듯이.. 처음에는 10,000,000 건을 Insert 이후에 해당 건을 while문이 돌면서 지속적으로 스트레스를 주는 것이다.


왜 while로 했냐면 해당 가상 머신 사양이 좋지 않아 CPU를 100%로 만들지 않으면서도 지속적인 스트레스를 주기 위함이다..

CPU를 100%하면 이후에 어떠한 반응도 체크를 하기 힘들기에 나는 적정선이 70%내를 유지하기 위함이다.


이렇게 한 후 이제 CPU와 QPS등을 체크를 해보자.

체크하는 것은 또한 Python을 이용하였다.



보는 바와 같이 QPS / CPU 등이 일정한 것을 확인할 수 있다...아쉬운 건 Disk I/O를 발생시키지 못한 점이다...


이제는 Audit을 ON으로 한 후 다시 진행 해 보자.


Audit을 제대로 설치 하였다면 아래와 같이 진행할 수 있다.


MariaDB [(none)]> show global status like 'server_audit%';

MariaDB [(none)]> set global server_audit_logging = on;


정상적으로 로그가 남기고 있는지 확인해 보자


따로 경로를 설정하지 않았다면, Data폴더에 가면 확인할 수 있다.

Data폴더에서는 일정 크기의 로그가 쌓이면 자동 백업을 진행하게 된다.

아래 일부를 캡쳐했는데, 유저 / 어디서 들어오는지..쿼리는 어떤것을 남기는지 확인할 수 있다.


여기까지 진행하고 DB / OS 상황을 보자.


별다른 특이 사항은 없어 보인다.


이제는 위와 같은 방법으로 로그인 성공 / 실패 를 하는 스크립트를 실행하고, 추가로 스트레스 테스트도 진행하자.

이렇게 어느정도의 스트레스가 완료 되었다면 둘을 비교해 보자.


비교는 ipython Jupyter를 이용하였다. 추가로 matplotlib을 이용하여 둘의 결과를 그래프로 표현해 보았다.


파란색 선이 Audit 을 Off로 한 후에 진행한 그래프이며, 녹색 선이 Audit 을 On 으로 한 후 진행 하였다.

결과를 보면 큰 차이가 없는 것을 확인할 수 있다.





간단한 결과만 보고 Audit을 On으로 한다고 부하가 발생ㅎㅏ지 않는다고 단정할 수 없다.

8 쓰레드로 진행하였으며(sysbench에서 8쓰레드로 명시했지만 제대로 됬는지는 확인을 못했다..), session도 30 내외로 진행하였기 때문에 더더욱 실제 운영에는 적용할 수 없다.


하지만 간단하게 테스트한 부분이기에 이 부분을 조금 더 보완하여 진행 한다면 분명 좋은 결과를 도출해 낼 수 있을 것이다.

반응형

Prometheus를 설치 했으니 Grafana로 모니터링을 진행해 보자.


사이트 : http://docs.grafana.org/


Grafana 란?


- metric 분석 및 시각화한 제품군으로서 Open source 

- 인프라 및 App 분석 데이터를 시각화로 많이 사용하지만 뿐만 아니라 산업용 센서, 자동화, 날씨, 프로세스 제어 등에서도 사용됨


간단하다.........즉 Prometheus 의 Metric 을 이용하여 해당 정보를 시각화 하여 보여 준다는 것이다.

그 외의 사용에 대해서는 테스트 하지 않아서 모르겠다.




그러면 앞서 Prometheus를 설치 및 데이터 수집ㅇㅣ 되는 것을 확인 하였다.

이제는 그 위에 Grafana를 설치해서 수집된 데이터를 한번 그래프화 해 보자.

Prometheus도 자체의 그래프를 가지고 있지만 심플하고 다양한 Metric을 한 눈에 보기 힘들다는 단점이 있다. 이런 부분을 Grafana가 해결해 주는 것이다.


Grafana 설치 


* Ubuntu / Centos / Mac / Windows 등 다양하게 설치 할 수 있다.

* 아래 사이트를 참조 하자.

http://docs.grafana.org/installation/rpm/

https://www.percona.com/blog/2016/02/29/graphing-mysql-performance-with-prometheus-and-grafana/


나 역시 yum으로 설치를 진행 하였다.

설치 후에는 service grafana-server start로  실행했다.


이제 실행해 보자.

Prometheus와 동일하게 Grafana도 http://설치한서버IP:3000 으로 접속을 할 수 있으며,

초기 비밀번호는 admin / admin 이다.



이후 나는 몇일을 고생했다..어떻게 하는지 몰라서 말이다..ㅠㅠ


먼저 좌측 메뉴를 PIN으로 고정을 하자..

이 후 Data source를 통해 기본 서버를 구성하자.

아래 메뉴는 PIN으로 고정 및 Data Source를 선택 한 화면이며 Add data source 로 추가하자.



Type  은 Prometheus로 하고 url은 prometheus를 설정한 곳을 지정해 주자.


그리고 Direct로 접속 설정 후 save & Test를 하면 다음과 같이 확인할 수 있다.

나같이 성질 급하고 영어 못하고..컴맹은 메뉴얼을 봐도 삽질을 하고 결국은 해결은 하겠지만 몇일 걸릴 것이다.ㅠㅠ



위와 같이 떴다면 성공이다...

다시 Data Sources를 선택하면 다음과 같은 화면이 나올 것이다.



나온 화면에서 Dashboards를 선택해 보자.

이 후 Dashboards 의 New를 선택해서 하나씩 나의 대쉬보드에 원하는 데이터를 추가해 보자



여러 템플릿이 있겠지만 가장 많이 사용하는 Graph를 이용해서 추가해 보자.

Graph를 선택해 보자.



앗!!뭔가 나왔다...하지만 뭔가가 이상하다.

나는 수집도 하지 않은 데이터가 차트로 그려졌다.


정확하게 다시 말하면 내가 node를 설치하기 전의 데이터도 수집 된 것으로 나온다.

뭔가가 이상하다....(처음에는 이게 끝인줄 알았다.....ㅠㅠ)



Panel Title이라는 글씨를 누르게 되면 상단처럼 위에 View - Edit - Duplicat - Share 등이 나온다.

여기서 Edit를 선택해 보자



그래...밑에 Metrics라는 것이 있는데 내용에 fakedata source 라고 나온 것을 확인할 수 있다.


즉. 해당 데ㅇㅣ터는 임시 데이터로써 나의 수집된 데이터와는 무관하다는 뜻이다.



하단에 Panel data source 항목 옆 default를 선택하면 우리가 Data source로 추가한 Test_Source가 나온다. 

Test_Source 를 추가하면 아래와 같이 나온다.



이제 우리가 원하는 Metric 을 추가할 수 있다.

Metric looup 항목에 metric name을 선택하면 여러가지 Metric이 항목으로 ㄴㅏ올 것이다.

여기서 원하는 Metric을 선택해 주자.



정말 환호의 순간이다..원하는 데이터가 나온다.

하지만 여기서 나의 센스가 없는 모습이 나온다.

다른 항목을 선택했더니 데이터가 나오지 않았다...열심히 삽질했다.



정말 센스있는 사람이라면 찾았을 것이다. query의 내용이 겹쳐있다는 것을...

쿼리는 우리가 원하는 데이터를 다시 한번 더 가공할 수 있다. 하지만 여기서는 가공하는 방법을 알려주지 않는다.


원하는 분은 아래를 참고하시기 바랍니다.

Query Edit

http://docs.grafana.org/features/datasources/prometheus/#query-editor



쿼리 내용을 깨끗이 지우고 다시 Metric을 추가하면 원하는 데이터가 나올 것이다.

원하는 데이터가 나왔다면 X 를 누르면 된다.

걱정 안해도 된다 이미 저장 되어 있기 때문이다.



이와 같은 방법으로 2개를 만든 모습ㅇㅣ다.

이렇게 원하는 정보들을 추가하면 한 화면에 여러개의 원하는 정보를 모니터링 할 수 있을 것이다.



정말 화려한 그래픽이다...

그래서 원하는 내용을 빠르게 보거나 아니면 패턴을 빨리 익힐 수 있다.




패턴을 알고 나면 이제는 어떤 부분이 문제가 있는지..또는 배치가 얼마나 자주 도는지..

그에 대한 메모리 사용을 확인하고 수정이 필요한지...

아니면 확장을 할 것인지 많은 해결 방법을 찾을 수 있다.


잠시 몇달동안 일을 하면서 팀장님에게 배운 것 중 가장 기억에 남는것이 있다면 바로 모니터링이다.


모니터링이 누구에게는 따분할 수 있지만..

모니터링을 잘 활용하면 얼마든지 개선할 수 도 있고, 패턴을 익혀 프로그램의 주기를 변경할 수 있는 기회가 생긴다.

프로그램의 주기를 변경하면 효율적으로 자원을 활용할 수 있고 자연스럽게 장애도 예방된다.


또한 패턴을 이용해서 해당 시점의 쿼리를 수집하여 튜닝을 할 수도 있고..

무슨일이 어떻게 돌아가는지 익힐 수 있는 기회가 생긴다.


그러니 모니터링을 하찮게 생각하지 않았으면 한다.

경계에 실패한 자는 용서할 수 없듯이.. 모니터링에 실패하는 자는 훌륭한 DBA가 될수 없다고 혼자 생각해 본다-ㅎㅎ

반응형

'Monitoring > Prometheus' 카테고리의 다른 글

[Prometheus] 프로메테우스 설치 및 개념  (1) 2017.03.17

이직한지 3개월째 되어가고 있지만 아직도 많이 부족하고 내가 모르는 것들이 많다는 것을 계속 느끼고 있다.


정말 이럴때는 자괴감??까지 들 정도이다..ㅎ

그래도 새로운 것을 공부하고 설치해 보고 이에 대해서 팀장님이랑 의논하다 보면 스트레스가 오히려 지식으로 바뀌어 나가고 있다는 것을 느낄 때도 많다.


오늘은 여기까지만 잡설을 하자...ㅋ


Prometheus라는 모니터링 툴이 있으니 한번 검토해 보라는 오더를 받았다.

하지만 이놈의 꽂히는게 어디냐에 따라서 다른 방향으로 가는경우가 있다 보니 이번에도 실컷 Prometheus라고 생각한 것을 Grafana만 해버린 꼴이 되었다.


덕분에 Prometheus 와 Grafana에 대해서 둘다 경험할 수 있는 좋은 기회가 되었다.


내가 조사하고 느낀 것 뿐만 아니라 테스트 한 것에 대해서 공유해 본다.


1. Prometheus란?

원문 : https://prometheus.io/docs/introduction/overview/


아래는 내가 임의로 번역???은 아니고 구글 번역기를 이용해서 정리한 것이다.

그냥 참고만 하셨으면 한다.


- SoundCloud에서 만든 open source 모니터링이며(라이센스 -Apache license V2), 알림 툴킷 (2012년에 개발 되었으며, 많은 회사와 조직에서 사용되고 있으며 독립형 오픈소스 프로젝트)

- 다차원 데이터 모델 (메트릭 이름과 키 / 값 쌍으로 식별되는 시계열)

- 데이터는 key-value 형태의 NoSQL제품인 LevelDB에 저장


- 단일 차원을 활용하는 유연한 쿼리 언어를 사용하여 원하는 데이터를 표현 가능(메트릭 내부 데이터를 이용 )

- 분산 저장 장치에 의존하지 않는다.

- 단일 서버 노드는 자율적입니다. - 각 역활별 노드들이 있으며 해당 노드들을 이용해서 원하는 정보를 확인 가능 

- HTTP를 통한 풀 모델을 통해 시계열 컬렉션이 발생합니다.(웹 브라우저를 통해서도  접근이 가능)

- 푸시 타임 시리즈는 중간 게이트웨이를 통해 지원됩니다.

- 대상은 서비스 검색 또는 정적 구성을 통해 검색됩니다.

- 다양한 그래프 및 대시 보드 지원 모드


적합한 곳
- 순수 시간 기반 모니터링 툴이며 여러 서버에 대해 최고의 역동적인 서비스에 적합한 모니터링 툴
- Prometheus는 신뢰할 수 있게 설계되어 있으며 빠르게 문제를 진단할 수 있음
- 각각의 Prometheus 서버는 독립적이며, 네트워크 저장소나 다른 원격 서비스에 의존적이지 않음

미적합한 곳
- 100% 상세하고 완벽하지 않음(통계성 데이터를 보는 곳)

이렇게 작성해 놓으니 알듯 말듯 하다...그래서 맨 하단에 블로그를 참조하면 더 좋은 정보를 확인할 수 있다.


하아...그래 정의는 그렇다고 치자...그렇다면 작동원리는 어떻게 되는지 알아보자

그전에 아키텍처 그림을 보자. 해당 그림은 Prometheus 홈페이지에서 확인 가능하다.



2. Prometheus 작동원리

- 흔히들 아키텍쳐라고 부름.

- Prometheus 모듈로는 / alertmanager / blackbox_exporter / consul_exporter / graphite_exporter / haproxy_exporter / memcached_exporter / mysqld_exporter / node_exporter / pushgateway / statsd_exporter 로 구성 

- Pull 방식으로 클라이언트들에 접근해서 데이터를 가져오는 형태

- Exporter 가 정보를 수집하여 HTTP end point를 열어서 Prometheus가 데이터를 수집해 갈 수 있도록 함(웹브라우저로 직접 접속하여 해당 정보를 볼 수 도 있음-Metric)


그렇다면 서버나 지수를 추가하려면 어떻게 해야하나..?


- 각 서버에 에이전트 파일을 실행 후 Prometheus 를 재시작(Prometheus 내 prometheus.yml에 정보를 추가 후 재기동)

- 재기동 후에는 제대로 올라왔는지 status의 Targets에서 확인


- 알림기능

- Grouping으로 해서 여러 알람을 하나의 알람으로 받을 수 있음 (여러번 알람을 받는것이 아닌 여러개를 하나의 알람으로 받음)

- Alert Manager 를 통해서 규칙을 설정하면 그 규치에 따라 알람을 보낼 수 있음(병목 현상을 찾기 보다는 해당 규칙을 정하면 그 규칙에 따라 먼저 확인이 가능)




* 하아..이렇게 써도 어렵구나...


간단하게 정리하자면 Prometheus 서버는 각 노드(관리하고자 하는 서버들에 자기가 원하는 모듈들을 설치 후 실행)들에 방문을 해서 각 노드들이 수집한 정보들을 가져오는(Pull) 방식으로 구성되어 있다.

각 노드들에 방문하기 위해서는 기본적으로 Prometheus 서버를 실행할 때 prometheus.yml 파일을 참조하는데...해당 파일에 각 노드들의 정보( IP 등)를 가지고 있어야 한다.


제가 이해하고 정리한 부분이기에 혹시라도 틀렸다면 댓글로 알려 주시면 감사하겠습니다.



흠...이렇게 힘들게 글로 읽고 조금이라도 이해 했다면 이제 진행해 보자.


1. Prometheus 설치 및 구성


다운 받을 위치는 다음과 같다

https://prometheus.io/download/


설치 방법은 아래와 같다

https://prometheus.io/docs/introduction/install/

https://prometheus.io/docs/introduction/getting_started/


사실 설치라고 하기 보다는 압축을 풀고 구성하면 된다.


이제 prometheus.yml 수정해보.


아래 보면 Target에는 모니터링 하고자 하는 서버들의 IP를 설정하고 내가 모니터링 하고자 모듈은 node_export / mysqld_exporter 이다.

그에 대한 포트는 9100 과 9104 이다.

그리고 두대의 모니터링 서버이며...그거에 대한 명은 'linux' , 'linux2' 로 명명 했다. - job_name


이제 prometheus 서버는 구성이 완료 되었다. 실행은 각 노드들(모듈)을 설치하고 실행하자.




2. 모듈 설치 및 구성


나는 node_export 와 mysqld_export를 통해 원하는 데이터를 읽어 오려고 한다.

각 서버에 모듈을 설치해 보자.

아래 사이트에서 원하는 모듈을 다운 후 압축을 풀어보면 파일이 하나만 생길 것이다.

(모듈을 다 설치해 보지 않았으나 내가 원하는 모듈들은 nodex_epxort / mysqld_export 만 했기에..)

모듈 : https://prometheus.io/download/


DB1에는 이미 prometheus 서버를 진행 했기 때문에 혼란을 ㅍㅣ하기 위해 DB2에서 모듈을 설정 및 실행해 보겠다.


먼저 node_exporter 압축 푼 곳을 확인해 보자


별 달리 설정해 줄 것이 없다.

ㅂㅏ로 실행해서 node_exporter가 수집할 수 있도록 하자.


9100포트가 오픈 되어 있으며 이제 수집을 진행할 것이다.


다음으로는 mysqld_exporter를 진행해 보자.



여기서 해야될 업무들이 있다. mysqld_exporter가 수집할 수 있도록 유저 생성 및 .my.cnf를 생성해서 유저 정보를 알려 주는 것이다.

설정 정보들은 아래 사이트를 참고해 주면 된다.


https://github.com/prometheus/mysqld_exporter


하지만 난 유저 생성시 PROCESS, REPLICATION CLIENT, SELECT 해당 권한만 주면 된다고 했는데 막상 실행해 보니 제대로 실행이 되지 않아서

다음과 같은 권한을 부여했다.


MariaDB > CREATE USER 'prom'@'localhost' IDENTIFIED BY 'prom';

MariaDB > CREATE USER 'prom'@'&' IDENTIFIED BY 'prom';

MariaDB > GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, ALTER ,DROP, INDEX ON *.* TO 'prom'@'localhost'

  WITH MAX_USER_CONNECTIONS 3;

MariaDB > GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, ALTER ,DROP, INDEX ON *.* TO 'prom'@'%'

  WITH MAX_USER_CONNECTIONS 3;

MariaDB > Flush privileges;


물론 항상 그런거는 아니었지만 나의 경우 서버에서는 다음과 같이 prom이 디비와 테이블을 생성 한 것을 확인할 수 있었다.


추측은 노드는 상관 없지만 서버에서는 Grafana와 연동 시 데이터를 남기는 작업을 진행하는 것 같아서 그런것 같다.

이거는 어디까지나 추측이기 때문에 아는 분이 계시면 댓글 부탁 드립니다.



유저를 생성 했다면 .my.cnf를 만들어 해당 유저와 패스워드 정보를 작성해 주자.


이것 까지 완료 하였다면 이제 mysqld_export를 실행하자.

이제 이 친구도 수집을 진행할 것이다.


이 작업들을 다른 서버에서도 동일하게 진행 해 주자.


서버들에서 모두 모듈들을 실행 했다면 마지막으로 Prometheus 서버에서 실행하자.


별 문제 없이 실행 되었다면 이제 웹에서 prometheus를 실행해 보자..


3. 운영 및 체크


이제 웹 페이지에서 http://프로메테우스서버IP:9090 으로 접속 해 보자.

혹시라도 접속이 안되거나 한다면 방화벽 포트를 의심해 보자.



첫..화면이다...참 심플하다.

이제 한번 정상적으로 프로메테우스 서버에서 각 서버 모듈들의 정보를 가져오는 지 확인해 보자.


Status > Targets 메뉴를 확인해 보자

정상 적으로 각 서버들에 접속해서 정보들을 가져오는 것을 확인 가능하다.

정상적으로 가져오지 못하고 있다면 State에서 다른 정보들이 보일 것이다.


예를들어서 DB2 ㅇㅔ서 mysqld_exporter 를 내렸을 경우다. Down...보기만해도 가슴이 철렁 거린다...ㅋㅋ

여기서 재미있는 부분이 해당 Endpoint 를 클릭해 보자.


무슨 텍스트들이 엄청나게 많다...

이 뜻은...각 메트릭스( Key) 에 대한 수집된 값(정보-value) 들을 볼 수 있다는 것이다.

즉..각 서버내 모듈들은 이렇게 Key-Value 형태로 정보를 가지고 있다가 Prometheus 서버가 직접 해당 모듈들을 http 로 호출해서 정보를 가져 온다는 것이다.


그렇다면 정의 의 내용들을 조금 이나마 이해 할 수 있을 것이다.


다음은 해당 프로메테우스가 수집한 정보를 보는 방법이다.

Execute 옆 드롭 박스를 클릭하면 메트릭 정보들이 쭉 나와 있을 것이다.

이 정보들을 선택 후 Execute를 하게 되면 수집된 정보들을 보여 줄 것이다.



ㄴㅏ는 go_gc_duration_seconds 라는 메트릭 을 선택후 결과를 보면(Execute 클릭) value가 쭉 나올 것이다.

여기서 다시 그래프를 누르면 그래프로 표현이 될 것이다.




이정도로 간단하게 사용하는 방법에 대해서 정리해 본다.


Grafana 는 다음 포스팅에서 진행해 보겠다.


참고로 이러한 Prometheus 의 좋은 기능 간단한 설치 방법 이 있으나 단 칼에 팀장님에게 잘린 이유는 다음과 같다.

이 내용들은 어디까지나 팀장님의 의견이고..나도 이해한 것이므로 반드시 그렇다!!!는 아니다.

절대적으로 잘 사용하는 곳이나 더 좋은 의견이 있으면 댓글로 남겨 주세요...많은 경험의 결과는 사용하신 분들이 잘 알기에 저는 그 의견들을 듣고 싶습니다.


사용 안하는 이유 

1. Pull 방식의 정보 수집

- Pull 방식은 각 서버에 접속해서 해당 정보들을 수집해 오는 방식이다. 그러다 보니 많은 서버가 추가가 되고 많은 모듈에서 데이터를 수집하며, 네트워크 속도가 좋지 못하다면 수집해 오는 대기 시간이 발생하기 마련이다. 하지만 OLTP 성의 DB의 경우 문제가 발생시 즉각적인 파악이 필요하다. Pull방식으로 수집 시 이런 딜레이 때문에 즉각적인 파악이 힘들 수 있으며 딜레이가 발생할 가능성 때문에  Prometheus를 사용하기 힘들다.


2. 각 모듈의 개별 설치

- 모듈이 필요하거나 서버가 늘어날 수록 해당 서버마다 모듈을 설치하고 설정을 해줘야 한다. 또한 추가 후에는 Prometheus서버를 재시작 해야 한다. 비록 간단할 지라도 이 또한 서버가 늘어 난다면 문제가 될 수 있으며 각각의 모듈의 최신화가 필요 하다면 이 또한 추가 작업으로 진행이 되어야 한다. 이러한 작업이 거절되는 이유 중 하나이다.


Prometheus는 어떠한 서버에서도 사용이 가능하다고 생각한다. 모듈에서 모든 정보??를 수집 하기 때문이다. 다양한 정보를 수집하고 볼 수 있는 장점이 있으나 앞서 생각해 봐야하는 2가지 이유로 적합하지 못하다는 팀장님과 나의 의견이었다.


중.소형 서버에서 간단하게 확인하기 위해서는 정말 좋고 강력하다. 하지만 2가지 문제점이 해결되지 못하다면 대규모에서는 사용하기 힘들지 않을까 조심스럽게 생각해 본다.


이 의견에 대해서도 반박??은 무서워요..ㅠㅠ의견을 주실 수 있다면 의견 주세요. 많은 분들에게 도움이 될 것 입니다. 또한 저에게도요!!!


그 외의 Alert 등의 자세한 내용 및 추가적으로 더 좋은 정보는 아래 블로그 참조하세요!!

정말 소개가 잘 나와 있어서 꼭 확인해 보세요!!


참고 : 

https://blog.outsider.ne.kr/1254

http://opennaru.tistory.com/126


반응형

Galera Cluster에서 auto increment  컬럼에 따로 값을 지정하지 않으면 해당 노드들끼리 중복이 생겨 정합성이 깨지는 경우가 생길 수 있다.

그래서 옵션에서 중복허용되지 않게 하기 위해 증가되는 값을 설정함으로써 중복을 방지한다.


예를들어 2개의 노드라고 한다면 1번 노드에서는 1,3,5,7,9로 증가하고 2번 노드에서는 2,4,6,8,10 이런식으로 증가한다.


해당 부분을 테스트하여 공유 및 이에 대한 자세한 내용도 첨부해 본다.


* 해당 서버는 노드수가 5개라 현재 5로 되어 있는 것을 확인할 수 있다


- 1번 노드에서  Insert 진행



- 3번 노드에서 insert 진행


노란색을 보면 28까지는 5씩 증가 했지만 a3 에서 넣는 순간 부터는 29에서 진행 후 다시 5씩 증가하는 것을 확인 가능하다.

 


좀더 자세한 내용은 아래 확인하면 될 듯 싶다.





[출처] http://h391106.tistory.com/323


1.    
MariaDB Galera Cluster Variables

wsrep_auto_increment_control

wsrep_causal_reads

wsrep_certify_nonPK

wsrep_cluster_address

wsrep_cluster_name

wsrep_convert_LOCK_to_trx

 

이 문서는 위 Variables 에 대해 다루고 있다.


테스트에 사용한 MariaDB  5.5.39 이며, wsrep_provider_version 은 내장되어 있던 25.3.5(r178) 를 사용하였다.

문서에서 언급 한 각 Variables  default, Introduced, deprecated 의 정보는 8.A 를 확인한 내용이며이는 wsrep API patch v0.8 를 기준으로 한다.

 

2.    wsrep_auto_increment_control

 

Default = on

Introduced = 1

 

Cluster membership 에 변화가 있을 때

auto_increment_increment  auto_increment_offset system variables 을 자동으로 조정한다.

 

Cluster Membership 의 변화란 클러스터를 구성하는 노드에 변화가 있어 클러스터 사이즈가 커지거나 작아지는 경우를 말한다.

 

계획 된 작업으로 신규 노드를 추가하는 일도장애가 발생해 노드가 클러스터 구성에서 빠지는 일도 Cluster Membership 의 변화이다.

 

wsrep_auto_increment_control = on 사용으로 INSERT 구문으로 인한 충돌을 줄여준다고 한다.

 

여기서 말하는 충돌은 MariaDB  Duplicate entry 에러를 의미하며, pk  auto_increment 를 사용하는 경우를 말한다.

 

auto_increment_increment

 

Default = (wsrep_cluster_size)

 

auto_increment_offset

 

Default = (Cluster 를 구성하는 노드 1, 2, 3, … 순차적으로 값을 가져간다.)

 

위의 두 파라미터는 Galera cluster 이전에도 존재하던 Variables 이다.

특히 매뉴얼을 보면 MASTER  TO MASTER Replication (DUAL MASTER) 셋팅에서 사용한다고 한다.

 

3 node Galera Cluster 를 구성하면 다음과 같이 System Variable 값을 가져간다. ( wsrep_auto_increment_control = on )

 

전 노드 공통으로 auto_increment_increment = 3

각 노드는 auto_increment_offset = [1|2|3]

 

auto_increment_offset 의 값이 auto_increment_increment 의 값보다 큰 경우 auto_increment_offset 의 값은 무시되며,

이외의 경우 auto_increment 의 기대되는 값은 다음의 공식을 따른다.

 

(현재의 auto_increment 보다 큰 가장 작은 값 ) = auto_increment_offset + N * auto_increment_increment

 

wsrep_auto_increment_control = on 을 사용하는 3 NODE CLUSTER 에서 A 라는 테이블이 AUTO_INCREMENT 를 사용하는 B 라는 컬럼을 가진다고 가정하자.

테이블의 B 컬럼의 AUTO_INCREMENT 로 할당 된 가장 큰 값이 20 이라고 할 때 기대되는 다음 AUTO_INCREMENT 는 다음과 같다.

 

auto_increment_increment = 3

auto_increment_offset = 1

ð  22

 

auto_increment_increment = 3

auto_increment_offset = 2

ð  23

 

auto_increment_increment = 3

auto_increment_offset = 3

ð  24

 

결과적으로 auto_increment_offset = on 의 사용으로 AUTO_INCREMENT  PKEY 로 사용하는 경우에 대해

INSERT  DUPLICATE ENTRY 에러를 줄일 수 있다.

 

3.    wsrep_causal_reads

 

Default = off

Introduced = 1

Deprecated = 3.6

 

이 기능은 3.6 에서 deprecated 되었으며, wsrep_sync_wait 로 대체되었다.

 

wsrep_sync_wait

 

Introduced = 3.6

 

아래 값을 지정하면 해당하는 오퍼레이션을 수행하기 전에 동기화 된 읽기 view 를 보장한다.

 

 SELECT, SHOW, BEGIN / START TRANSACTION 을 포함한 READ Statement 를 검사

 UPDATE, DELETE statements 를 검사

 1, 2 를 포함한다.

 INSERT, REPLACE statement 를 검사

 

4.    wsrep_certify_nonPK

 

Default = on

Introduced = 1

 

wsrep_certify_nonPK = on 인 경우 PK 없는 테이블에 대해 DML 을 허용한다.

 

(내부적으로 pk 를 생성하는 듯 함 – 확인할 것…)

 

wsrep_certify_nonPK = off 인 경우 PK 없는 테이블에 DML 을 사용하는 경우 다음과 같은 에러가 발생한다.

 

ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction

 

5.    wsrep_cluster_address

 

Cluster Membership 을 구성하는데 사용되는 System Variable 이다.

 

클러스터의 최초 노드를 구성하는 경우 gcomm:// 셋팅으로 startup 한다.

이 의미는 다른 클러스터 노드에 접속하지 않는다란 뜻이다.

 

이후 해당 vriable 을 클러스터를 구성하는 전 노드로 업데이트 한다.

 

A, B, C 라는 3개의 Address 가 있다고 하면 gcomm:// 셋팅으로 인스턴스를 시작한 이후,

gcomm://A,B,C 로 변경한다.

 

사실 위 과정 없이도 Joiner 노드의 올바른 wsrep_cluster_name 구성과 wsrep_cluster_address 구성이 있다면

startup 하는데 문제가 없다.

 

6.    wsrep_cluster_name

 

Default = my_wsrep_cluster

Introduced = 1

 

Logical  Cluster name 을 지정한다.

Cluster name 이 다르면 Cluster Membership 참가가 실패한다.

 

이를 테면 Donor node 에서는 다음과 같은 에러가 발생하다 Joiner node 프로세스가 죽는다.

 

[Note] WSREP: handshake failed, my group: 'mariatest', peer group: 'my_wsrep_cluster'

 

 Variable 셋팅으로 하나의 장비에 여러 서비스 목적의 Cluster 를 구성하는 경우나,

wsrep_cluster_address 를 실수로 잘못 지정한 경우에 데이터를 보호할 수 있다.

 

7.    wsrep_convert_LOCK_to_trx

 

Default = off

Introduced = 1

 

LOCK / UNLOCK TABLES  BEGIN / COMMIT statements 로 바꿔준다.

암묵적으로 locking sessions  transactions 을 사용하도록 변환한다.

 

이 옵션이 off 인 상태에서는

 

NODE #1 – lock table test [ read | write ]

NODE #1  Insert : Waiting

NODE #2  No Waiting

NODE #3  No Waiting

 

on 인 경우에는 같은 테스트에서 NODE #1 에서의 Insert : Waiting 이 없다.

 

8.    REFERENCE

A.     wsrep 파라미터
http://galeracluster.com/documentation-webpages/mysqlwsrepoptions.html

B.     mariadb 파라미터
https://mariadb.com/kb/en/mariadb/documentation/optimization-and-tuning/system-variables/server-system-variables/          

C.     wsrep_auto_increment_control
http://www.percona.com/blog/2011/01/12/conflict-avoidance-with-auto_increment_incremen-and-auto_increment_offset/

D.     16.1.2.2 Replication Master Options and Variables
http://dev.mysql.com/doc/refman/5.0/en/replication-options-master.html

E.     Bootstrapping the cluster
http://www.percona.com/doc/percona-xtradb-cluster/5.5/manual/bootstrap.html

반응형

개발은 어렵지는 않지만 시간이 오래 걸린다.

특히나 많은 검증을 위해 테스트를 해야되고 이에 대해 오류가 생기거나, 다양한 경우에 대한 대비가 필요하다..


아래 스크립트는 Replication 도중에 Sync가 안맞아 잘못된 데이터나 데이터가 없는 경우에 대해서 확인하여 추가하여 준다.

(오히려 Slave에서 데이가 있으며...Master에서 없는 경우는 해당 사항이 없다.....ㅠ)


로직은 간단하다.


1. file(ini파일로 지정) 를 읽어 해당 파일 내의 테이블 명을 읽는다.(ini 파일 작성 형ㅌㅐ : 테이블1 (줄바꿈) 테이블2 (줄바꿈) ...으로 구분한다.) 

2. 테이블 정보를 가지고  Master에 접속하여 해당 테이블에 대한 PK를 확인 및 테이블 컬럼들을 받아 온다.

3. 다시 해당 테이블 정보를 가지고 full scan한다.

4. Slave에 접속하여 마스터에 대한 테이블 정보( PK)를 가지고 데이터 여부를 확인한다.

5. 해당 데이터가 있으면 Update를 없으면 Insert를 한다.


스크립트 내용은 아래와 같다...스크립트 개발 시 참고하시기를 ....ㅠ

(해당 스크립트에 대한 검증없이 적용 하셨다가 사고가 나도 책임 못집니다........ㅠㅠ 검증의 검증을 하시기를...)

 




#coding: utf-8


import pymysql
import os
import time

now = time.localtime()
todate = "%04d%02d%02d %02d:%02d:%02d" % (now.tm_year, now.tm_mon, now.tm_mday, now.tm_hour, now.tm_min, now.tm_sec)

print "======================================================="
print " Start Time is %s " % todate
print "======================================================="

##### Use Korean Character #######
import sys
reload(sys)
sys.setdefaultencoding('utf-8')
##################################
# Master Info
srv_Master = '마스터IP'

# DBName
srv_DBName = '디비명!!'

# slave1 / slave2 / ...
lst_srv = ['슬레이브IP1','슬레이브IP2',....]
#lst_table = [] -> file 에서 읽어서 사용
#File 형태는 확장자가 ini 이며, 내부에는 한 라인당 하나의 테이블명으로 지정
_filepath = 'INI파일이 저장되어 있는 위치(경로) /home/path...'

def fn_ConnectDB(srv, strDBName) :

db = pymysql.connect(host=srv, port=3306, user='접속유저명', passwd='접속패스워드', db='접속디비명', autocommit=True, use_unicode=True, charset="utf8")

dbCursor = db.cursor()

return dbCursor


def fn_TruncateTable(tmpTable, strDBName) :
for srv in lst_srv: # 2servers
##check Data in Slave DB
curSlave = fn_ConnectDB(srv, srv_DBName)

sqlTruncate = "TRUNCATE TABLE %s.%s" % (strDBName, tmpTable)

print "Nothing Key in table : %s.%s" % (strDBName, tmpTable)
print sqlTruncate

#check curSlave.execute(sqlTruncate)
curSlave.close()


def fn_GetTableInfo(curMaster, strDBName, tmpTable) :
#PK확인
sqlPK = "select COLUMN_NAME, TABLE_NAME, TABLE_SCHEMA " \
" from INFORMATION_SCHEMA.COLUMNS " \
"where " \
" TABLE_NAME='%s' " \
" and TABLE_SCHEMA='%s' " \
" and COLUMN_KEY = 'PRI'" % (tmpTable, strDBName)
curMaster.execute(sqlPK)
#pkResult = curMaster.fetchall()
pkResult = curMaster.fetchall()

#if pkResult == None :
if curMaster.rowcount == 0 :
print 'Nothing Key!!!'
#truncate 진행 -> Key가 없을 경우 truncate 진행
fn_TruncateTable(tmpTable, strDBName)

# 아래처럼 group concat 하면 PK가 무조건 앞에 위치하게
sqlColumns = "SELECT GROUP_CONCAT(COLUMN_NAME) " \
"FROM INFORMATION_SCHEMA.COLUMNS " \
"WHERE " \
" TABLE_NAME = '%s' " \
" AND TABLE_SCHEMA='%s' " % (tmpTable, strDBName)
curMaster.execute(sqlColumns)
# pkResult = curMaster.fetchall()
colResult = curMaster.fetchall()

return(pkResult, colResult)

def fn_WorkingData(tmpMasterData, tmpTable, tmpInfo) :
for srv in lst_srv: # 2servers
##check Data in Slave DB
curSlave = fn_ConnectDB(srv, srv_DBName)
print "==================================================================================="
for tmpData in tmpMasterData :
tmpPK = ''
#curMaster[0]은 무조건 PK값임 / tmpInfo[0] - pk / tmpInfo[1] - all columns
if len(tmpInfo[0]) > 1 :
cntPk = 0
intcolPK = 0
#for colPK in tmpInfo[0][cntPk][0] :
for intcolPK in range(0,len(tmpInfo[0])) :
colPK = tmpInfo[0][intcolPK][0]
if cntPk <> (len(tmpInfo[0])-1) :
tmpPK += colPK + """= \"""" + str(tmpData[cntPk]) + """\" AND """
else :
tmpPK += colPK + """= \"""" + str(tmpData[cntPk]) + """\" """
cntPk += 1
print "Server PK Info : %s" % tmpPK
elif len(tmpInfo[0]) == 1 :
tmpPK = str(tmpInfo[0][0][0]) + """= \"""" + str(tmpData[0]) + """\" """
else :
#PK 가 없을경우 무조건 insert로 취급
tmpPK = " 1=0 "

sqlCheckData = "SELECT 1 FROM %s.%s " \
"WHERE " \
" %s " % (srv_DBName, tmpTable, tmpPK)
curSlave.execute(sqlCheckData)

tmpCheckCnt = curSlave.rowcount

#tmpColName 은 Insert에서 사용하기 위함 ColName 은 Update에서 사용하기 위함
tmpColName = tmpInfo[1][0]
ColName = tmpColName[0].split(',')

if tmpCheckCnt == 0 :
# 데이터 생성을 위한 초기화 작업
j = 0
strData = ""
for i in tmpData:
if j == 0:
strData = """(\"""" + str(i) + """\","""
elif j == (len(tmpData) - 1):
strData += """\"""" + str(i) + """\")"""
else:
strData += """\"""" + str(i) + """\","""
j += 1

#값이 없으니 Insert 진행
sqlInsertSlave = """INSERT INTO %s.%s (%s) VALUES %s""" % (srv_DBName, tmpTable, tmpColName[0], strData)

print ("Insert Query : %s") % sqlInsertSlave

#check 필요 curSlave.execute(sqlInsertSlave)

else :
#값이 있기 때문에 Update 진행
strData = ""
#ColName = tmpInfo[1].split(',')
for i in range(0,len(tmpData)) :
if i == (len(tmpData)-1):
strData += str(ColName[i]) + """ = \"""" + str(tmpData[i]) +"""\""""
else:
strData += str(ColName[i]) + """ = \"""" + str(tmpData[i]) + """\","""

sqlUpdateSlave = """UPDATE %s.%s SET %s WHERE %s""" % (srv_DBName, tmpTable, strData, tmpPK)

print ("Update Query : %s") % sqlUpdateSlave

#Check 필요 curSlave.execute(sqlUpdateSlave)
print "==================================================================================="
def fn_main(curMaster, tmpTable) :

# for tmpTable in lst_table : #60 tables
#tmpInfo[0] = pk column, tmpInfo[1] = All Column(col1,col2...)

tmpInfo = fn_GetTableInfo(curMaster, srv_DBName, tmpTable)

int_Start = 0

#for tCol in range(0, len(tmpInfo[1][0]))

tmpAllCol = tmpInfo[1][0]
#print tmpAllCol -> (u'CODEID,CGID,CG_CID,CODENAME,CODEMEMO,SORTS,USEYN,RDATE',) 이렇게 표현되어 tmpAllCol[0]으로 변환
while True :
#만건씩 잘라서 진행
sqlMasterTable = "SELECT %s " \
"FROM %s.%s " \
"limit %d, 10000" % (tmpAllCol[0], srv_DBName, tmpTable, int_Start)
print ("Limit Query : %s" % sqlMasterTable)

curMaster.execute(sqlMasterTable)

int_Start += 10000
tmpMasterData = curMaster.fetchall()

fn_WorkingData(tmpMasterData, tmpTable, tmpInfo)

if curMaster.rowcount < 10000 :
total_count = int_Start+curMaster.rowcount
print "Last data in Table %s (last select count %d/%d)" % (tmpTable, curMaster.rowcount, total_count)
print "==================================================================================="
break

def fn_read_log(_filename) :
f = open(_filename, 'r')
t_line = f.readlines()
f.close()

f_line = []
#중간에 \n 에 대해서 삭제 후에 진행
for i in t_line:
f_line.append(i.strip())

return f_line

if __name__ == "__main__":

curMaster = fn_ConnectDB(srv_Master, srv_DBName)

filenames = os.listdir(_filepath)
for filename in filenames :
full_filename = os.path.join(_filepath, filename)
ext = os.path.splitext(full_filename)[-1]

if ext == '.ini':
print "Talbes File : " + full_filename
lst_table = fn_read_log(full_filename)
print "Table List : " + str(lst_table)

for aTable in lst_table :
print "Table Name : " + aTable

if aTable <> '' :
fn_main(curMaster, aTable)

now = time.localtime()
finishtime = "%04d%02d%02d %02d:%02d:%02d" % (now.tm_year, now.tm_mon, now.tm_mday, now.tm_hour, now.tm_min, now.tm_sec)

print "======================================================="
print " Start Time is %s " % todate
print " End Time is %s " % finishtime

print "======================================================="


반응형


설명을 제대로 못하는 지식은 내가 제대로 이해하지 못했거나 모르는 지식이다.



1. INNER JOIN
  - 두 테이블간의 조인 조건을 만족하는 ROW만 리턴
  - 일반적인 조인 으로 이해하면 됨
  - 아래 코딩 내용을 보게 되면 이해가 쉽게 될 것 같다.
FOR ( record1 IN TABLE1) { // 드라이빙 테이블 (join을 주도하는 테이블)
  FOR (reocrd2 IN TABLE2) { // 드리븐 테이블 (join에서 끌려가는 테이블)
  IF ( record1.join_column == record2.join_column) {
  join_record_found(record1.*, record2.*);
  } else {
  join_record_notfound( ); //outer join과 달리 만약에 매칭되는게 없다면 더이상 찾지 않는다
  }
  }
}

 
2. OUTER JOIN
  - LEFT/RIGHT/FULL 형태의 OUTER JOIN 이 있음
  - LEFT OUTER JOIN의 경우 조인문 왼쪽에 있는 테이블의 모든 결과를 가져온 후 오른쪽 테이블의 데이터를 매칭하며, 매칭되는 데이터가 없는 경우 NULL 매칭
  - RIGHT OUTER JOIN은 LEFT 조인의 반대  - FULL OUTER JOIN은 일반적으로 사용할 일이 없으며, DB에 따라 지원하지 않음

FOR (record1 IN TABLE1) {
FOr (record2 IN TABLE2) {
IF ( record1.join_column == record2.join_column) {
join_record_found(record1.*, record2.*);
} else {
join_record_found(record1.*, NULL);// Inner Join과 다른 부분. 매칭이 안된다고 하더라도 드라이빙 테이블 데이터(매칭안된 데이터) 와 null로 같이 매칭
}
}
}

출처: http://dimdim.tistory.com/entry/SQL-JOIN-정리-Inner-Join-Outer-Join [딤딤이의 블로그]

출처 : Real Maria DB [위키북스]

반응형

마리아 DB에서 Galera Cluster를 설치하면서 개념이 필요해서 검색해서 공유해 본다.

추가적으로 정리가 되면 MariaDB에서 Galera Cluster 설치 하는 부분을 공유할 예정이다.


[원본] http://bcho.tistory.com/1062


MySQL Galera Replication


조대협 (http://bcho.tistory.com)


RDBMS 오픈소스 중에서 단연 가장 많이 사용되는 것은 MySQL인데, 근래에 웹 스케일이 커지면서, 단일 인스턴스로만 서비스가 불가능한 용량까지 가게 되서, 이 MySQL에 대한 클러스터링 스케일링에 대한 이슈가 많아졌다. 이에 Tungsten, MySQL Replication, NDB, Galera 등 다양한 클러스터링 방법이 있는데, 그중에서 갈레라 클러스터링 (Galera Clustering)에 대해서 간단하게 정리하고자 한다.


MySQL Replication


갈레라 클러스터링을 이해하기에 앞서서 먼저 가장 널리(그리고 쉽게) 사용되는 MySQL Replication 방식에 대해서 알아보자. MySQL Replication 방식은 Master/Slave 방식의 구성이 일반적이며, 이 구성의 경우 특정 노드는 쓰기를 담당하고 나머지 노드는 읽기를 담당하는 형태로 구성이 된다.

통상적으로 데이타 베이스 트랜젝션의 60~80%가 읽기 트렌젝션이기 때문에, 이러한 구조를 사용하더라도 충분히 성능의 향상을 기대할 수 있다.


다음 그림은 MySQL Replication 의 간단한 구조도 이다.

 




먼저 좌측의 Master Node에 쓰기 트렌젝션이 수행되면 Master node는 데이타를 저장하고, 트렌젝션에 대한 로그를 내부적으로 BIN LOG라는 파일에 저장한다. (시간 순서대로 수행한 업데이트 트렌젝션이 기록되어 있다.)


Slave Node에서는 이 BIN LOG를 복사해온다. 이 복사 작업을 IO Thread라는 스레드가 수행하는데, 이렇게 읽어온 내용은 Replay Log라는 파일에 기록이 된다. 이렇게 기록된 내용은 SQL Thread라는 스레드가 읽어서, 하나씩 수행을 해서  MySQL 데이타 파일에 기록을 한다.


쉽게 설명하면, insert 쿼리를 master node에서 실행했으면 그 쿼리가 master node의 bin log에 기록이 되고, 이 내용은 slave node에 복사가 된후에, slave node에서 같은 쿼리가 수행이 되서 복제가 반영되는 방식이다.


방식이 단순해서 신뢰도가 높은 반면, 단점으로는

  • 읽기와 쓰기 노드를 분리해야 하며,
  • 데이타 복제가 동기 방식이 아닌 비동기 방식으로 적용된다. 바꿔서 말하면, master node에 적용한 데이타 변경사항이 slave에 반영될때까지 일정 시간이 걸린다는 것으로, master와 slave node간의 순간적인 데이타 불일치성이 발생할 수 있다는 것이다.


Galera cluster


Galera cluster는 http://galeracluster.com/ 에서 제공되는 오픈소스로, 동기방식의 복제 구조를 사용하고 있다.

간단하게 구조를 살펴보자 아래 그림을 보면 




각각의 노드가 있을때, 아무 노드에나 쓰기나 업데이트가 발생하면, 모든 노드에 데이타를 복사를 완료하고 나서, 업데이트 내용이 파일에 저장된다. 아키텍쳐상의 구조를 보면, 위의 그림과 같이 각 MySQL 노드에는 WSREP 라는 모듈이 있다. 이 모듈은 데이타베이스에 복제를 위한 범용 모듈로 여기에 마치 드라이버처럼 Galera replication module을 연결해주면 데이타 변경이 있을때 마다, 이 Garela replication module이 다른 mysql node로 데이타를 복제한다.


약간 더 구체적인 구조를 살펴보면 노드간의 데이타 복제는 다음과 같은 흐름을 따르게 된다. 





노드에 트랜젝션이 발생하고 COMMIT이 실행이되면, 디스크에 내용을 쓰기 전에 다른 노드로 복제를 요청하고 다른 노드에 복제 요청이 접수되었을때, 해당 노드의 디스크에 실제로 데이타를 쓰게 된다.


이러한 특성으로, 전체 노드에 데이타가 항상 일관성있게 저장되고, 모든 노드가 마스터 노드로 작동을 하며, 특정 노드가 장애가 나더라도 서비스에 크게 문제가 없다. 

(MySQL Replication의 경우 마스터 노드가 장애가 나면 슬레이브 노드중 하나를 마스터로 승격을 해야하는 등 다소 운영 프로세스가 갈레라에 비해서는 복잡하다.)


상당히 좋아 보이는 구조이기는 한데, 반대로 가지는 단점도 만만하지 않다.


성능

먼저 성능적인 부분에서, 데이타를 디스크에 저장하기 전에, 다른 모든 노드에 데이타 복제 요청을 해야 하기 때문에, 비동기 방식의 MySQL Replication에 비해서, 쓰기 성능이 떨어지는 것으로 보인다.


장애 전파

이렇게 다른 노드에 복제 요청을 하는 클러스터 구조의 경우, 장애를 다른 노드로 전파 시킬 가능성이 높은데, 예전에 대표적인 웹 애플리케이션 서버인 웹로직의 경우 유사한 세션 클러스터링 구조를 사용했다. 

이 경우 복제를 요청했을때 복제 요청을 받은 노드가 장애 상황 특히 느려지거나 일시적으로 멈췄으때, 복제를 요청한 노드가 응답을 받지 못하고 대기하게 되고, 이 대기한 노드에 다른 노드가 복제를 또 요청하면, 같은 이유로 복제가 지연 되면서 클러스터를 타고 장애가 전파되는 현상을 야기하게 된다.

그래서 갈레라 클러스터의 경우 LOCK문제가 생기거나 슬로우 쿼리들이 많이 발생할때 장애를 전파시킬 수 있는 잠재적인 문제를 가지고 있다.


스케일링의 한계

갈레라가 모든 노드에 데이타를 복제하고 트렌젝션을 끝내는 만큼, 전체적인 노드수가 많아지게 되면, 복제를 하는데 그만큼 시간이 많이 걸림에 따라, 하나의 클러스터에서 유지할 수 있는 노드의 수가 한계가 있어져서, 횡적 스케일링의 한계가 올 수 있다. 


이런 단점에도 불구하고, 모든 노드에 읽기 쓰기가 가능한 멀티 마스터 구조와 모든 노드의 데이타를 일관적으로 유지 시켜준다는 장점과 쉬운 설정 방법으로 인하여 MySQL 클러스터를 구성한다면 한번쯤 검토해봐야 하는 솔루션이 아닌가 한다.


(아쉽게도 국내 사례는 그다지 많지 않은듯...)


몇가지 참고 사항

  • 갈레라 클러스터는 서로 다른 MySQL 버전간에도 클러스터로 묶을 수 있다.
  • 갈레라 클러스터에서 노드가 떨어졌다가 붙으면 일정 부분은 GTID (Global Transaction ID)를 이용하여, 데이타가 복제 되지 않은 델타 부분만 복제가 가능하지만, 시차가 오래되 버리면 풀 백업본을 가져다 엎어야 한다. (풀백업은 복구는 시간이 많이 걸림)


반응형

[출처] http://metalbear.egloos.com/tag/mysql/page/1




myisamchk 사용하기


MySQL 서버를 사용하다보면 예기치 못한 문제가 발생하여 데이터가 손상되는 경우가 생길 수 있다. 만약 테이블 타입이 InnoDB 라면 트랜잭션이 적용되기 때문에 그러한 경우 자동으로 데이터를 복구하지만 테이블 타입이 MyISAM 이라면 데이터가 손상될 확률이 좀 더 높다. 그런 경우 myisamchk 유틸리티를 사용하면 손상된 데이터를 수동으로나마 복구할 수 있다. 테이블을 체크하고 복구하는 SQL 로 CHECK TABLE 과 REPAIR TABLE 이 있으나 myisamchk 에 비해 속도는 느리다. ( 또한 테이블이 날아갈 확률도 높다. )


 


myisamchk 유틸리티는 데이터 파일의 상태를 체크하거나 손상된 데이터 파일을 복구하는 데 사용되는 유틸리티이다. 테이블을 안전하게 유지, 보수하고 싶다면 주기적으로 myisamchk 를 실행하여 상태를 검사해보는 것이 좋을 것이다.

 


myisamchk 유틸리티를 이용하여 데이터 파일의 상태를 보거나 체크할 때는 상관없지만 데이터 파일을 복구하려 할 때는 반드시 MySQL 서버를 종료한 상태에서 실행해야 한다.


만약 MySQL 서버를 종요할 수 없는 피치못할 상황이라면 테이블에 Lock 를 건 다음 myisamchk 유틸리티를 실행하도록 한다.


 


myisamchk 유틸리티의 사용 형식은 다음과 같다.


 


myisamchk [옵션] <테이블명>


 


이 때 <테이블명> 에는 테이블명만 써서 해당 테이블을 검사할 수도 있고, *.MYI 라고 패턴문자를 사용하여 해당 디렉토리 내의 모든 테이블을 검사할 수도 있다.


 


다음은 myisamchk 를 이용하는 예제인데 orders 테이블의 정보를 출력하는 모습을 보여주고 있다.


 


C:\windows\system32>


myisamchk -d c:\APM_Setup\Server\MySQL\data\shopsample\orders


 


MyISAM file:         c:\APM_Setup\Server\MySQL\data\shopsample\orders

Record format:       Fixed length

Character set:       latin1 (8)

Data records:                    8  Deleted blocks:                 0

Recordlength:                   17


table description:

Key Start Len Index   Type

1   2     4   unique  long


 


myisamchk 유틸리티에서 사용할 수 있는 옵션은 크게 Global 옵션, 체크 옵션, 복구 옵션 그리고 그 밖의 옵션으로 나눌 수 있다. Global 옵션은 유틸리티에서 전반적으로 사용되는 일반적인 옵션이고, 체크 옵션은 테이블의 상태를 체크하는데 사용하는 옵션이며, 복구 옵션은 테이블을 복구할 때 사용하는 옵션이다. 먼저 Global 옵션은 다음과 같다.


 


+--------------------+-----------------------------------------------------------------+


| Global 옵션             | 설명                                                                                           |


+--------------------+-----------------------------------------------------------------+


| -#, --debug=...        | Debug 로그를 출력한다.


| -?, --help               | 도움말을 출력한다.


| -t, --tmpdir=<경로>  | 임시 파일의 경로를 설정한다.


| -s, --silent             | 침묵 모드로 작동한다. 검사도중 에러가 발생했을 때만 출력한다.


| -v, --verbose         | 상세 정보를 출력한다.


|-V, --version           | 버전을 출력한다.


| -w, --wait              | 테이블에 Lock 이 걸려있으면 기다린다.


+--------------------+-----------------------------------------------------------------+


 


다음은 -d 와 -v 옵션을 이용하여 테이블의 정보를 상세하게 출력한 모습이다.


 


C:\windows\system32>


myisamchk -d -v c:\APM_Setup\Server\MySQL\data\shopsample\orders


 


MyISAM file:         c:\APM_Setup\Server\MySQL\data\shopsample\orders

Record format:       Fixed length

Character set:       latin1 (8)

File-version:        1

Creation time:       2006-10-20 23:51:37

Status:              changed

Auto increment key:              1  Last value:                    17

Data records:                    8  Deleted blocks:                 0

Datafile parts:                  8  Deleted data:                   0

Datafile pointer (bytes):        4  Keyfile pointer (bytes):        3

Datafile length:               136  Keyfile length:              2048

Max datafile length:   73014444030  Max keyfile length:   17179868159

Recordlength:                   17


table description:

Key Start Len Index   Type                     Rec/key         Root  Blocksize

1   2     4   unique  long                           1         1024       1024


- 체크 옵션은 다음과 같다.


 


+---------------------------+----------------------------------------------------------+


| 체크 옵션                           | 설명                                                                                |


+---------------------------+----------------------------------------------------------+


| -c, --check                       | 테이블에 오류가 없는지 체크한다.


| -e, --extend-check             | 테이블에 오류가 없는지 좀 더 세밀하게 검사한다.


|                                         | 오류를 수정했음에도 불구하고 계속해서 오류가 발생할때


|                                         |  이 옵션을 부여한다. 대신 체크 속도는 느리다.


| -F, --fast                           | 테이블이 제대로 닫혀있는지 체크한다.


| -C, --check-only-changed  | 마지막으로 체크한 이후로 변경된 테이블만 체크한다.


| -f, --force                          | 체크중 에러가 발견되면 에러 테이블을 표시해놓고


|                                         | 바로 복구를 시작한다.


| -i, --information                  | 체크된 테이블의 정보를 출력한다.


| -m, --medium-check           | --extend-check 체크보다 빠르게 체크한다.


|                                         | 일반적으로 많이 사용한다.


| -U, --update-state              | 에러가 발견된 테이블을 표시해둔다.


| -T, --read-only                  | 에러가 발견되어도 테이블에 표시해두지 않는다.


+---------------------------+----------------------------------------------------------+


 


다음은 -eis 옵션을 주고 테이블을 체크한 모습이다.


 


C:\windows\system32>


myisamchk -eis -v c:\APM_Setup\Server\MySQL\data\shopsample\orders


Checking MyISAM file: c:\APM_Setup\Server\MySQL\data\shopsample\orders

No recordlinks

block_size 1024:

Key:  1:  Keyblocks used:   6%  Packed:    0%  Max levels:  1

Total:    Keyblocks used:   6%  Packed:    0%


Records:                 8    M.recordlength:       17   Packed:             0%

Recordspace used:      100%   Empty space:           0%  Blocks/Record:   1.00

Record blocks:           8    Delete blocks:         0

Record data:           136    Deleted data:          0

Lost space:              0    Linkdata:              0


 


다음으로 복구 옵션과 그 밖의 옵션은 다음과 같다.


 


+-------------------------------+------------------------------------------------------+


| 복구 옵션                                | 설명


+-------------------------------+------------------------------------------------------+


| -B, --backup                          | .MYD 파일의 백업 파일을 '<파일명>-<생성시간>.BAK' 라는


|                                              | 이름으로 만든다.


| --correct-checksum                | 테이블의 checksum 정보를 바로 잡는다.


| -D, --data-file-length=<크기>   | 복구후 다시 생성할 데이터 파일의 최대 크기를 지정한다.


| -e, --extend-check                 | 로우 단위로 세밀하게 복구한다. 따라서 많을 쓰레기 값들이


|                                             | 생길 수 있다. 파일이 완전히 손상된 경우에만 이 옵션을


|                                             | 사용하도록 한다.


| -f, --force                              | 기존의 임시 파일을 덮어쓴다.


| -r, --recover                          | 거의 모든 것을 복구한다.


| -n, --sort-recover                   | 임시 파일이 매우 크더라도 강제적으로 정렬해서 복구한다.


| -o, --safe-recover                  | 예전 버전의 복구 방법을 사용하여 복구한다. -r 보다는


|                                             | 느리지만 -r 옵션에서 복구할 수 없는 몇가지 것들을


|                                             | 복구할 수 있다.


| --character-sets-dir=...           | 문자셋 디렉토리를 지정한다.


| --set-characater-set=<문자셋> | 문자셋을 지정한다.


| -q, --quick                             | 데이터 파일(.MYD) 를 제외하고 빠르게 복구한다.


+------------------------------+-------------------------------------------------------+


 


+------------------+-------------------------------------------------------------------+


| 그 밖의 옵션         | 설명                                                                                              |


+------------------+-------------------------------------------------------------------+


| -a, --analyze       | 키의 분포를 분석한다. 몇몇 JOIN 을 빠르게 할 것이다.


| -d, --description   | 테이블의 정보를 출력한다.


| -S, --sort-index    | 인덱스 블록을 정렬한다. 다음 값 읽기(read-next) 의 속도를 향상시킨다.


| -R, --sort-records | 인덱에 따라 로우를 정렬한다. SELECT 와 ORDER BY 속도를 향상 시킬


| =<인덱스 번호>     | 수 있다. <인덱스 번호> 는 SHOW INDEX FROM <테이블명> 으로


|                            | 알 수 있다.


+------------------+-------------------------------------------------------------------+


 


테이블의 데이터 파일은 시스템이 전원이 나가서 MySQL 서버가 비정상 종료된 경우나 하드웨어의 결함 등으로 손상될 수 있다.


 


테이블이 손상된 경우에는 다음과 같은 에러가 출력될 수 있다.


 


<테이블명>.frm' is locked against change


Can't find file <테이블명>.MYI' (Errcode: <에러코드>)


Unexpected end of file


Record file is crashed


 


테이블이 손상되면 myisamchk 를 이용하여 복구를 실행하게 되는데, 손상된 부분을 찾아 복구 하는 과정을 알아보자.


 


그전에 먼저 테이블의 데이터 파일에 대해 알아야 할 필요가 있다. 앞서 설명한 바가 있지만 하나의 테이블에 대해 기본적으로 3 개의 파일이 생성된다. 각각의 파일은 다음과 같다.


 


+------------------+-------------------------------------------------------------------+


| 파일                    | 설명                                                                                              |


+------------------+-------------------------------------------------------------------+


| <테이블명>.MYI   | 인덱스 파일이다.


| <테이블명>.frm     | 테이블의 구조를 정의하는 파일이다.


| <테이블명>.MYD  | 데이터 파일이다.


+------------------+-------------------------------------------------------------------+


 


에러 코드에 대한 에러 메세지를 보는 방법


MySQL 의 bin 디렉토리를 보면 perror 라는 유틸리티를 찾을 수 있다. 이 유틸리티는 MySQL 의 에러 코드에 대한 메시지를 확인할 때 사용할 수 있는 유틸리티이다. 사용 방법은 다음과 같다.


 


perror [옵션] [<에러코드1> [<에러모드2>...]]


 


perror 을 이용하여 몇 개의 에러 코드에 대한 메시지를 출력한 예이다.


 


C:\windows\system32>perror 126 127 132


MySQL error:  126 = Index file is crashed

MySQL error:  127 = Record-file is crashed

MySQL error:  132 = Old database file


 


테이블의 복구 과정은 다음과 같이 진행된다.


 


1 단계 : 테이블 체크


먼저 myisamchk *.MYI 를 실행하거나 시간이 더 있다면 myisamchk -e *.MYI 를 실행하여 어떤 테이블에 오류가 있는지 체크한다. 테이블의 오류가 체크되면 2 단계로 넘어가고, 체크가 되지 않거나 체크 도중 에러가 발생하면 3 단계로 넘어간다.


 


2 단계 : 쉬우면서 안전한 복구


오류가 있는 테이블을 발견하였으므로 myisamchk -r -q <테이블명> 을 실행하여 빠른 복구를 실행한다. -q 옵션을 부여하였기 때문에 데이터 파일(.MYD) 은 건드리지 않고 인덱스 파일을 복구할 것이다. 이런 식으로 계속해서 다음 테이블을 복구한다.


 


그래도 복구가 되지 않으면 다음과 같이 시도한다.


 


1. 데이터 파일을 백업해둔다.


2. myisamchk -r <테이블명> 을 실행하여 복구 모드로 복구를 실행한다. 오류가 있는 로우를 삭제하면서 새로운 인덱스 파일을 생성할 것이다.


3. 그래도 안되면 myisamchk --safe-recover <테이블명> 을 실행한다. 예전 버전의 복구 방법이지만 이 옵션으로 복구될 수도 있다.


 


복구 도중 에러가 발생하면 3 단계로 넘어간다.


 


3 단계 : 어려운 복구


이 단계는 인덱스 파일의 처음 16K 가 손상되거나 잘못된 정보를 가지고 있을 때 혹은 인덱스 파일이 없을 때 실시하게 된다. 이러한 경우 필요에 따라 다음과 같이 인덱스 파일을 생성하도록 한다.


 


  1. 데이터 파일(.MYD) 을 다른 장소로 이동한다. 복사가 아니라 이동이다.


  2. mysql> 에 접속하여 해당 테이블을 삭제한다. TRUNCATE TABLE 문장을 사용할 것을 권장한다.


  3. 앞서 이동시켜둔 데이터 파일을 원래 위치로 복사하자. 이 때는 이동이 아니라 복사를 시켜서 만약의 사태를 대비한다.


  4. myisamchk -r -q <테이블명> 을 실행하여 복구한다. .frm 파일과 .MYD 파일을 이용하여 인덱스 파일(.MYI) 이 복구될 것이다.


 


4 단계 : 매우 어려운 복구


이 단계는 .frm 파일마저 심하게 손상되었을 때 실행한다. 사실 최초 테이블을 생성항 이후 .frm 파일은 변경될 일이 없기 때문에 .frm 파일이 손상되는 일은 거의 발생하지 않는다. 이러한 경우 다음과 같은 방법으로 복구를 실시한다.


 


  1. 백업해둔 파일이 있다면 백업해둔 .frm 파일을 원래 자리로 복사시키고 3 단계를 실시한다.


     그리고 백업해둔 .MYI 파일을 원래 자리로 복사시키고 2 단계를 실시한다. 그리고 나서 마지막으로 myisamchk -r 을 실행한다.


  2. 백업해둔 파일은 없지만 테이블의 구조를 알고 있다면 .frm 파일과 .MYI 파일은 복구할 수 있다. 먼저 다른 곳에서 새로 테이블을 생성하여 .frm 과 .MYI 파일을 만들 수 있다. 물론  .MYD 파일은 새로 만들 수는 없을 것이다. 2 단계로 가서 기존의 .MYD 파일을 이용하여  .MYI 파일을 재구성한다.


출처 : 네이버 블로그 어딘가.. -_-;; 예전에 받아서 보고 정리해놓은... 거라...

반응형

'MySQL' 카테고리의 다른 글

[MySQL] ARCHIVE Engine  (0) 2017.07.19
[펌][MySQL] CockroachDB in Comparison  (0) 2017.07.03
[MySQL] auto_increment duplicate entry for key 1  (0) 2016.11.14
[MySQL] MS SQL to MySQL Migration  (3) 2016.11.09
[MySQL] old_passwords 관련 에러  (1) 2016.11.04

평소와 동일하게 Auto_Incremental 컬럼이 포함된 테이블에 Insert 를 할 때 다음과 같은 에러가 발생하는 경우가 생겼다.


ERROR 1062 (23000): Duplicate entry '11522886' for key 1


흠...스크립트는 아래와 같다.


CREATE TABLE `TimeLimitItems` (

  `ID` int(10) NOT NULL auto_increment,

  `World` enum('DRAC','GENT') NOT NULL default 'DRAC',

  `OwnerID` char(20) NOT NULL,

  `ItemClass` smallint(10) NOT NULL default '0',

  `ItemID` int(10) NOT NULL default '0',

  `LimitDateTime` datetime NOT NULL default '2003-04-04 12:00:00',

  `Status` tinyint(3) NOT NULL default '0',

  PRIMARY KEY  (`ID`),

  KEY `TimeLimitItems_m1` (`OwnerID`),

  KEY `TimeLimitItems_m2` (`ItemClass`,`ItemID`)

) AUTO_INCREMENT=11522885 ;


auto_increment 값이 위의 값과 다른 것을 확인할 수 있다.


즉, Duplicate 가 생길 수 없는 것이다.


혹시나 하여 해당 ID 값을 조회 했지만 해당 하는 값이 존재하지 않는 것으로 나왔다.


여러가지 검색을 통해 해결 방법을 나열해 봤다.

하지만 내가 해결한 방법은 간단하게 테이블 repair 를 진행한 것이다.


먼저 내가 해결한 방법이다.


repair table TimeLimitItems;


아래 방법들로 진행을 하고자 노력했지만 해결되지 않았고..테이블에 대해 문제가 생겼을 것으로 판단하여

복구를 진행한 것 뿐이다.

이것이 해결한 방법이다. (운이 좋았나....)


다음은 검색을 통한 방법 공유다.



[출처] http://ktdsoss.tistory.com/389


MySQL을 AUTO_INCREMENT 기능은 기본 키 값을 증가의 자동 시퀀스를 생성 할 수있는 가장 쉬운 방법입니다.

 너무 사용하기가 매우 쉽습니다 :


create table t (
    i integer auto_increment,
    other_field varchar(100),
    primary key(id)
);

 AUTO_INCREMENT를 사용할 때 염두에 두어야 할 몇 가지 기능.


AUTO_INCREMENT는 결코 테이블에 이미 존재한 미만의 값을 제공하지는 않습니다. 

명시 적으로 더 높은 값으로 ID를 추가 할 경우, AUTO_INCREMENT에 의해 생성되는 다음 값은 하나 더 높은 값일 것입니다. 
이 말은 간격이 있다는 것을 의미합니다. 
이것은 어떤 이유로 필드의 가능한 최대 값이 삽입되어있는 경우, 다음과 같은 삽입이 실패 할 것을 의미합니다 :
mysql> insert into t values (2147483647);
Query OK, 1 row affected (0.05 sec)
mysql> insert into t values ();
ERROR 1062 (23000): Duplicate entry '2147483647' for key 'PRIMARY'

 AUTO_INCREMENT counter를 재 설정 하는 방법

값은 테이블과 연관된 카운터에 의해 생성되고, 카운터는 증가값을 가집니다. 

그 경우를 제외하고 때때로 당신은 오래된 값으로 카운터를 재설정 할 수 있습니다.

alter table t auto_increment = 0;
당신은 자신이 DELETE로 테스트 테이블을 비우는 후이 일을 찾을 경우, TRUNCATE 명령문을 다음 번 사용해보십시오. 
그것은 모든 데이터를 삭제하고 자동 증가 카운터를 재설정합니다.

기록을 삭제하는 경우, AUTO_INCREMENT 는 서버 재시작으로 다시 점프할 수 있다.

InnoDB의 스토리지 엔진에서는 자동 증분 카운터는 디스크가 아닌 메모리에 저장됩니다.
 이 자동 증가 카운터가 MySQL을 다시 시작할 때 열이 최대 값으로 초기화되는 것을 의미합니다.
이것은 또 다른에서 삭제 된 데이터를 보관하는 하나의 테이블을 사용하는 응용 프로그램에 문제가 될 수 있습니다. : 갑자기 ID가 반복되는 경우

테이블은 하나의 AUTO_INCREMENT 값을 가지고, 그 값은 key가 된다.

자동 증가 컬럼은 기본 키가 될 필요가 없습니다;
그것은에 어떤 종류의 인덱스가 있다는 것을 충분합니다 심지어 고유 인덱스 일 필요는 없습니다.
MyISAM과 BDB 스토리지 엔진에서는 다중 열 인덱스에 보조 컬럼과 같이 자동 증가 열을 가질 수 있으며, 다음 자동 증가는 각 그룹에 대해 1에서 시작합니다


create temporary table t (
    v varchar(100), 
    i integer(2) auto_increment, 
    primary key(v, i)
) engine=myisam;
insert into t (v) values ('a'),('a'),('b'),('b'),('b');
select * from t;
+---+---+
| v | i |
+---+---+
| a | 1 |
| a | 2 |
| b | 1 |
| b | 2 |
| b | 3 |
+---+---+





BY LEE JI EUN




또 다른 방법이다.



[출처] http://jason-heo.github.io/mysql/2014/03/05/manage-dup-key2.html

MySQL 중복 키 관리 방법 (INSERT 시 중복 키 관리 방법 (INSERT IGNORE, REPLACE INTO, ON DUPLICATE UPDATE)

안내

본 문서는 블로그의 운영자인 본인이 Stackoverflow에 올린 답변을 정리한 글입니다. Stackoverflow에 올린 답변 중 한국에 있는 다른 개발자들에게도 도움이 될만한 Q&A를 보기 쉽게 정리했습니다. 가능한 경우는 SQLFiddle에 샘플 데이터도 같이 올려서 실습도 해 볼 수 있도록 하였습니다. 또한 전체 Q&A를 묶어서 PDF 파일로도 배포하고 있습니다. 방문해 주시는 많은 분들에게 도움이 되었으면 좋겠습니다.

Stackoverflow URL

http://stackoverflow.com/questions/20342518/on-duplicate-key-update-value-inserting-same-values-twice/20342598

질문

다음과 같이 INSERT 구문을 사용 중이다.

INSERT INTO person VALUES(NULL, 15, 'James', 'Barkely')
    ON DUPLICATE KEY UPDATE academy_id = VALUES(academy_id);

중복된 값을 여러 번 INSERT한 뒤에 SELECT를 해 보면 중복된 값이 저장되어 있다.

mysql> SELECT * FROM person;
+----+------------+------------+-----------+
| id | academy_id | first_name | last_name |
+----+------------+------------+-----------+
|  1 |         15 | James      | Barkely   |
|  2 |         15 | Cynthia    | Smith     |
|  3 |         15 | James      | Barkely   |
|  4 |         15 | Cynthia    | Smith     |
|  5 |         15 | James      | Barkely   |
+----+------------+------------+-----------+
5 rows in set (0.00 sec)

무엇이 잘못된 것인가?

답변

중복 키 관리를 위해서는 중복 방지를 할 컬럼이 PRIMARY KEY이거나 UNIQUE INDEX이어야 한다. 질문자의 경우 first_name과 last_name의 조합을 이용하여 중복 관리를 하려는 것 같다. 따라서다음과 같이 last_name, last_name을 PRIMARY KEY로 설정하거나,

ALTER TABLE person ADD PRIMARY KEY (first_name, last_name) UNIQUE INDEX를 추가해야 한다.

ALTER TABLE person ADD UNIQUE INDEX (first_name, last_name)

INSERT 시 중복 키 관리를 위한 방법에는 다음과 같이 3가지가 있다.

  1. INSERT IGNORE
  2. REPLACE INTO …
  3. INSERT INTO … ON DUPLICATE UPDATE

중복 키 관리는 본 책의 앞 부분에서 잠시 언급되었는데 각각의 특징을 좀 더 자세히 알아보도록 하자.

앞의 person 테이블을 다음과 같이 생성한 뒤에 테스트를 진행하였다.

CREATE TABLE person
(
  id INT NOT NULL AUTO_INCREMENT,
  academy_id INT,
  first_name VARCHAR(20),
  last_name VARCHAR(20),
  PRIMARY KEY (id),
  UNIQUE INDEX (first_name, last_name)
);

INSERT IGNORE

`INSERT IGNORE`는 중복 키 에러가 발생했을 때 신규로 입력되는 레코드를 무시하는 단순한 방법이다. 다음의 예를 보면 중복 키 에러가 발생했을 때 INSERT 구문 자체는 오류가 발생하지 않고, 대신'0 row affected'가 출력된 것을 볼 수 있다.

mysql> INSERT IGNORE INTO person VALUES (NULL, 15, 'James', 'Barkely');
Query OK, 1 row affected (0.00 sec)
 
mysql> INSERT IGNORE INTO person VALUES (NULL, 15, 'Cynthia', 'Smith');
Query OK, 1 row affected (0.00 sec)
 
mysql> INSERT IGNORE INTO person VALUES (NULL, 15, 'James', 'Barkely');
Query OK, 0 rows affected (0.00 sec)
 
mysql> INSERT IGNORE INTO person VALUES (NULL, 15, 'Cynthia', 'Smith');
Query OK, 0 rows affected (0.00 sec)
 
mysql> INSERT IGNORE INTO person VALUES (NULL, 15, 'James', 'Barkely');
Query OK, 0 rows affected (0.00 sec)

당연히 SELECT의 결과는 2건만 존재한다.

mysql> SELECT * FROM person;
+----+------------+------------+-----------+
| id | academy_id | first_name | last_name |
+----+------------+------------+-----------+
|  1 |         15 | James      | Barkely   |
|  2 |         15 | Cynthia    | Smith     |
+----+------------+------------+-----------+
2 rows in set (0.00 sec)

AUTO_INCREMENT 컬럼의 값이 1, 2인 것에 주목하라

REPLACE INTO

"REPLACE INTO"는 "INSERT INTO" 구문에서 INSERT를 REPLACE로 바꾼 구문이다. 사용 방법은 "INSERT INTO"와 완벽히 동일하다.

mysql> REPLACE INTO person VALUES (NULL, 15, 'James', 'Barkely');
Query OK, 1 row affected (0.00 sec)
 
mysql> REPLACE INTO person VALUES (NULL, 15, 'Cynthia', 'Smith');
Query OK, 1 row affected (0.00 sec)
 
mysql> REPLACE INTO person VALUES (NULL, 15, 'James', 'Barkely');
Query OK, 2 rows affected (0.00 sec)
 
mysql> REPLACE INTO person VALUES (NULL, 15, 'Cynthia', 'Smith');
Query OK, 2 rows affected (0.00 sec)
 
mysql> REPLACE INTO person VALUES (NULL, 15, 'James', 'Barkely');
Query OK, 2 rows affected (0.00 sec)

REPLACE INTO의 결과는 INSERT IGNORE와 다르게 중복 키 오류 발생 시 ‘2 rows affected’가 출력되었다. SELECT 결과는 다음과 같다.

mysql> SELECT * FROM person;
+----+------------+------------+-----------+
| id | academy_id | first_name | last_name |
+----+------------+------------+-----------+
|  4 |         15 | Cynthia    | Smith     |
|  5 |         15 | James      | Barkely   |
+----+------------+------------+-----------+
2 rows in set (0.00 sec)

id가 4, 5로 변하였다. 이를 ‘2 rows affected’와 함께 종합적으로 판단한다면 “REPLACE INTO”는 중복 키 오류 발생 시 기존 레코드를 삭제하고 새로운 레코드를 입력한 것이다. 그래서 ‘2 rows affected’가 출력되었다. 1건은 DELETE, 1건은 INSERT로 보면 되고, 새로운 레코드가 입력되면서 AUTO_INCREMENT 컬럼의 값이 매번 새롭게 발급되었다.

AUTO_INCREMENT는 흔히 레코드를 식별할 수 있는 id로 사용되는데 이 값이 변경될 수 있으므로 “REPLACE INTO”는 그다지 좋은 방법이 아니다.

ON DUPLICATE UPDATE

ON DUPLICATE UPDATE는 중복 키 오류 발생 시 사용자가 원하는 값을 직접 설정할 수 있다는 장점이 있다. 우선 기본적인 사용 방법을 보자.

mysql> INSERT INTO person VALUES (NULL, 15, 'James', 'Barkely')
    ->     ON DUPLICATE KEY UPDATE academy_id = VALUES(academy_id);
Query OK, 1 row affected (0.00 sec)
 
mysql>
mysql> INSERT INTO person VALUES (NULL, 15, 'Cynthia', 'Smith')
    ->     ON DUPLICATE KEY UPDATE academy_id = VALUES(academy_id);
Query OK, 1 row affected (0.00 sec)
 
mysql>
mysql> INSERT INTO person VALUES (NULL, 15, 'James', 'Barkely')
    ->     ON DUPLICATE KEY UPDATE academy_id = VALUES(academy_id);
Query OK, 0 rows affected (0.00 sec)
 
mysql>
mysql> INSERT INTO person VALUES (NULL, 15, 'Cynthia', 'Smith')
    ->     ON DUPLICATE KEY UPDATE academy_id = VALUES(academy_id);
Query OK, 0 rows affected (0.00 sec)
 
mysql>
mysql> INSERT INTO person VALUES (NULL, 15, 'James', 'Barkely')
    ->     ON DUPLICATE KEY UPDATE academy_id = VALUES(academy_id);
Query OK, 0 rows affected (0.00 sec)

INSERT 결과만 보면 “INSERT IGNORE”와 동일하다.

mysql> SELECT * FROM person;
+----+------------+------------+-----------+
| id | academy_id | first_name | last_name |
+----+------------+------------+-----------+
|  1 |         15 | James      | Barkely   |
|  2 |         15 | Cynthia    | Smith     |
+----+------------+------------+-----------+
2 rows in set (0.00 sec)

SELECT 결과를 보니, 중복 키 오류 발생 시 기존 레코드는 그대로 남아 있는 것 같다. 즉, id 값이 변경되지 않았다. 그렇다면 “ON DUPLICATE UPDATE”는 “INSERT IGNORE” 대비 장점은 없을까?아니다. 복잡하고 어려운 대신에 중복 키 오류 발생 시 사용자가 원하는 행동을 지정할 수 있다는 장점이 있다. 예를 위해 person 테이블 구조를 다음과 같이 변경했다.

CREATE TABLE person
(
  id INT NOT NULL AUTO_INCREMENT,
  academy_id INT,
  first_name VARCHAR(20),
  last_name VARCHAR(20),
  insert_cnt INT,
  PRIMARY KEY (id),
  UNIQUE INDEX (first_name, last_name)
);

그런 뒤 다음과 같은 INSERT 구문을 실행하였다.

mysql> INSERT INTO person VALUES (NULL, 15, 'James', 'Barkely', 1)
    ->     ON DUPLICATE KEY UPDATE insert_cnt = insert_cnt + 1;
Query OK, 1 row affected (0.00 sec)
 
mysql>
mysql> INSERT INTO person VALUES (NULL, 15, 'Cynthia', 'Smith', 1)
    ->     ON DUPLICATE KEY UPDATE insert_cnt = insert_cnt + 1;
Query OK, 1 row affected (0.00 sec)
 
mysql>
mysql> INSERT INTO person VALUES (NULL, 15, 'James', 'Barkely', 1)
    ->     ON DUPLICATE KEY UPDATE insert_cnt = insert_cnt + 1;
Query OK, 2 rows affected (0.00 sec)
 
mysql>
mysql> INSERT INTO person VALUES (NULL, 15, 'Cynthia', 'Smith', 1)
    ->     ON DUPLICATE KEY UPDATE insert_cnt = insert_cnt + 1;
Query OK, 2 rows affected (0.00 sec)
 
mysql>
mysql> INSERT INTO person VALUES (NULL, 15, 'James', 'Barkely', 1)
    ->     ON DUPLICATE KEY UPDATE insert_cnt = insert_cnt + 1;

Query OK, 2 rows affected (0.00 sec) SELECT를 해 보면 insert_cnt에는 해당 중복 값이 몇 번 INSERT 시도가 되었는지 기록되어 있을 것이다.

mysql> SELECT * FROM person;
+----+------------+------------+-----------+------------+
| id | academy_id | first_name | last_name | insert_cnt |
+----+------------+------------+-----------+------------+
|  1 |         15 | James      | Barkely   |          3 |
|  2 |         15 | Cynthia    | Smith     |          2 |
+----+------------+------------+-----------+------------+
2 rows in set (0.00 sec)

이 외에도 다양한 용도로 활용될 수 있다.

주의해야 할 점은 INSERT 구문에 주어진 값으로 UPDATE하고자 할 때는 항상 “VALUES(column)”과 같이 VALUES()로 감싸야 한다는 점이다. 만약 다음과 같이 VALUES() 없이 사용한다면 기존에존재하는 레코드의 컬럼 값을 의미하게 된다.

INSERT INTO person VALUES (NULL, 15, 'James', 'Barkely')
    ON DUPLICATE KEY UPDATE academy_id = academy_id;

앞과 같이 사용했을 때, 기존 person 테이블에 존재하는 ‘James Barkely’의 academy_id가 13이었다면, INSERT 후에도 academy_id는 여전히 13이다.

요약

  • INSERT IGNORE
  • REPLACE INTO …
  • INSERT INTO … ON DUPLICATE UPDATE


반응형

'MySQL' 카테고리의 다른 글

[펌][MySQL] CockroachDB in Comparison  (0) 2017.07.03
[펌] [MyISAM] myisamchk 사용하기  (0) 2016.11.25
[MySQL] MS SQL to MySQL Migration  (3) 2016.11.09
[MySQL] old_passwords 관련 에러  (1) 2016.11.04
[펌][MySQL] Stored Procedure 와 Compile  (0) 2016.11.03

MS Sql to MySQL 로 Migration 을 테스트로 진행해 봤다.


간단한 테스트라 Index , View, SP 등을 만들어서 옮기지 않고, 테이블 2개 4만5천건?정도의 데이터를 옮겨봤다.


MySQL WorkBench 를 이용하였으며..

짧게 진행한 부분이라 많은 부분을 공유하기 보다는 어떻게 진행하였고..


문제점에 대해서 해결한 정도다.


여기서 발생한 문제는 lower_case_table_names 가 0 으로 지정되어 있어서 소문자로 테이블이 생겨 테이블이 없다는 에러이었으며,

2번째는 언어 character set 이 맞지 않아 문제가 발생 하였다. (보통 깨져서 들어가기 마련인데...전혀 들어가지 않았다.)


아래는 하나하나 캡쳐한 것이다.


1. MS Sql Server 2008  (Source DB)

2. My SQL 5.7.12    (Target DB)

3. Windows 환경

4. MySQL Workbench 6.3.8 (단순 zip포터블이 아닌 설치형)


1. Source DB 설정 부분이다.

  MS SQL Server라서 ODBC 설정이 필요하다. ODBC 설정은 검색하면 잘 나와 있으니 검색하기 바란다.



2. Target DB(MySQL) 

  둘다 Local 에 설치하였지만 Host에서 접속만 가능하다면 원격지에 있는 것으로 IP 설정하면 된다.

  실제로 원격지에 있는 곳에 설정도 해 보았다.



3. Connection Test



4. Source DB에서 어떠한 DB 를 옮길 것인지 선택



5. 다시 한번 Connection 확인



6. 어떠한 테이블 / 뷰 등을 옮길 것인지 선택



7. 이전 선택한 Type들에 대해 Show selection 을 선택하면 원하는 테이블도 직접 선택이 가능한 옵션



8. 선택한 object들에 대해 체크 및 SQL 생성




9. Migration 대상 표시 및 세부 설정 지정



10. Target DB(MySQL)에 테이블 생성 및 SQL Schema Script 생성



11. 생성 되는 동안 상태 표시



12. 완료 현항 표시

  - 만약 에러 발생시 우측 화면에 오류 스크립트 표시



13. Migration Data에 대한 어떤 형태로 진행할 것인지 선태

  - Online의 경우 바로 Target DB에 생성

  - batch file 의 경우 batch file(bat) 파일로 생성되며 바로 Online DB로 Migration 은 되지 않음..

   단, batch file 을 실행 할 경우 해당 Online (Target DB)로 Migration 진행

  - 위와 동일한 개념으로 shell script 파일로 생성도 가능


  - 하단의 Option 의 경우 Migration 진행하기 전 해당 Table 을 Truncate 여부 등을 선택 가능

  - (Driver seeds data already encoded as UTF-8 )encoded를 이미 UTF-8일 경우 dump 하는 경우 (체크 하였더니 한글 부분은 깨져서 표시 되는것을 확인 -> 미체크로 한 후 진행)

  - 이외의 것들은 테스트 해 보지 않아서 잘 모르겠네요.ㅠ



14. 오류 발생1. 

  - 해당 오류의 경우 Data Migration fair 된 경우로 생성된 테이블명이 달라서 발생된 문제이다.

  - MySQL lower_case_table_names 이 0으로 되어 있어서 리눅스 mysql의 경우(My.cnf) 1로, MS 윈도우 내의 MySQL의 경우(My.ini) 2로 변경 후 MySQL 재시작을 해야한다.


   오류 발생2.

  - MySQL이 기본으로 설치하여 Latin1 으로 설정되어 있었던 부분을 확인

  - 그래서 Latin1 을 utf8mb4 로 변경 및 테이블도 character 변환 후 작업 하였더니 제대로 동작하였다.


15. 완료 후 해당 테이블에 대한 데이터 결과 및 완료 화면




이렇게 MS SQL to MySQL Migration 을 진행해 보았다.

비록 MySQL Workbench 를 이용하였으며, 단순 테이블 / 데이터 이관이었지만 추후에 필요시 해당 내용을 바탕으로 진행 된다면

분명 좋은 결과를 가져올 것이라고 예상한다.


반응형

+ Recent posts