기본 콘텐츠로 건너뛰기

AIX의 대규모 오라클데이터를 x86으로 마이그레이션 하면서 있었던 일...





아무 생각없이 쉽게 생각해버린 20 년간 축적된 AIX위의 ORACLE 데이터 이전 작업.. 

AIX가 서비스연한이 다되어 신규 구매를 해야만 하는 상황이다. 신규 구매 18억 + 연간 6억원의 유지 비용. 5년 사용기준으로 연간 약 10억원의 비용이 드는 것을 x86으로 교체함으로 신규 구매비용 5천만원(2대 이중화)으로 퉁치고 오라클 라이선스도 44Core -> 16 Core로 줄이는 것이 이번 프로젝트의 목표이다.

별 준비 없이 오라클이니까 라는 생각만으로 너무 자체 기능을 믿고 진행 한 것이 문제 였다. 

서비스 정지 허용 시간은 교섭에 교섭을 해서 단 7시간.. 

Full backup만으로 40시간이 걸린다. 게다가 센터가 클라우드 센터 내에 있는 물리 서버라 백업 장비 반입 불가, 백업 영역이 500GB도 채 남지 않은 곳에서 NAS 이용 불가, 1Gbps 네트워크에서 알아서 하랜다. 프로젝트를 받고 나서 이런 상황이었다는 사실을 뒤늦게 알았다.. 당연히 이런거 다 지원해주는 곳인줄 알았는데.. ㅠㅡㅠ

나중에 안 얘기로는 SI업체 견적으론 30억이었다고 한다. 

이전이 결정되자마자 DBA는 사표를 던지고.. 대타가 없어 내가 들어와서 간단한 인수인계만으로 준비를 시작..

서버 선택에서부터 이전준비, 이전작업까지 모두 나 혼자 하게 되어버렸는데, 그냥 그 동안의 경험상 문제 없겠지 하고 개시..

사전에 이건 미션임파서블이기 때문에 일부 데이터의 누락이 발생하거나 하면 수동처리하는 걸로 합의 했다. 

병렬 export & import용 스크립트를 만들어서 충분히 테스트 해봤다. 별 문제없이 계산 상으로 7시간정도에 맞출 수 있었다.

이전 당일...

서비스 정지후 복사 작업 진행.. 사전에 변경되지 않을거라 생각되는 2800여개 테이블을 미리 복사해두고 남은 1500여개의 테이블을 복사하는 시간만 40시간인 것이다. 

병렬 export 및 import를 스크립트화 하여 10개의 창에서 병렬로 진행했다. 

10 개가 되니까 문제가.. 

import 큐를 가져오고 중복되지 않도록 패리티 체크하는 것이 완전 동시에 일어나서 import가 2회 일어나는 사태가 발생. 더 큰 문제는 보통 import를 하면 중복되지 않게 해주지 않나? 그냥 추가로 import를 하네.. 이전 데이터 지우고 import하는 기능이 없고.. (impdp를 사용하려 했으나 11.2.0에서 뭔가 자꾸 에러 뜨는 상황이 생겨서 안함) 그래서 스크립트로 truncate후에 import를 하게 만들었더니 truncate가 완료 되기 전에 import를 하지 않나, import를 하다가 멋대로 중단하지 않나.. 
뭔넘의 import툴이 이렇게 안좋나?
중단도 에러로 중단이 아니고 그냥 import successfully 라고 하면서 일부만 하다니..

이로 인해 데이터 정합성에 큰 문제는 발생했으나, 서비스를 롤백하면 라이선스 문제 등으로 다시 시도할 여유 시간이 없어서 강행하기로 했다. 

결국 일부 데이터가 중복되거나 들어가다 만 테이블을 유저 서포트 및 관계자 전원이 찾아서 온 서비스 중에 비교하면서 업데이트 치거나 중복 삭제 등을 하기로 했다. 
거의 2 주를 이렇게 해서 맞추었는데..

어느날 갑자기 서버가 뻗었다. 
CPU 14%, Disk IO 2%, Memory 50%정도 사용하는데 서버가 자꾸 DB접속이 끊어진다는 얘기를 들었다. 

전문가라는 사람들에게 조언을 구해도 쿼리가 문제니 쿼리튜닝하시요.. 라는 식으로 끝났다. CPU가 놀고 있는데 쿼리튜닝을??

awr report로 여러가지 보다보니  이상하게 disk io가 여유가 있는데 enq: TX - row lock contention 라는게 생각보다 너무 많다. 그리고 이 메시지가 뜨면서 다른 대부분의 쿼리가 대기를 타고 있는게 보였다. 찾아보니 update등으로 인한 row lock에 관련된 메시지였다. 

iostat에서 디스크 사용률을 보라고 누가 조언해줬지만.. 
2%의 디스크 사용률에서 뭘보라고?
이건 아니다 싶어 디스크의 Read count, write count, read MB/s, write MB/s를 보았다. 
AIX의 성능만큼은 아니더라도 최대한 Disk IO 성능을 끌어내기 위해서 24개의 SSD를 RAID10으로 구성, 장애율이 늘어나기 직전의 8개를 하나의 볼륨으로 6, 8, 8개로 묶었다. (RAID10 구성시 8개가 넘어가면 성능 증가가 떨어지기 시작하며 2개 이상 디스크 장애율이 높아지기 시작하는 변곡점이 된다)

그 중에 6개로 묶은 볼륨의 write MB/s 가 3400 넘는게 신경 쓰였다. 
여기에 write를 중점적으로 하고 있는 것은 redolog...

오라클 권장에선 데이터와 redo log의 볼륨을 나누는 것을 권장해서 Vol1은 redo log, Vol2는 데이터 Vol3은 백업 및 인덱스를 잡았는데.. 권장대로 하면 안될 것 같아서 Vol1, 2, 3을 그 동안의 Disk IO 성능에 맞추어 redo log도 2 파일씩 11셋트를 Vol 1,2,3에 고루 분산시키고 데이터 및 Index도 Physical Read/Write를 보면서 분산을 해봤다. 
두 어번 분산해서 Vol1, 2, 3에 고루 분포 되도록 하니 위의 row lock contention이 사라졌다. Redo log의 write 대기타임이 원인으로 Redo log writing이 끝나야 비로소 latch free가 발생하여 다음 쿼리가 진행된다는 가설이 맞아 떨어졌다.

아무리 SSD라고 해도 Write는 거의 SAS HDD와 비슷하다. 조금 더 빠른 정도? SSD는 대부분 READ가 6배 이상 빠르다. 검색엔진에서는 SSD는 효율 적이지만 OLTP서비스는 SAS HDD가 가성비가 더 좋을 수 있다. 게다가 RAID5로 사용중인 서비스도 있다.. 그냥 RAID가 뭔지 모르는 사람들이 만든 서비스 같다는 생각이..
지금의 이야기는 MLC방식의 SSD의 이야기이고 가격이 비싼 SLC는 HDD의 3배 이상의 성능으로 Write를 할 수 있다. 가격도 2배 이상 올라간다. 가성비를 따진다면 MLC방식의 SSD가 좋긴 하다.

참고로 SAS형 과 SATA형을 잘 모르는 사람들이 많은데, 소켓은 같기 때문에 SAS 컨트롤러에 SATA를 끼워도 된다. 
차이는 SATA는 HDD의 컨트롤러를 CPU가 직접 컨트롤 한다. 이에 비해 SAS는 HDD의 컨트롤러가 Disabled 되고 SCSI컨트롤러가 직접 CPU랑 통신한다. 

어떤 차이가 있냐면 Disk수가 많을 수록 CPU가 제어해야 할 리소스를 HDD에게 많이 뺏기게 되고 CPU가 컨트롤 하다보니 Disk IO로 CPU가 100%가 될 수 있다. 
하지만 SAS를 사용하게 되면 SCSI가 IO를 모두 관제하기 때문에 CPU와 SCSI와의 컨트롤 리소스만 사용하므로 CPU는 5% 이상을 Disk IO에 할당하지 않는다. 

이렇게 해결이 되고 다시 일 주일..

memory allocation error가 떴다.. 메모리 할당이 안되??

ORA-01034: ORACLE not available
ORA-27102: out of memory
Linux-x86_64 Error: 12: Cannot allocate memory

이 내용에 대해서 그룹사의 오라클을 오래 사용했다는 DBA들에게 물어봤다. 
다들 PGA늘리라는 말만 한다..

그러니까.. 지금 PGA가 64GB이고 사용중인건 기껏해야 20GB라니까.. 

그랬더니 Large Pool을 늘려라..

Large Pool사용량을 봤더니 0.. 

더이상 수십년 여기 그룹사에서 DBA했다는 사람들의 말을 믿으면 안되겠다 싶었다. 
내 나름으로 여러가지 각도에서 보다가 느낀게.. 
다른건 ORA-XXXXX 이렇게 메시지가 뜨는데 

왜 마지막에는 

 Linux-x86_64 Error: 12: Cannot allocate memory

Linux-x86이라고 뜰까?

가설을 세워 봤다. 
Oracle에서 메모리를 할당해가다가 설정된 메모리보다 물리 메모리를 더 써버려서 OS가 할당을 못해주고, 그로 인해 Oracle에서는 out of memory가 뜬 게 아닐까.. 

실제로 Oracle설정은 64GB의 PGA에 250GB의 SGA를 할당 했는데 SGA도 100G도 사용하지도 않았다. 

그렇다면 Linux의 메모리 할당량 중에 Oracle에게 할당하는 건 뭘 보면 될까?

이렇게 찾다보니 Semaphore라는 개념이 나왔다. 
SHM 값들을 조정한 뒤에 OS에서 적용후 Oracle을 기동하면 Oracle에서 Linux의 SHM관련 값들을 로드해서 사용한다. 

즉, OS가 전부 사용해버리고 Oracle이 가져가는 값이 적다면 아무리 PGA, SGA를 설정해봐도 out of memory가 발생하는거 아닐까?

이 가설을 기반으로 몇 가지 실험을 했다. 

처음에는 SHM을 400G정도로 주면 이 중에서 Oracle이 가져가는 값이 여유가 생기지 않을까?

이렇게 했더니 df -h에서 /dev/shm 이 236G / 252G 까지 올라갔다. 
umount remount 옵션으로 이걸 400G까지 올려봤다.. 

그랬더니 기존에는 OS + Oracle메모리가 230G까지 버티던게 130G정도에서 out of memory 메시지가 떴다.. 

그 얘기는 SHM으로 OS가 메모리를 가져간 뒤에 남는걸로 오라클이 가져가서 할당하는거 아닐까?

그래서 다시 /dev/shm은 252G로 디폴트로 돌리고, 
/etc/sysctl.conf 에서 shm관련 값들을 250G 아래로 수정해 봤다. 
그리고 SGA도 250G였던 것을 179G정도로 낮추어 총 합이 512G정도의 물리 메모리에 비슷하게 맞추어 봤다. 

이렇게 하니까 out of memory 에러가 해소 되었다!

AIX에서는 이런거 안맞추고 물리 메모리를 계속 증가 시켜주었으니까 이런 이슈가 발생하지 않았던 것 같다. (AIX에서는 어디에 이런 설정이 있는지조차 모르겠다;;)

그리고 여기 그룹사에서는 Oracle을 이 정도 규모로 쓰면 모두 AIX로 바꾸나보다.. x86에서 사용하는 Oracle이라고 해도 거의 소규모이고.. 
원래 서비스 자체가 다들 작아서 전부 mysql로 커버 되니 oracle을 이렇게 까지 치밀하게 사용한 적이 없던 것 같다. 게다가 회사가 부자다 보니 그냥 돈으로 발랐던 것 같다.. =ㅅ=;;

드디어 안정화 되었나.. 싶더니

이번엔 CPU가 폭주하기 시작했다. 
그 동안에는 IO 병목으로 CPU가 튈 만큼을 받지 못했다면, 이 번에는 IO에 여유가 생기니 엄청나게 쏟아들어져 오는 쿼리로 CPU가 터진 것이다. 
CPU자체의 성능으로는 AIX와는 크게 다르지 않다.. 
하지만 가설을 세워 봤다. 
Oracle이 왜 x86은 Core당 0.5 로 라이선스를 계산 했을까?
그리고 예전에 영문으로 된 자료를 읽은 적이 있다. 
AIX등의 메인프레임은 1Core = 1CPU로 PPAR또는 LPAR를 이용해서 라이선스 키를 넣으면 물리 CPU가 활성화 되는 방식이므로 병렬 처리에서도 퍼포먼스 저하가 없다. 
하지만 x86은 1CPU내에 core를 이용해서 사용하기 때문에 아무리 코어를 전부 사용해도 CPU BUS의 성능에서 1Core 가 1CPU만큼을 내지 못하고 0.75정도의 계수로 점점 떨어진다는 내용이다. AMD의 경우는 멀티버스를 사용하지만 Intel의 Single bus보다 성능이 좋지 못하기 때문에 CPU클럭이 아무리 좋아도 병렬연산의 성능이 떨어지므로 Intel보다 저렴하다. 
게다가 CISC command가 달라 동일한 OS라 할지더라도 연산 속도의 차이 및 부동 소숫점 연산의 결과 차이등이 있어 유저가 말하는 OS버그 같은 현상이 자주 일어난다. 

이러한 이유로 x86에서는 CPU가 100%를 쳐도 CPU Bus의 성능으로 빨리 뽑아주지 못해서 AIX보다 좋은듯 하지만 병렬 처리에서 밀리는 이유이기도 하다. 

Oracle 라이선스를 기반으로 생각해본다면, 
원래는 44Core의 처리를 지금은 16Core * 0.75 * 0.75... 로 처리 하고 있는 것이다.

각 서비스 운영팀들은 그 동안 AIX에서 잘 써왔는데 x86에서 자꾸 장애가 생기니 그냥 AIX로 가는게 어떠냐고 말하지만 CTO가 강하게 밀어붙여서 x86으로 안정화를 꾀하는걸로 결론이 났다. 

그럼 이제 튜닝을 해야지.. 

튜닝을 하려고 보니까.. 1시간에 150만회 이상 동일 쿼리를 날리는 서버들이 몇 대가 보인다. 동접 유저는 1000명도 안되는데 이게 뭔일?
SQL을 추출해서 개발자들이랑 얘기해보니.. 
SQL을 날리고 몇 초안에 안돌아오면 루프를 돌려 무한대로 던지는 구조이다. 
뭐, 빨리 리턴만 되면 이걸로는 문제가 없다. 
가장 큰 문제는 다른 서비스가 한 쿼리당 7500만 row를 읽어내는등의 말도 안되는 쿼리를 짠 게 있다. 20년이 되어서 그런지 그냥 데이터 성향을 보지 않고 방치되어 있는 서비스 들이 있는 것이다. 

이게 여러 서버에서 자기의 타이밍에 던지는데.. 
이게 우연히 집중하면 CPU가 동시에 처리할 수 있는 수량을 넘어서면서 무한루프로 SQL던지는 애들이 폭주하는 방식이다.... 
1999년부터 지금까지 IT에 종사해 오면서 이런 자체 폭주 시스템은 처음본다. 
물론 이 서비스는 DB서버에 부하만 없다며 문제가 없는 구조이다. 
하지만, 병렬처리 성능이 확연하게 떨어져 있는 지금 가장 큰 문제로 자리 잡는다. 

그런데 각 서비스 들은 예전엔 잘 썼는데.. 라고 해서 
정말 지금 서버 성능이 그렇게 문제 였는지 확인을 위해 8월 시점의 awr과 12월 시점의 awr을 비교해 보았다. 

동일 시간대 한 시간 동안의 신규 SQL의 parsing숫자가 2배가 늘어있다. 
그리고 SQL execution은 1시간 동안 8월에 900만이었던 것이 지금은 1500만정도까지 늘어나 있다. 

그냥 AIX로 돌렸어도 펑크 직전이었던 것이다. (이 전에도 펑크나서 몇 회에 걸쳐서 라이선스를 넣어서 CPU와 메모리를 늘렸다고 한다. )
그런 사실을 숨진채 그냥 x86이 안좋다고 잡아떼는 서비스 개발팀들..
그럴리는 없다고 증거를 만드는 시간이 거의 한 달을 잡아먹은 것 같다. 

결국 각 서비스 개발팀들은 자기네 성능 개선을 위한 수정 작업을 거치고, 데이터 분포에 따른 SQL튜닝을 하여 한 달만에 안정화를 찾는데 성공했다. 

갑자기 영국 드라마 닥터 하우스가 생각이 났다.. 
병의 증세는 다양한 원인에서 오기 때문에 하나라고 단정짓고 처방하면 실패할 확률이 높다. 때문에 다각도에서 인체실험(?)을 통해서 욕을 먹더라도 진짜 원인을 찾아내는 의료 드라마 이다. 

나도 서비스 중에 하나씩 실험하면서 장애가 일어나는 타이밍을 조절하다보니 한 달간 20년 간의 장애보다 많이 일으켰다.. 하지만 가설을 하나씩 없애가면서 결국 전문가라고 하는 사람들이 찾지 못하는 원인을 소거법으로 규명해 나갔다. 

서비스 운영팀들에게는 조금 미안하지만.. 

SQL은 처음에는 성능이 좋더라도 국가, 문화, 트렌드에 따라 데이터 양이 달라져서 성능이 크게 바뀌는 경우가 많다. 그렇기 때문에 매 년 튜닝을 해주는 것이 좋다. 
하지만 이 회사는 20년간 한 번 만든 SQL은 그게 정석이라 생각하고 힌트조차 바꾸지 않고 그대로 사용해 왔다. 
때문에 쿼리 하나에 2시간이 걸리더라도 그냥 세월아 하면서 기다리고 있다. 
Join을 하기 위해 source의 테이블을 바꾸는 방법만으로도 개선이 되는가 하면, 
데이터의 cardinality가 클 수록 B-Tree index에 강하기 때문에 index도 Cardinality가 높은 애들을 앞으로 땡겨서 복합 인덱스를 만드는 경우가 index scan 보다 index seek로 가기 때문에 훨씬 성능이 좋아진다. 

물론 table을 보면 성능 이슈 때문에 비정규화를 했던 흔적이나 M/W를 만들지 않으면 DB가 터질거라 생각해서 MW를 전제하에 만들어진 것 같은 구조가 보였다. 
하지만 표준화를 해줄 개발팀 없이 서비스 개발팀이 제각각 만들어 하나의 DB에 모든 서비스를 때려 넣는 식으로 운영 해오다 보니 이런 문제가 만들어진 것 같다. 

그래도 개고생을 하면서 이 서비스와 시스템의 근본 적인 문제를 CTO도 알게 됬고, 내년도의과제를 명확하게 던질 수 있어서 의미가 깊은 작업이었던 것 같다. 

잠깐...
여기 내가 줄인 연간 비용이 순수익의 30%를 차지하는데?
인프라 만으로 순수익을 30% 올렸는데 뭔가 없나?


giip :: Free mixed RPA orchestration tool! 

댓글

이 블로그의 인기 게시물

Alter table 에서 modify 와 change 의 차이 :: SQL Server

두 개의 차이를 모르는 경우가 많아서 정리합니다.  modify는 필드의 속성값을 바꿀때 사용하구요.. change는 필드명을 바꿀떄 사용합니다.  alter table tbbs modify bNote varchar(2000) NULL; alter table tbbs change bNoteOrg bNoteNew varchar(2000) NULL; change에는 원래 필드와 바꾸고 싶은 필드명을 넣어서 필드명을 바꾸는 것이죠~ 더 많은 SQL Server 팁을 보려면  https://github.com/LowyShin/KnowledgeBase/tree/master/wiki/SQL-Server giip :: Control all Robots and Devices! Free inter-RPA orchestration tool! https://giipasp.azurewebsites.net/

책에서는 안 알려주는 대규모 트래픽을 위한 설계

음성 버전 :  https://www.youtube.com/watch?v=ZZlW6diG_XM 대규모 트래픽을 커버하는 첫 페이지 만드는 법..  보통 DB를 연결할 때 대규모 설계는 어떻게 하시나요?  잘 만들었다는 전제 하에 동접 3000명 이하는  어떤 DBMS를 사용해도 문제 없이 돌아갑니다.  여기서 이미 터졌다면 이 콘텐츠를 보기 전에 DB의 기초부터 보셔야 합니다.  아.. 개발 코드가 터졌다구요? 그럼 개발자를 때리셔야지요..  만약 3000명을 넘겼다면? 이제 Write/Read를 분리해서  1 CRUD + n개의 READ Replica를 만들겠죠?  보통 Read Replica는 5개가 최대라고 보시면 됩니다.  누가 연구한 자료가 있었는데...  6번째 레플리카를 만든느 순간 마스터가 되는 서버의 효율 저하 때문에  5번째에서 6번쨰로 올릴때의 성능이 급격히 줄어든다는 연구 결과가 있습니다.  때문에 Azure에서도 replica설정할 때 5대까지 밖에 설정 못하게 되어 있지요.  유저의 행동 패턴에 따라 다르긴 하지만,  1 CRUD + 5 Read Replica의 경우 동접 15000명 정도는 커버 합니다.  즉, 동접 15000명 에서 다시 터져서 저를 부르는 경우가 많지요..  이 때부터는  회원 DB, 게시판DB, 서비스DB, 과금 DB 등등 으로 성격, 서로의 연관도에 따라 나누기 시작합니다.  물리적으로 DB가 나눠지면 Join을 못하거나 Linked Table또는 LinkDB등의 연결자를 이용해서 JOIN이 되기도 합니다.  그에 따라 성능 차이가 생기지만 가장 중요한 포인트는  서로 다른 물리적 테이블의 JOIN은 인덱스를 타지 않는다!  라는 것입니다. 즉, JOIN할 테이블들을 최소한으로 만든 뒤에 JOIN을 걸지 않으면 NoSQ...

BI의 궁극판! Apache Drill을 써보자!

사실 Apache Drill 은 BI(Business Intelligence)라고 부르는 것 보다는 단순 데이터 연결 엔진이다. https://drill.apache.org/ 하지만 내가 왜 극찬을 하느냐면.. DBA로서 항상 문제가 되어왔던게, 이기종 데이터의 변환이나 처리였다. 포맷을 맞추는데 엄청난 시간이 걸리고, 데이터 임포트 실패가 무수하게 나고.. 한 번 잘못 데이터를 추출하면 다시 조정, 변환, 추출하는데 시간이 많이 걸린다. 그런데! Apache Drill은 그냥 RDB를 CSV랑 연결해서 조인해서 통계를 낼 수 있다. 그것도 표준 SQL을 사용하여! 예를 들어, CSV의 세 번째 컬럼이 price 이고, 물건의 판매이력을 PG사에서 CSV로 출력 받았다. 우리 DB와의 검증을 위해서는 수동으로 Import를 한 뒤에 포맷이 안맞아 잘리는 데이터가 있다면 다시 맞춰주고, 재 임포트를 수십 번, 그리고 나서 겨우 들어간 데이터를 조인하여 빠진 데이터를 분간한다. 숫자가 적다면 개발자가 개발로 처리할 수도 있지만, 건수가 하루에 300만건 짜리라면.. 한 달 온 파일은 9천만 건이다. 프로그램으로 고작 처리하는 것이 초당 500건. 거의 20만초, 에러 없이 약 56시간.. 에러가 생기면 다시 56시간.. ㅠㅡㅠ 이런게 현실이기 때문에 쿼리 말고는 방법이 없다. apache drill 의 진면목을 보자! 이번에는 좀 범용 적인 MySQL DB와 붙여 보자. . 난 이번에는 Mac에서 작업을 했기 때문에 그냥 다운 받아서 풀었음.. https://drill.apache.org/download/ 여기서 자기 OS에 맞는 버전을 받아서 설치하시길.. 압축을 풀고 나면 MySQL 커넥터를 붙여야 한다. https://dev.mysql.com/downloads/connector/j/5.1.html 여기서 다운로드 이런 커넥터 들을 붙일 때마다 콘피그를 수정해 줘야 하지만, 몇 번만...