본문 바로가기

데이터베이스(DA, AA, TA)/대용량DB

[대규모데이터] 규모조정의 요소

데이터가 커지면 그 속도차에 기인하는 문제가 복잡해지기 쉽습니다. 이런 사항들이 시스템 전체의 확장성 전략에 어떤 영향을 주게 됩니다. 대규모 환경이라고 하면 서버를 여러 대 나열해놓고 그 서버로 부하를 분산하게 됩니다. 웹 서비스에서 자주 거론되는 규모조정(scaling), 확장성(scalability)은 그런 종류의 이야기입니다.


웹 서비스에서는 고가의 빠른 하드웨어를 사서 성능을 높이는 '스케일업(scale-up)' 전략보다도 저가이면서 일반적인 성능의 하드웨어를 많이 나열해서 시스템 전체 성능을 올리는 '스케일아웃(scale-out)' 전략이 주류입니다. 개별적인 이유는 다양하겠지만, 스케일아웃 전략이 더 나은 이유는 웹 서비스에 적합한 형태이고 비용이 저렴하다는 점과 시스템 구성에 유연성이 있다는 점이 포인트입니다.


하드웨어 가격이 성능에 비례하지는 않습니다. 가격이 10배인 제품이 속도나 신뢰성 면에서 10배만큼 발휘하는 것은 아닙니다. 다양한 패턴이 있겠지만, 동일한 성능을 확보할 때 저가의 하드웨어를 나열해서 확보하는 편이 더 낫습니다.


시스템 구성의 유연성이라는 것은 생각하기 나름인데, 부하가 적을때는 최소한으로 투자하고 부하가 높아짐에 따라 확장해가기 쉽다는 것이고, 혹은 상당한 용도의 서버도 저렴하고 간단하게 준비할 수 있다는 것입니다. 여러모로 빠르게 상황 대처가 쉽다는 점일 것입니다.



규모조정의 요소 - CPU 부하와 I/O 부하


스케일아웃은 하드웨어를 나열해서 성능을 높이는, 즉 하드웨어를 횡으로 전개해서 확장성을 확보해가게 됩니다. 이때 CPU 부하의 확장성을 확보하기는 쉽습니다.


예를 들면 웹 애플리케이션에서 계산을 수행하고 있을 때, 즉 HTTP 요청을 받아 DB에 질의하고 DB로부터 응답받은 데이터를 가공해서 HTML로 클라이언트에 반환할 때는 기본적으로 CPU 부하만 소요되는 부분입니다. 서버 구성 중에 프록시나 AP 서버(application server)가 담당할 일입니다.


한편 DB 서버 측면에서는 I/O 부하가 걸립니다. 이는 대규모 데이터 및 DB라는 두 가지 관점에서 자세히 살펴 보도록 합니다.



웹 애플리케이션과 부하의 관계


아래 그림은 웹애플리케이션과 부하의 관계를 나타낸 그림입니다. 웹 애플리케이션의 3단 구조에는 프록시, AP 서버, DB가 있습니다. 웹 애플리케이션에 [1][2]라는 요청이 오고, DB에 도달해서 I/O가 발생하고, 이 I/O가 발생해서 되돌아온 콘텐츠를 변경한 후 클라이언트로 응답합니다. 기본적으로 AP서버에는 I/O 부하가 걸리지 않고, DB 측에 I/O 부하가 걸립니다.


AP 서버는 CPU 부하만 걸리므로 분산이 간단합니다. 그 이유는 기본적으로 데이터를 분산해서 갖고 있는 것이 아니므로 동일한 호스트가 동일하게 작업을 처리하기만 하면 분산할 수 있습니다. 따라서 대수를 늘리기만 하면 간단히 확장해갈 수 있습니다. 결국 새로운 서버를 추가하고자 한다면 원래 있던 서버와 완전히 동일한 구성을 갖는 서버, 심하게 말하면 복사본을 마련해서 추가하면 됩니다. 요청을 균등하게 분산하는 것은 로드 밸런서(load balancer)라는 장치가 해줍니다.


한편 I/O 부하에는 문제가 있습니다. DB를 물리적으로 추가한다고 해도 하나의 DB에서 쓰기가 발생했을때 다른 DB와 데이터를 어떻게 동기화할 것인가라는 문제가 생깁니다. 데이터를 복사하면 좋겠지만 한 DB에 쓰인 데이터를 다른 DB에 어떻게 쓸 것인가라는 상황에 놓이게 됩니다. 쓰기는 간단히 분산할 수가 없습니다.



DB 확장성 확보의 어려움


이상과 같이 DB 확장성을 확보하는 것은 상당히 어렵습니다. 디스크가 느리다는 문제도 여기에 영향을 미칩니다. AP서버 요청시 디스크를 거의 사용하지 않으므로 디스크에 관해서는 별로 생각하지 않아도 되겠지만, DB에서는 디스크를 많이 사용하므로 디스크 I/O를 많이 발생시키는 구성으로 되어 있으면 속도차 문제가 생기게 됩니다. 게다가 데이터가 커지면 커질수록 메모리에서 처리 못하고 디스크상에서 처리할 수 밖에 없는 요건이 늘어납니다.


즉, 대규모 환경에서는 I/O 부하를 부담하고 있는 서버는 애초에 분산시키기 어려운데다가 디스크 I/O가 많이 발생하면 서버가 금새 느려지는 본질적인 문제가 있습니다.


서비스를 운영해보면 '서비스가 무거우니 서버를 늘리는게 어때?'와 같은 제안을 하는 경우도 있는데, 사실 서버를 늘려서 해결할 수 있다면 간단할 것입니다. 그것만으로는 문제가 해결되지 않기 때문에 더욱 어려운 것입니다. 그러므로 어딘가의 시스템을 이요하면서 무겁게 느껴진다고 해서 '서버를 늘리지'라고 생각하는 것은 위험합니다. 혹시 그렇게 말한다면, 그 순간에 기술적인 지식의 배경이 훤히 드러나 보일 수도 있습니다.



CPU 부하의 규모조정은 간단하다.

- 같은 구성의 서버를 늘리고 로드밸런서로 분산

- 웹, AP 서버, 크롤러(crawler)


I/O 부하의 규모조정은 어렵다.

- DB

- 대규모 데이터



두 종류의 부하와 웹 애플리케이션


부하는 CPU 부하, I/O 부하 두가지로 분류됩니다. 대규모의 과학계산을 수행하는 프로그램이 있는데, 이 프로그램은 디스크 입출력(Input/Output, I/O)은 하지 않지만 처리가 완료될 때까지 상당한 시간을 요한다고 가정해봅시다. 이 프로그램의 처리속도는 CPU의 계산속도에 의존하고 있습니다. 이것이 바로 CPU에 부하를 주는 프로그램입니다. 'CPU 바운드한 프로그램'이라고도 합니다.


한편, 디스크에 저장된 대량의 데이터에서 임의의 문서를 찾아내는 검색 프로그램이 있다고 가정해봅시다. 이 검색 프로그램의 처리속도는 CPU가 아닌, 디스크의 읽기속도, 즉 입출력에 의존할 것입니다. 디스크가 빠르면 빠를수록 검색에 걸리는 시간은 짧아집니다. I/O에 부하를 주는 종류의 프로그램이라고 해서 'I/O 바운드한 프로그램'이라고도 합니다.


일반적으로 AP 서버는 DB로부터 얻은 데이터를 가공해서 클라이언트로 전달하는 처리를 수행합니다. 그 과정에서 대규모 I/O를 발생시키는 일은 드뭅니다. 따라서 많은 경우에 AP 서버는 CPU 바운드한 서버라고 할 수 있습니다.


반면, 웹 애플리케이션을 구성하는 또 하나의 요소 시스템인 DB 서버는 데이터를 디스크로부터 검색하는 것이 주된 일로, 특히 데이터가 대규모가 되면 될수록 CPU에서의 계산시간보다도 I/O에 대한 영향이 커지는 I/O 바운드한 서버입니다. 같은 서버라도 부하의 종류가 다르면 그 특성은 크게 달라집니다.



멀티태스킹 OS와 부하

Windows나 Linux 등 최근의 멀티태스킹 OS는 그 이름처럼 동시에 서로 다른 여러 태스크=처리를 실행할 수 있습니다. 그러나 여러 태스크를 실행한다고 해도 실제로는 CPU나 디스크 등 유한한 하드웨어를 그 이상의 태스크에서 공유할 필요가 있습니다. 그래서 매우 짧은 시간 간격으로 여러 태스크를 전환해가면서 처리함으로써 멀티태스킹을 실현하고 있습니다.

실행할 태스크가 적은 상황에서는 OS는 태스크에 대기를 발생하지 않고 전환을 할 수 있습니다. 그러나 실행할 태스크가 늘어나면 특정 태스크 A가 CPU에서 계산을 수행하고 있는 동안 다음에 계산을 수행하고자 하는 다른 태스크 B나 C는 CPU에 시간이 날 때까지 대기하게 됩니다. 이렇듯 '처리를 실행하려고 해도 대기한다'라는 대기상태는 프로그램의 실행지연으로 나타납니다.


top의 출력내용에는 'Load Average(평균부하)'라는 수치가 포함되어 있습니다.


load average: 0.70, 0.66, 0.59


Load Average는 왼쪽부터 차례로 1분, 5분, 15분 동안에 단위시간당 대기된 태스크의 수, 즉 평균적으로 어느 정도의 태스크가 대기상태로 있었는지를 보고하는 수치입니다. Load Average가 높은 상황은 그만큼 태스크 실행에 대기가 발생하고 있다는 표시이므로, 지연이 되는=부하가 높은 상황이라고 할 수 있습니다.



Average가 보고하는 부하의 정체

하드웨어는 일정 주기로 CPU로 인터럽트라고 하는 신호를 보냅니다. 주기적으로 보내지는 신호라는 점에서 '타이머 인터럽트(Timer Interrupt)'라고 합니다. 예를 들면, CentOS 5에서의 인터럽트 간격은 4ms(밀리초)가 되도록 설정되어 있습니다. 이 인터럽트마다 CPU는 시간을 진행시키거나 실행 중인 프로세스가 CPU가 얼마나 사용했는지를 계산하는 등 시간에 관련된 처리를 수행합니다. 이때 타이머 인터럽트마다 Load Average 값이 계산됩니다.


커널은 타이머 인터럽트가 발생했을 때 실행가능 상태인 태스크와 I/O 대기인 태스크의 개수를 세어둡니다. 그 값을 단위시간으로 나눈 것이 Load Average 값으로 보고됩니다. 실행 가능 상태인 태스크란 다른 태스크가 CPU를 점유하고 있어 계산을 시작할 수 없는 태스크입니다.


즉, Load Average가 의미하는 부하는, 처리를 실행하려고 해도 실행할 수 없어서 대기하고 있는 프로세스의 수를 말하며, 보다 구체적으로는 다음과 같음을 알 수 있습니다.


- CPU의 실행권한이 부여되기를 기다리고 있는 프로세스

- 디스크 I/O가 완료하기를 기다리고 있는 프로세스


이것은 분명히 직감과 일치합니다. CPU에 부하가 걸릴 것 같은 처리, 예를 들면 동영상 인코딩 등을 수행하고 있는 도중에 다른 동종의 처리를 수행하고자 해도 결과가 늦어지거나 디스크에서 데이터를 대량으로 읽는 동안은 시스템의 반응이 둔해집니다. 한편, 키보드 대기 중인 프로세스가 아무리 많더라도 그것을 원인으로 해서 시스템의 응답이 늦는 일은 없습니다.


Load Average 자체는 두 가지의 부하를 합쳐서 어디까지나 대기 태스크 수만을 나타내는 수치이므로 이를 보는 것만으로는 CPU 부하가 높은지, I/O 부하가 높은지는 판단할 수 없습니다. 최종적으로 서버 리소스 중 어디가 병목이 되고 있는지를 판단하려면 좀더 자세하게 조사할 필요가 있습니다.