Book Study/웹 엔지니어가 알아야 할 인프라의 기본

Chapter 7. 웹 서비스 튜닝 1 : 보틀넥을 찾는 방법

cocologue 2021. 4. 17. 20:21

7.1 용량의 검토 방법과 용량의 향상 / 7.2 시스템 튜닝의 철칙

  웹 시스템의 용량을 향상시키기 위해서는 용량을 고려하는 단위를 먼저 확인하여야 한다. 웹 시스템의 용량에는 단위 시간당 시스템 이용자의 수가 중요한데, 이 "단위 시간당 시스템 이용자 수"는 곧 단위 시간당 소요되는 처리량이라고 볼 수 있다. 즉, 웹 시스템의 용량은 단위 시간당 처리 성능과 단위 시간당 소요되는 처리량 중에서 낮은 쪽으로 결정이 되는 것이라 볼 수 있다. 따라서 용량을 향상시키기 위해서는 ① 단위 시간당 처리 성능을 높이거나 ② 단위 시간당 소요되는 처리량을 낮추는 방법 중 한 가지의 접근을 할 수 있다. 단위 시간당 처리 성능은 하드웨어 성능이나 배치 등의 물리적 제약에 따라 결정된다. 구체적으로는 CPU 성능(클럭 수 x코어 수), 메모리 성능(메모리 용량 x 액세스 속도), 디스크 성능(I/O 대역폭과 IOPS), 네트워크 성능(Bandwidth나 RTT) 등이 있다.

  엔지니어로서 성능을 높이기 위해 할 수 있는 일은 단위 시간당 처리 성능을 얼마나 적절하게 맞추는지, 단위 시간당 소요되는 처리량을 얼마나 낮출 수 있는지가 있고, 여기서 단위 시간당 소요되는 처리량은 단위 시간당 액세스 수 x 한 번의 액세스당 처리량 이므로, 사업적인 측면에서 액세스는 계속 증가하는 추세여야 하므로 한 번의 액세스 당 처리량을 낮추는 것이 이라고 볼 수 있다. 대부분의 웹 시스템은 처리가 동시에 실행되기 때문에 한 번의 액세스당 처리량이 감소하면 응답 시간이 줄어들게 된다. 이것은 RTT(Round Trip Time)이라고 하며, 웹 사이트의 화면을 표시하는 경우에는 화면이 모두 표시될 때까지의 시간이 속도라고 생각하면 된다. 실제로는 시스템 처리가 단순하지 못해 Lock 등의 리소스 경합의 요소가 더해져 복잡하지만, '한 번의 액세스 당 처리량'을 낮추면 시스템의 시간당 이용자 수를 증가시킬 수 있으므로, 이를 통해 시스템의 용량을 향상 시킬 수 있다.

  시스템을 튜닝한다는 것은 시스템의 용량을 높인다는 것이다. 여기서 인프라와 애플리케이션은 용량 향상에 있어 모두 고려 대상이 되어야 한다. 계층별로 튜닝을 진행하였을 때 인프라를 튜닝함으로써 마이너스 용량을 0까지 끌어올릴 수 있고, 애플리케이션을 튜닝함으로써 처리 효율을 개선할 수 있기 때문이다. 또한, 시스템 구성을 변경하여 보틀넥이 극적으로 해소되기도 하지만 시스템 자체의 스펙을 변경하지 않더라도 사용량을 줄여 튜닝을 하기도 한다. 시스템 튜닝을 하기 위해서는 우선 '무엇을 개선할 것인지'를 결정하여야 한다. 이러한 튜닝 대상, 튜닝 목표는 시스템 이용자의 동향이나 사이트 자체의 사업적인 요청으로부터 결정되는 경우가 대부분이므로 목표를 결정할 때 정확한 수치가 결정될 수 있도록 하여야 한다. 

시스템 튜닝의 방법

  시스템의 전체 용량은 보틀넥으로 결정된다. 이러한 환경에서는 보틀넥을 하나 해결하면 또 다른 부분이 보틀넥이 되는 것이 배부분이지만, 이것은 자연스러운 현상이므로 하나씩 튜닝을 하며 시스템을 개선해 나간다. 이처럼 시스템 튜닝은 보틀넥을 해소하는 것이 전부이므로, 보틀넥이 어느 구간인지를 파악하는 것이 가장 최우선 과제이다. 튜닝에는 계측, 보틀넥 발견, 개선할 부분과 지표 결정, 개선 실행을 계속적으로 진행하는 것이 필요하다.

 

7.3 보틀넥을 찾는 방법 - 기초

  구체적으로 보틀넥을 찾기 위해서는 아래 순서를 따라 진행한다.

 

① 대상과 목표를 결정한다.
② 데이터 흐름을 확인한다.
③ 데이터 프름의 포인트마다 처리 내용을 확인한다.
④ 시스템 리소스를 확인한다.

  운용 중인 시스템이 튜닝의 대상일 때는 주로 사용자의 피드백을 통해 어떤 화면의 전환에 대처하여야 하는지가 정해진다. 주로 "95%의 사용자에게 http://example.com/post/1을 3초 이내에 표시하도록 한다" 등의 목표가 정해지는데, 이러한 요구 사항이 주어지면 서비스의 액세스 로그를 확인하였을 때 해당 응답이 3초 이내에 오게 하는 것이 서비스 튜닝의 목표가 된다. 이렇게 튜닝의 대상과 목표가 주어지면, 대상을 바탕으로 데이터 흐름을 확인하게 되는데, 순서는 아래와 같다.

 

① FQDN으로부터 IP 주소를 확인
② IP 주소로부터 서버를 확인
③ IP 주소와 포트로부터 데이터를 수신하는 프로세스를 확인
④ 미들웨어의 설정으로부터 실행되고 있는 프로그램을 확인

  DNS 질의를 통해 어느 서버에 액세스를 확인하고 있는지 흐름을 확인하고, 해당 도메인이 정확한 IP주소에 제대로 할당되어있는지를 먼저 확인한 후, 대상 서버로 접속하여 IP주소가 제대로 네트워크 인터페이스에 연결되어있는지를 확인한다. 그 후에는 어느 프로세스가 고객의 데이터를 수신하는지를 확인하여 해당 포트를 사용하는 서비스의 설정을 확인한다. Apache의 경우 virtualhost 설정을 호가인 해 데이터가 정확히 어떤 식으로 처리되는지를 추가로 확인하게 된다. 여기까지 정상적으로 동작했다면, 여기서 부터는 구체적인 처리 내용을 확인하기 위해 애플리케이션의 프레임워크 사양을 확인하며 데이터의 흐름을 추적한다. 데이터의 구체적인 처리 내용은 애플리케이션의 소스코드를 확인하여야 한다. 특히 아래 6가지를 중점으로 소스코드를 확인한다.

 

① 프로그램 외부와의 통신 부분과 내용(DB, KVS 등)
② 프로그램의 외부 호출(exec 함수나 system 함수에 의한 외부 프로그램 실행)
③ 외부 시스템과의 통신 부분과 내용(API 호출 등)
④ 디스크의 액세스 부분과 내용
⑤ 배열이나 리스트의 크기가 불필요하게 큰 것은 아닌지 확인
⑥ 루프의 횟수가 불필요하게 많은 것은 아닌지 확인

  리소스를 확인할 때에는 제약과 사용량을 확인한다. 물리적인 제약이나 설정상의 제약, 그리고 각각의 이용 상황을 확인하여 모니터링을 한다. 모니터링 툴 외에 실시간으로 서버에서 직접 리소스를 확인할 때에는 이전 장에서 소개했던 'dstat', 'top', 'iostat' 명령어를 이용하여 점검을 진행한다. 성능뿐만 아니라 제약적인 부분도 확인하여야 한다. 대부분의 미들웨어는 의도치 않게 서버의 리소스를 모두 소진하지 않도록 제약 내에서만 가동할 수 있게 되어있다. 하지만 이 제약이 너무 엄격한 경우 미들웨어의 성능을 발휘할 수 없는 경우도 있기 때문에 주의 깊게 확인하여야 한다. 이러한 제약을 빠짐없이 파악하기 위해서는 대상 소프트웨어의 문서를 모두 읽는 것과 운용 경험을 쌓아 사례를 축적하는 방법이 최선이다. 예를 들어, OS에서 제한하는 ulimit의 'open file' 제한의 상한에 도달하면 Apache/MySQl의 오류 로그에 'Too many open files in system'이라고 에러가 출력이 된다. 또한 Apache의 'MaxClients' 제한의 상한에 도달하면 Apache의 오류 로그에 'server reached MaxClients setting, consider raising the MaxClients setting'이라고 출력된다. 이러한 상황은 사전에 설정을 변경하여 제약의 문제를 피하는 것이 최선이지만, 리소스 사용량을 고려하여 기본값은 작은 값으로 되어있기 때문에 발생할 때마다 적절히 대응하도록 한다.

 

[리소스에 대하여 확인해야 할 것]

물리적인 제약 확인 내용
CPU 성능 CPU 성능을 모두 사용하지는 않았는가?
메모리 메모리 용량을 모두 사용하지는 않았는가?
네트워크 네트워크 대역을 모두 사용하지는 않았는가?
디스크 디스크 대역과 IOPS를 모두 사용하지는 않았는가?

[LAMP(Linux, Apache, MySQL, PHP)+memchched 시스템에서 확인해야 할 것]

요소 확인내용 확인항목
OS 시스템 리소스의 상한에 도달하지 않았는가? ulimit의 open files
ulimit의 max user processes
net.ipv4 .ip_local_port_range
Apache 병렬 수의 상한에 도달하지 않았는가? MaxClients
PHP 메모리 사용 가능량의 상한에 도달하지 않았는가?
php-fpm이라면 병렬 수의 상한에 도달하지 않았는가?
memory_limit
php-fpm이라면 pm.max_children
MySQL 병렬 수의 상한에 도달하지 않았는가? max_connections
memcached 병렬 수의 상한에 도달하지 않았는가? -c(max connections)

 

7.4 보틀넥을 찾는 방법 - 로그

  앞 절에서 보틀넥은 '단위 시간당 소요되는 처리량'이라고 정의하며 이 값은 '단위 시간당 액세스 수 x 한 번의 액세스당 처리량'이라 설명하였다. 웹 시스템의 경우 어떤 URL에 대해 대처해야 하는지, 즉 시스템적으로 어떤 URL을 대상으로 해야 할지를 발견할 수 있으면 순조롭게 개선할 수 있다. 그러기 위해서는 액세스 로그를 근거로 문제점을 찾아간다. Apache에서 보틀넥을 찾기 위해서는 한 번의 액세스당 소요되는 시간을 함께 액세스 로그에 출력하여 볼 필요가 있다. 아래와 같은 로그 형식을 적용하면 요청을 처리하기 위해 소요된 시간이 함께 기록된다.

 

LogFormat "%h %l %u %t \"%r\" %>s %b"%{Referer}i\" \"%{User-Agent}i\" %D" combined

  이렇게 찍힌 액세스 로그를 통해 요청의 일시와 URL, 요청을 처리하는데 소요된 시간을 확인하여 각 URL마다 요청을 처리하는 데 소요된 시간의 총합을 계산하여 어떤 URL을 튜닝의 대상에 넣어야 할지 판단할 수 있다. 각 액세스의 '응답 시간(us) x횟수'가 시스템의 부하로 계산하여 합계가 큰 숫자로 나열하면 어떤 부분을 개선하여야 하는지 정확히 판단이 가능하다.

 

7.5 보틀넥을 찾는 방법 - 서버 리소스

  서버 리소스에서 보틀넥을 찾기 위해서는 모니터링 툴을 통해 서버의 그래프를 모두 살펴보고 그 측정 결과로부터 보틀넥 부분을 먼저 가늠해본다. 각각의 값에 대한 절대 값이 이상하지는 않은지 확인하고, 수상한 움직임이 있는지를 파악한다. 그래프를 판단할 때에는 ① 값이 일정한 값에 머물러 있는지 확인하여, 이미 서비스가 성능의 상한에 도달해 있지는 않은지 확인한다. 예를 들어 트래픽이 일정한 값 이상으로 늘어나지 않는다면 상위 회선의 대역 제한에 의해 그 이상의 성능이 제한되어 있을 가능성이 있다. ② 하루 전, 일주일 전의 같은 요일, 한 달 전의 같은 날짜와 값의 움직임이 크게 다른지 확인한다. 항상 같은 시간에 일어나던 변화가 일어났는지, 예상 밖의 변화가 일어나고 있지는 않은지 확인하여 값이 증가/감소할 타이밍에서 증가/감소하고 있는지, 평소보다 증/감량이 크거나 작은지, 그 추세가 급하거나 완만한지를 확인한다. ③ 값의 변동이 심한지 확인한다. 이 사례로는 데이터 결손, 데이터 불일치, 카운터 루프의 3가지를 생각해 볼 수 있는데, 그래프가 끊어져 있는 모습일 경우 데이터 결손으로 확인할 수 있다.

 

[Cacti, dstat, top에서의 항목 이름 대응]

본문 명칭 의미 Cacti dstat top
[User] 사용자 영역에서의 CPU 이용률 CPU User usr us
[System] 커널 영역에서의 CPU 이용률 CPU System sys sy
[Idle] Idle 상태의 CPU 이용률 CPU Idle idl id
[Iowait] I/O 대기에서의 CPU 이용률 CPU Iowait wai wa

  dstat과 top을 이용하여 dstat와 top의 'CpuN' 항목에서 100%가 계속되는 타이밍이 있는지를 확인하여, 이 현상이 지속되는 타이밍이 있다면 CPU 성능이 부족한지 의심해본다. CPU 성능이 부족하고 RTT도 느리게 되면 CPU 성능이 보틀넥일 수 있다. [User]가 너무 큰 경우, 애플리케이션에서의 처리량이 너무 많은 것이므로 낮출 필요가 있다. 처리를 다수의 코어에 분산하면 도움이 된다. [System]이 너무 큰 경우에는 애플리케이션의 효율화를 검토한다. 메모리 액세스가 너무 많을 시에는 메모리에 데이터를 유지하는 양이 적은 알고리즘으로 바꾸는 등 효율화를 진행한다. [Iowait]이 너무 큰 경우에는 그 원인을 찾아야 한다. 'iostat'에서 어떤 디바이스에 대한 I/O가 많은지, 그것이 어느 정도의 문제가 되고 있는지를 확인할 수 있다. 또한 dstat에서 'paging'이 발생하고 있는 것은 아닌지 확인한다. 'paging'이 지속적으로 발생한다면 이것은 메모리가 부족한 것을 뜻한다. 이때에는 메모리 용량을 늘리거나 메모리 사용량을 줄일 필요가 있다(이 경우 top에서 swap의 used 수치가 0이 아니었다고 해도 paging이 지속적으로 발생하는 것이 아닐 경우 문제가 되지는 않는다).

  추가로, 메모리에 free가 남아있을 필요는 없다(top에서 Mem의 free를 확인). Memcahed가 어느 정도 필요한지 추정하는 것은 매우 어렵지만, 캐시 후 사용하지 않은 용량은 확인 가능하기 때문에 그 값을 상한으로 하여 'MemUsed'를 늘려도 괜찮다. 캐시 후 별로 사용하지 않은 메모리 용량은 아래와 같이 확인한다.

 

$ grep -i '^inactive(file)' /proc/meminfo

 

7.6 보틀넥을 찾는 방법 - 애플리케이션 코드

  애플리케이션 속의 보틀넥을 찾기 위해서는 프로파일러를 사용한다. 사용하는 언어에 따라 프로파일러를 달리하여 사용한다. 

반응형