블로그 이미지
redkite

카테고리

분류 전체보기 (291)
00.SI프로젝트 산출물 (0)
00.센터 운영 문서 (0)
01.DBMS ============.. (0)
01.오라클 (117)
01.MS-SQL (15)
01.MySQL (30)
01.PostgreSql (0)
01.DB튜닝 (28)
====================.. (0)
02.SERVER ==========.. (0)
02.서버-공통 (11)
02.서버-Linux (58)
02.서버-Unix (12)
02.서버-Windows (2)
====================.. (0)
03.APPLICATION =====.. (11)
====================.. (0)
04.ETC =============.. (0)
04.보안 (5)
====================.. (0)
05.개인자료 (1)
06.캠핑관련 (0)
07.OA관련 (1)
Total
Today
Yesterday

달력

« » 2024.11
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30

공지사항

최근에 올라온 글

0027. Apache Performace Turning

아파치 서버 성능 개선을 설정 가이드

Woon San Ko

0.8

Revision History
Revision 0.8 2007.4.13 Woonsan
Ko
신규 작성

아파치 서버는 많은 부하가 발생하는 상황에서도 매우 훌륭하게 처리하는 서버이긴 하지만, 모든 것을 스스로 자동으로 최적화할 수는 없다. 이 글에서는, 어떻게 하면 아파치 서버를 최적의 상태로 설정하는가를 다룬다.

우리가 보다 나은 성능을 위해 제일 먼저 접근할 수 있는 것은, 아파치를 소스로부터 다시 빌드하는 일이다. 아파치에 어떤 모듈들을 포함시킬지, 또 그 모듈들을 동적으로 로드할지 아니면 정적으로 로드하게 할지 등을 잘 선택해야 한다. 이는 성능 뿐만 아니라 보안에 있어서도 매우 중요한 사항이다. 완전히 정적으로 모듈들을 로드하는 아파치 서버는 mod_so를 필요로 하지 않으며, 이는 운영에 있어서 약간의 성능 개선 효과를 가져온다. 그리고 플랫폼에 맞는 최적화 옵션들을 사용하여 성능을 개선할 수 있다.

아파치 2.0의 주요 목표 중 하나는, 아파치 1.3 보다 성능을 개선하고, 핵심 엔진을 완벽한 멀티쓰레드로 구현하여 대규모 사이트에 가용성을 보장하는 것이다.

아파치 2.0은 이를 위해 병렬처리모듈 (Multi Processing Module; MPMs) 을 제공한다. 유닉스 계열 서버들에서는 세 가지의 다른 옵션들을 적용하여 매우 다른 방식의 병렬처리 모드를 선택할 수 있다. MPM은 오직 빌드 시에만 적용된다. 그러므로 이것이 바로 우리가 제일 먼저 아파치를 다시 빌드하는 것에서 출발해야 하는 이유이다.

아파치는 성능, 처리 시의 서버의 동작, HTTP 그리고 네트워크 프로토콜 수준의 통제를 가능하게 하는 여러 가지 지시어들을 제공한다. 또한 기타 아파치 환경 설정들도 서버의 성능에 지대한 영향을 끼칠 수 있다. 시스템 관리자는 이러한 여러 측면들을 숙지해야만 보다 나은 성능을 얻을 수 있다.

성능은 보통 하나의 자원을 다른 자원에 대해 트레이드 오프하는 일이다. 예를 들어, CPU 시간 대 메모리, 메모리 대 대역폭, 또는 대역폭 대 CPU 시간 등. 우리가 만일 올바른 설정을 결정할 수 없다면, 거의 대부분 보다 나은 성능을 기대하기 어렵게 된다. 우리가 만일 서로 다른 자원들 간의 균형을 잘못 맞추게 되면, 종종 성능을 개선하려고 한 시도 자체가 오히려 성능을 떨어뜨리는 경우도 많다. 아파치 서버의 성능이 나아졌는지를 알아낼 수 있는 유일한 방법은 벤치마킹을 해 보는 것이다. 그렇기 때문에 아파치 서버는 벤치마킹 유틸리티 도구인 ab 프로그램을 함께 제공하고 있다. 이 ab 도구에 대해서는 이 글의 후반부에서 다룰 것이다.

아파치 서버는 또한, 아파치 서버를 중간 프록시로 설정함으로써 ("Reverse-proxy"), 다른 웹 서버들의 성능을 개선하는 데에 사용될 수도 있다. 프록시가 캐시 응답을 수행하게 설정함으로써, 대상 서버의 부하를 줄일 수 있다.

성능 개선에 있어서, 궁극적으로, 더이상 어떠한 성능 튜닝 작업을 하더라도 그 효과가 더 이상 없는 경우까지 도달할 수 있다. 이러한 지점에 이르면, 두 가지 가능한 대안이 있다. 하나는 보다 강력한 하드웨어로 이전하는 것이며, 또 다른 하나는 (보다 재밌는 방법인데) 보다 저급의 하드웨어들을 엮어서 클러스터를 구성하는 것이다. 아파치의 URL 재작성 기능과 프록시 기능을 함께 사용하게 되면, 매우 적은 노력으로 그러한 클러스터를 구성할 수 있다.

1. 아파치 서버 성능 관련 지시어

아파치는 서버의 성능과 직접적으로 관련된 여러 지시어들을 제공한다. 이 지시어들은 다음과 같이 프로세스, 네트워크, 그리고 HTTP 제어 수준과 같은 세 가지 주요 그룹들로 분화된다.

  • 아파치 서버가 시작될 때 프로세스와 쓰레드의 개수를 제어하는 지시어들과 아파치 서버가 풀을 어떻게 증가시키고 감소시키는지에 대한 지시어들. 아파치 2.0에서는, 이러한 사항들이 MPM에 의해서 제공되므로, 사용 가능한 지시어들과 기본 값들은 우리가 어떤 MPM을 사용하는가에 따라 달라진다. 이 지시어들은 다시 프로세스 관리와 쓰레드 관리라는 두 가지 그룹으로 나뉜다.

  • 네트워크 수준에서 아파치의 행위를 변경할 수 있는 지시어들. 이 지시어들은 수신되는 요청의 큐의 크기, 대기 프로세스 또는 대기 쓰레드에 요청을 할당하기 위한 락킹 메커니즘, 그리고 클라이언트에 정보를 보내기 위한 버퍼의 크기를 설정하는 데에 사용된다.

  • HTTP 프로토콜 수준에서 아파치의 행위를 변경할 수 있는 지시어들. 이 지시어들은 아파치가 클라이언트와의 연결을 어떻게 제어할지를 설정할 수 있게 한다. 이 지시어들에는 Timeout 값과 HTTP/1.1의 keepalive 설정을 포함한다. 또한 아파치 서버가 클라이언트로부터 데 한번에 얼마 만큼의 데이터를 받게끔 제한을 가할 수 있는 설정들도 포함한다.

1.1. MPM 설정

아파치 2.0의 가장 주요한 개선 사항은 바로 MPM의 도입이다. MPM은 아파치 2.0이 모든 플랫폼에서 완벽한 멀티 쓰레드 서버로서 동작할 수 있게끔 할 뿐만 아니라, 특정 서버 환경에서 가장 적절한 프로세스 모델을 선택하여 사용할 수 있도록 한다. MPM 아키텍처는 개방적으로 설계되어, 관심있는 사람이라면 누구나 자신의 프로세스 모델을 설계하고 구현하여 아파치 핵심 엔진에 결합할 수 있다. 심지어 TCP/IP 방식이 아닌 다른 통신 방법을 사용하는 MPM을 개발할 수도 있다.

MPM의 실제적인 선택은, --with-mpm 옵션에 의해, 아파치를 빌드할 때에 이루어지게 된다. 서로 다른 MPM들에 대한 설계 철학의 차이점들과 유사점들을 이해하는 것은 매우 유익하다. 이 장에서는 MPM과 관련된 각 설정들을 살펴 본다.

1.1.1. MPM 프로세스 모델

각각의 모든 MPM들이 서로 다르긴 하지만, 그들 모두 세 가지 그룹으로 분류될 수 있는데, 그 분류 기준은 프로세스 기반인가, 쓰레드 기반인가, 아니면 이 두 가지를 섞은 것인가이다.

1.1.1.1. Process-Only

Process-Only 서버는 서버 프로세스의 풀을 관리하면서, 필요에 따라 새로운 프로세스들을 생성하는 것이다. 이에는 두 가지 변형이 있는데, 하나는 필요할 때마다 새로운 프로세스를 생성 (fork) 하는 형태이고, 다른 하나는 미리 프로세스를 생성해 놓고 (prefork), 그 프로세스들을 풀에 넣어 두었다가 사용하는 방식이다.

아파치 1.3과 아파치 2.0의 prefork MPM은 모두 pre-forking 서버들이다. prefork MPM은 안정성과 아파치 1.3과 그 이전 버전들과의 호환성을 위해 제공된다.

1.1.1.2. Thread-Only

fork 시스템 호출을 지원하지 않는 플랫폼들은, 모든 쓰레드들이 단일 프로세스 내에서 동작하는 순수한 멀티 쓰레드 프로세스 모델을 사용한다. 이러한 플랫폼들로는, Windows, Netware, 그리고 BeOS가 있다.

beos MPM과 netware MPM은 동적인 쓰레드 풀을 관리하여, 클라이언트 요구에 따라 그 풀을 증가시키거나 감소시킨다. 이 MPM들은 본질적으로 prefork MPM이 동작하는 방식과 동일한 방식이라고 볼 수 있는데, 이들은 다만 프로세스들 대신에 쓰레드가 사용한다는 점이다.

winnt MPM은 가장 제한적이다. 이 MPM은 단일 프로세스 내에서 고정적인 개수의 쓰레드들을 동작시키며, 서버 생명 주기 동안에 쓰레드 풀이 증가하거나 감소하지 않는다. 쓰레드들은 최대 요청 처리 개수에 도달한 경우에 종료되고 재시작될 수 있다. 하지만 전체 쓰레드 개수는 변하지 않는다. 물론, 이러한 점은 환경 설정을 매우 쉽게 만든다. 즉, MaxRequestsPerChildThreadsPerChild 라는 두 가지 지시어들만 신경 쓰면 되기 때문이다.

1.1.1.3. Hybrid (Multiprocess/Multithreaded)

Hybrid 서버들은 프로세스 기반 모델과 쓰레드 기반 모델의 혼합 형태를 사용한다. 즉, 각 프로세스는 여러 쓰레드를 실행시킨다. 이러한 MPM 설정을 사용하는 것은 결국 프로세스 관리 지시어들과 쓰레드 관리 지시어들을 모두 사용해야 함을 의미한다. 또한, MPM의 구현 알고리즘에 따라 서로 다른 지시어들 사이의 상호 관련성이 달라질 수도 있다.

worker MPM은 프로세스의 동적 풀을 관리하는데, 각 프로세스는 고정된 개수의 쓰레드들을 포함한다. 모든 프로세스들의 모든 쓰레드들은 서버로부터 받은 클라이언트 요청에 응답할 수 있다. 이 설정은 아파치 2.0 유닉스 계열 서버들에서 가능한 주요 설정이다.

perchild MPM은 프로세스들의 정적인 풀을 관리하는데, 각 프로세스는 동적으로 변동 가능한 쓰레드들을 관리한다. 다른 MPM들과는 달리, 유독 perchild는 프로세스들이 다른 서버 설정들(예를 들면, 가상 호스트 설정)과 연관된다. 하나의 프로세스를 특정 가상 호스트와 연관시키게 되면, perchild는 각 가상 호스트가 자신만의 쓰레드 풀을 관리할 수 있도록 허용하여, 그 가상 호스트의 요청에 따라 쓰레드 풀을 증가시키거나 감소시키게 된다. perchild의 또 다른 독보적인 특성은 각 프로세스가 서로 다른 사용자나 그룹으로 실행할 수 있다는 점이다.

mpmt_os2 MPM은 OS/2 플랫폼에서 적용 가능하다. 동작 수행에 있어서는, 대부분 worker MPM과 동일하다.

1.2. 프로세스 관리 지시어

아파치는 프로세스 관리와 관련한 일곱 가지의 지시어들을 제공한다. 이들은 사용하는 MPM에 의존적이며, process-only 모델과 hybrid 모델에서 적용 가능하다.

Table 1. 프로세스 관리 지시어

지시어 환경 기본값 영향 지시어
설명
ServerLimit <number> 1.3/prefork, worker, perchild 1.3/prefork: 256, worker: 16, perchild: 8 1.3/prefork: MaxClients, worker: MaxClients, perchild: NumServers

ServerLimit 지시어는 유닉스 계열 아파치 서버에 의해 생성되는 서버 프로세스의 최대 개수를 제약함. 사용되는 프로세스 모델에 따라, 결과가 달라짐. ServerLimit 지시어의 목표는 모든 병렬 프로세스 서버들에 통일된 제한 사항을 가지는 것임. ThreadLimit과 함께 사용되는 경우에는, ServerLimit이 스코어보드 개수를 정의할 수도 있다. 이 지시어가 변경된 후 적용되려면, 서버가 재시작해야 함.

참고사항

ServerLimit 값은 CFLAGS 를 정의하여 컴파일 시에 설정할 수도 있다. 예) -DDEFAULT_SERVER_LIMIT=512. 하지만, 기본 최대값인 2000을 초과할 수는 없다. 이 기본 최대값은 빌드시에 -DMAX_SERVER_LIMIT=1000와 같이 오버라이드 가능함.

StartServers <number> 1.3/prefork, worker, mpmt_os2 1.3/prefork: 5, worker: 3, perchild: 2

프로세스들의 동적인 풀을 허용하는 프로세스 모델에서, 이 지시어는 아파치 서버가 시작될 때 생성해야 할 자식 프로세스들의 개수를 결정.

참고사항

MinSpareServers (1.3/prefork) 값이 만일 더 큰 경우에는 아파치 서버가 그 차이를 즉시 더 생성하게 된다. 프로세스 풀의 크기는 또한 MaxSpareServers (1.3/prefork) 값과 MaxRequestsPerChild (모든 MPM) 에 의해서도 영향 받는다.

NumServers <number> perchild

고정 불변의 프로세스 풀을 생성하는 프로세스 모델에서, NumServers 지시어는 생성되는 프로세스 개수를 결정. 현재 이 지시어는 perchild MPM에만 적용됨. perchild MPM은 StartServersMaxClients 지시어들 대신에 이 지시어를 사용한다. 이는 생성되는 프로세스가 불멸한다는 것을 의미하진 않는다. 만일 MaxRequestsPerChild 값이 0이 아니면, 각 자식 프로세스는 자신의 최대 요청 개수를 초과하게 되면 새로운 프로세스로 대체된다. 따라서, 프로세스의 총 개수는 항상 일정하다. 가상 호스트가 없는 상황에서 이 지시어에 해당하는 기본 값은 2이다. 하지만, 실제 운영할 때에는 이 값을 증가시켜야 할 때가 있을 것이다.

참고사항

perchild는 (반드시 그럴 필요는 없지만) 기본적으로 가상 호스트들과 함께 사용하기 위해 고안된 것이다. 그러므로 서버의 개수는 서버의 모든 가상 호스트들을 처리할 수 있을 정도로 충분히 있어야 한다. 이는 ServerLimit 지시어에 의해 제약되며, -DDEFAULT_NUM_DAEMON 과 같이 컴파일 시에도 설정될 수 있다.

perchild 프로세스를 가상 호스트들에 할당하기 위해서는, ChildPerUserID 지시어를 1에서 NumServers 지시어 값에 이르는 각각의 프로세스 번호에 설정하여, 프로세스에게 사용자와 그룹을 설정해 줄 수 있다. 그리고 나서 AssignUserID 지시어를 <VirtualHostgt; 컨테이너 내에서 설정하여 해당 가상 호스트가 하나의 프로세스와 연관될 수 있다.

MinSpareServers <number> 1.3/prefork

이는 한번에 가능한 아파치 프로세스의 최소 개수를 설정한다. 만일 프로세스들이 클라이언트 요청 처리에 바쁜 상황이 되면, 아파치는 새로운 프로세스들을 생성하여 서버의 풀의 크기를 최소 값으로 유지하게 된다. 필요에 따른 서버의 시작에 관한 아파치의 알고리즘으로 인하여, 이 값을 증가시키는 것은 동시에 많은 요청들을 동시에 처리해야 하는 상황에서만 의미가 있다. 즉 날마다 수백만의 히트를 기록하는 사이트들에서는 다음과 같은 설정이 적절하다.

MinSpareServers 32

참고사항

이 값은 -DDEFAULT_MIN_FREE_DAEMON=32 와 같이 컴파일 시에 설정할 수도 있다.

MaxSpareServers <number> 1.3/prefork

이 지시어는 한번에 IDLE 상태로 될 수 있는 최대 아파치 프로세스 개수를 설정한다. 만일 피크 시간에 많은 프로세스들이 시작되고 급격히 요청들의 빈도가 사라지게 되는 경우에는, 이 지시어의 설정이 너무 많은 프로세스들이 영속적으로 남아 있는 문제를 해결해 주게 된다. 이 값은 MinSpareServers 값 이상이어야 한다. 하루에 수백만 이상의 방문이 이루어지는 사이트에서는 다음과 같은 값을 설정하는 것이 타당할 수 있다. (하지만 worker MPM을 고려해 보아야 한다.

MaxSpareServers 64

MaxSpareServersMinSpareServers는 지금보다 예전에 매우 중요한 것이었다. 버전 1.3 이후로, 아파치는 들어오는 요청들을 1부터 최대 32개의 새로운 프로세스들을 생성하는 대응 알고리즘을 가지고 있다. 그 이유는, 아파치가, 반드시 필요하지 않는 한, 한꺼번에 너무 많은 프로세스를 시작하지 않도록 하는 데에 있다.

이러한 전략은 아파치가 서버 풀을 동적으로 관리하는 효과를 가져오게 된다. 다음은, 이 설정들에 대한 기본 값들이다.

  • StartServers: 5

  • MinSpareServers: 5

  • MaxSpareServers: 10

참고사항

이 지시어는 -DDEFAULT_MAX_FREE_DAEMON=64 와 같이 CFLAGS를 설정함으로써 컴파일 시에 설정 가능하다.

MaxRequestsPerChild <requests> 1.3, 모든 MPM

MaxRequestsPerChild 지시어는 특정 아파치 프로세스가 처리해야 하는 요청의 최대 개수를 설정한다. 이 설정의 주 목표는 메모리 릭을 방지하는 것이다. 만일 다음과 같이 설정하게 되면, 프로세스들은 영원히 종료하지 않게 된다.

MaxRequestsPerChild 0

참고로, beos MPM을 제외한 다른 모든 MPM들의 기본 값은 10000이다.

한편, 이 값이 너무 작게 되면, 성능 상의 문제를 일으킬 수 있다. 즉, 아파치가 너무 자주 프로세스를 시작시키고 종료시켜야 하기 때문이다. 유닉스 계열 시스템에서 바람직한 값은 1000~10000이며, winnt MPM에서는 1000~100000이다.

이 지시어에 설정된 값에 따른 그 결과는, 프로세스 모델에 따라 매우 다르게 나타날 수 있다. 1.3/prefork와 같은 순수한 프로세스 모듈에서는, 이 값이 각 프로세스의 생명 주기를 결정한다. 순수한 쓰레드 모델에서는, 이 값이 전체 서버 프로세스의 생명 주기를 결정한다. 왜냐하면 순수한 쓰레드 모델에서는 오직 하나의 프로세스만을 재시작하기 때문이다. 이 값을 100으로 설정하게 되면, 하나의 prefork MPM 프로세스는 100*StartServers ~ 100*MaxClients 개수의 요청들을 처리하게 된다. 그러나, 이것이 winnt MPM에 있어서 모든 쓰레드들의 완전한 재시작을 의미하지는 않는다.

참고사항

이 설정 값은 컴파일 시에 CFLAGS-DDEFAULT_MAX_REQUESTS_PERCHILD=10000와 같이 설정할 수도 있다. beos MPM은 특별한 경우인데, MaxRequestsPerThread 옵션을 제공한다. 이 값은 컴파일 시에 -DDEFAULT_MAX_REQUESTS_PER_THREAD=10000와 같이 설정될 수 있다.

MaxClients <connections> 1.3/prefork, worker

이 지시어는 아파치 서버가 전체적으로 처리해야 하는 클라이언트의 총 개수를 설정한다. 모든 프로세스가 모두 Busy 상태에 있을 때 (worker MPM인 경우는 모든 프로세스들의 모든 쓰레드들이 Busy 상태에 있을 때), 연결을 시도하려고 하는 클라이언트들은 ServerUnavailable 응답을 받게 된다. 그렇기 때문에 이런 경우 이 값은 아래와 같이 너무 작게 설정되어서는 안된다.

MaxClients 100

이 제한 설정은 worker MPM과 1.3/prefork MPM에서 설정 가능하지만, 서로 동작하는 방식은 다르다. 1.3/prefork의 경우에는, 이 값은 전체 프로세스의 개수에 대한 제한이 되며, 이 개수는 연결하려는 클라이언트들의 총 개수를 제한하게 된다. worker MPM의 경우에는, 클라이언트의 총 연결 개수는 프로세스 개수와 프로세스 당 쓰레드 개수 (ThreadsPerChild) 를 곱하여 계산된다. 따라서 MaxClientsThreadsPerChild와 곱해져야 한다. MaxCleintsServerLimit 값보다 더 크지 않아야 한다.

한편 MaxClients를 작은 값으로 설정하게 되면, 일부 클라이언트들이 연결할 수 없는 상황이 있을 수 있긴 하지만, 클라이언트 요청의 턴어라운드를 증가시키는 효과를 가져올 수 있다. 따라서, 이 지시어는 양날의 도구라고 볼 수 있으며, 서버가 성능 튜닝이 되어야 하는지, 업그레이드 되어야 하는지, 아니면 클러스터 되어야 하는지를 알려 줄 수 있다. 이런 맥락과 맞닿는 부분이 바로 요청들의 대기 큐인데, 이는 후에 다루어질 ListenBacklog를 통해 설정될 수 있다.


1.3. 쓰레드 관리 지시어

아파치는 여섯 가지의 쓰레드 관리와 관련한 지시어들을 제공한다. 다섯 가지는 프로세스 관리 지시어들과 유사한 방식이며, 나머지 하나인 MaxThreadsPerChildThreadsPerChild의 변형된 형태라고 볼 수 있다.

Table 2. 쓰레드 관리 지시어

지시어 환경 설명
ThreadLimit <number>

worker,

perchild

ThreadLimit 지시어는 병렬 프로세스들에 의해 생성되는 서버 쓰레드들의 최대 개수를 설정한다. 사용되는 프로세스 모델에 따라, 이 지시어는 다른 의미를 가질 수 있다. ThreadLimit의 목적은 모든 병렬 프로세스 서버들에서 단일한 쓰레드 제한 사항을 두기 위함이다. ServerLimit과 함께, ThreadLimit은 스코어보드 개수를 정의하므로, 서버가 재시작될 때 다시 읽혀지지 않고 원래의 값을 유지한다. 이 값의 변경 사항은 서버가 재시작되어야 적용된다. 이 지시어의 기본 값은 64이다.

ThreadLimit 64

ThreadLimit은 모든 프로세스들의 전체 쓰레드 개수를 제한한다. 이 값은 MPM에 따라서 다르게 적용될 수 있다. worker MPM에서는 이 값이 MaxClients를 제한하며, 또한 ServerLimit * ThreadsPerServer를 제한한다. perchild MPM에서는, 이 값이 NumServers * MaxThreadsPerServer 값을 제한하게 된다.

참고사항

이 값은 CFLAGS 값에 -DDEFAULT_THREAD_LIMIT=512 와 같이 설정함으로써 컴파일 시에 설정될 수도 있다. 하지만, 기본적으로 제공되는 최대값을 초과할 순 없는데, 그 최대 값은 기본적으로 20000이다. 이 값을 오버라이드하고 싶다면, -DMAX_THREAD_LIMIT=50000와 같이 사용하라.

StartThreads <number>

perchild,

netware,

beos

프로세스 당 동적인 크기의 쓰레드들을 관리할 수 있도록 제공하는 프로세스 모델에서는, StartThreads 값은 아파치가 시작될 때 생성해야 할 자식 프로세스들의 개수를 결정한다. 이 값의 기본 값은 다음과 같다.

  • perchild: 5

  • netware: 25

  • beos: 10

perchild MPM의 경우에는 이 값은 서버 당 설정되는 것으로서, 전체 쓰레드 개수는 StartThreads * NumServers가 된다. 단일 프로세스 모델인 netwarebeos MPM인 경우에는, 이 값이 서버가 시작될 때의 전체 쓰레드 개수가 된다.

참고사항

이 값은 CFLAGS-DDEFAULT_START_THREADS=20와 같이 설정함으로써 컴파일 시에 설정 가능하다. netware MPM은 이 값이 기본적으로 25로 설정된 DEFAULT_THREADS_PER_CHILD로 설정되어 있다.

MinSpareThreads <number>

worker,

perchild,

mpmt_os2,

netware,

beos

이 지시어는 언제든지 항상 가용한 최소 개수의 아파치 쓰레드 개수를 설정한다. 클라이언트 요청들로부터 쓰레드들이 Busy 상태에 이르면, 아파치는 새로운 쓰레드들을 생성하여 풀을 관리한다. 이 값의 기본 값은 5이다.

MinSpareThreads 5

perchild MPM이 아닌 다른 모든 MPM들에서는, 이 값이 전 서버에 해당하는 설정이 되지만, perchild MPM에서는, 이 값이 자식 프로세스 당 여분 쓰레드 개수를 설정하는 의미가 된다. NumServers가 10이고, MinSpareThreads가 5인 경우, 여분 쓰레드 개수는 50이 된다.

참고사항

이 값은 유닉스 계열, 윈도우, OS/2 서버들에서 컴파일 시에 CFLAGS-DDEFAULT_MIN_SPARE_THREADS=10와 같이 설정할 수 있다. 윈도우에서는, DEFAULT_MIN_FREE_DAEMON * DEFAULT_THREADS_PER_CHILD 값으로 설정된다.

Netware와 BeOS에서는, 지시어는 같지만, 컴파일 시의 옵션은 좀 다르다. -DDEFAULT_MIN_FREE_THREADS=10와 같이 사용하여 오버라이드 가능하다. 이 두 가지 플랫폼에서 기본 값은 10이다.

MaxSpareThreads <number>

worker,

perchild,

mpmt_os2,

netware,

beos

이 지시어는 Idle 상태의 쓰레드들의 최대 개수를 설정한다. 피크 시에 많은 쓰레드가 생성되고 난 뒤, 이 지시어는 초과된 쓰레드들이 더이상 동작하지 않도록 한다. 이 값은 MinSpareServers 값 이상이어야 의미가 있다.

perchild MPM이 아닌 모든 MPM들에서, 이 값은 전 서버에 걸쳐 유휴 쓰레드의 최대 개수를 설정한다. perchild MPM의 경우에는, 이 값이 자식 프로세스 당 유휴 쓰레드의 개수를 설정한다. 만일 NumServers가 10이고 MaxSpareThreads가 10인 경우, 전체 유휴 쓰레드의 개수는 100이 된다.

참고사항

이 값은 perchildmpmt_os2 MPM에서 CFLAGS-DDEFAULT_MIN_SPARE_THREADS=10와 같이 설정하여 컴파일 시에 설정 가능하다. 이는 또한 윈도우 환경에서도 적용 가능한데, DEFAULT_MAX_FREE_DAEMON * DEFAULT_THREADS_PER_CHILD에 의해 설정된다.

Netware와 BeOS에서는 -DDEFAULT_MAX_FREE_THREADS=10를 사용하여 오버라이드 가능하다. 이 값의 기본 값은 NtWare에서 100이며, BeOS에서는 HARD_THREAD_LIMIT 의 값이 50으로 기본 설정되어 있다.

ThreadsPerChild <number>

worker,

winnt

프로세스 당 고정된 쓰레드 개수를 설정하는 프로세스 모델에서는, ThreadsPerChild가 아파치가 시작될 때 생성하는 쓰레드의 개수를 결정한다. 이에 대한 기본 값들은 다음과 같다.

  • worker: 25

  • winnt: 50

위 두 가지 MPM에서는, 이 값들을 어떻게 사용하는가에 따라 의미가 크게 달라진다. worker MPM에서는, 이 값이 얼마나 많은 동시 연결이, 또 다른 프로세스가 시작되기 전에 처리될 수 있는지를 결정한다. 프로세스의 개수는 동적이지만, 각 프로세스의 쓰레드 개수는 ThreadsPerChild로 설정된 개수로 고정된다. 유휴 쓰레드들의 최대 개수와 최소 개수는 이 숫자와 곱해져야 한다.

참고사항

worker MPM에서 이 값은 CFLAGS-DDEFAULT_THREADS_PER_CHILD=100와 같이 설정함으로써 컴파일 시에 설정될 수 있다. 이는 또한 Netware에서도 적용되는데, DEFAULT_START_THREADS의 기본 값으로 사용되어, StartThreads에 적용된다.

윈도우 환경에서는, ThreadsPerChild가 서버가 처리할 수 있는 전체 동시 연결 개수를 설정한다. 즉, 풀은 늘어나거나 줄어들지 않는다. 기본 값은 50이다.

ThreadsPerChild 50

윈도우 환경에서는, 자식 프로세스가 오직 하나이므로, 이 연결 개수에 대한 제한 값은 서버 전체로서 계산된다. 매우 Busy한 사이트에서는, 이 값은 최대 1024로 증가시킬 수 있다.

ThreadsPerChild 1024

윈도우 환경에서, 이 값은 또한 StartThreads 값이기도 하다. 이 값은 -DSTART_THREADS=1024와 같이 설정하여 컴파일 시에 설정할 수도 있다. 이 값은 HARD_THREAD_LIMIT 값을 초과할 수 없으며, HARD_THREAD_LIMIT는 1920으로 미리 설정되어 있다.

MaxThreadsPerChild <number>

perchild

쓰레드들의 동적 풀을 관리하는 프로세스 모델에서는, 이 지시어가 생성될 수 있는 최대의 쓰레드 개수를 결정한다. 현재 이 설정은 오직 perchild MPM에서만 적용 가능하다. 이 설정의 기본 값은 ThreadLimit 값과 동일하며, ThreadLimit 값을 초과할 수 없다.

MaxThreadsPerChild 10

ThreadLimit과는 달리, MaxThreadsPerChild는 변경될 수 있으며, 서버가 재시작될 때 새로운 값이 읽혀 적용될 수 있다. 그러므로, 이 값은 ThreadLimit 값보다 작게 설정될 수 있어서 나중에 아파치를 완전히 종료시키지 않고도 값을 올려서 적용할 수도 있다.

참고사항

이 값은 -DDEFAULT_MAX_THREADS_PER_CHILD=10 과 같이 컴파일 시에 설정될 수 있으며, MAX_THREAD_LIMIT 값을 초과할 수 없다. MAX_THREAD_LIMIT 값은 ThreadLimit 값의 가장 높은 가능한 값을 설정한다.

MaxRequestsPerChild <number>

beos

이 지시어는 단일 쓰레드의 최대 요청 처리 개수를 설정한다. 이는 beos MPM에서만 적용 가능하며, 위에서 언급된 MaxRequestsPerChild와 유사하다. 기본 값은 10000으로 다음과 같이 명시적으로 선언될 수 있다.

MaxRequestsPerChild 10000

참고사항

이 설정은 -DDEFAULT_MAX_REQUESTS_PER_THREAD=10000 와 같이 컴파일 시에 설정될 수 있으며, HARD_THREAD_LIMIT 값을 초과할 수 없다.


1.4. 네트워크 및 IP 관련 성능 지시어

HTTP는 TCP와 IP 네트워크 프로토콜 기반 위에 서 있다. 우리는 또한 운영체계의 네트워크 연결 처리 측면에서 제어할 수 있다. 아파치 2.0에서는 서로 다른 쓰레드들과 프로세스들이 네트워크 연결을 받아들이는 방식을 변경할 수도 있다.

Table 3. 네트워크 및 IP 관련 성능 지시어

지시어 설명
ListenBacklog <length>

클라이언트 연결 요청은, 아파치 프로세스 또는 쓰레드가 서비스할 수 있을 때까지 큐 내에 들어가게 된다. 이 큐의 최대 길이는 ListenBacklog 값에 의해 통제되며, 이의 기본 값은 511이다. 다음과 같이 변경할 수 있다.

ListenBacklog 1023

하지만, 이 값을 변경할 이유는 별로 없다. 만일 아파치가 요청 처리를 신속하게 수행하지 못하게 되어 이 큐가 넘치게 되는 상황이라면, 이 큐의 크기를 증가시키는 것보다는, 다른 곳에서 성능 개선을 하는 것이 보다 낫기 때문이다. 또한 많은 운영체계들은 이 값은 시스템 자체에서 제한시키고 있기도 하다.

이 값은 CFLAGS-DDEFAULT_LISTENBACKLOG=1023와 같이 설정하여 컴파일 시에 설정할 수 있다.

AcceptMutex <type>

웹서버 성능의 핵심 측면 중의 하나는 바로 네트워크 요청이 들어 왔을 때에, 대기하고 있던 프로세스나 쓰레드에 어떻게 할당해 주는가하는 방식이다. 어떤 경우에든, 아파치는 새로운 연결을 대기하고 있는 많은 쓰레드나 프로세스들을 가지고 있을 것이다. 이 경우 아파치는 서로 다른 쓰레드들이나 프로세스들이 어떻게 조정하여 연결을 취할 것인가하는 메커니즘을 가지고 있어야 한다. 이 메커니즘은 바로 상호 배타적 락킹 (Mutually Exclusively Lock), 즉 mutex에 의해 결정된다.

아파치는 네 가지의 서로 다른 mutex를 지원한다. 아파치 1.3에서는, 어떤 타입이 사용될지가 컴파일 시에 결정되고 변경될 수 없었다. CFLAGS 변수를 사용하여 오버라이드할 수만 있었다. 아파치 2.0에서는, 컴파일 시 뿐만 아니라, 서버 수준에서 설정 파일 내의 AcceptMutex 지시어를 사용하여 설정을 변경할 수 있다. 가용한 락킹 타입들은 플랫폼에 따라 다르긴 하지만, 아래 표에 나열된 것들 중 한 개 이상을 포함한다.

Table 4. AcceptMutex 지시어 설정 옵션

AcceptMutex CFLAGS 의미
flock -DUSE_FLOCK_SERIALIZED_ACCEPT flock 시스템 호출을 사용하며, LockFile에 정의된 락 파일을 사용
fcntl -DUSE_FCNTL_SERIALIZED_ACCEPT fcntl 시스템 호출을 사용하며, LockFile에 정의된 락 파일을 사용
sysvsem -DUSE_SYSVSEM_SERIALIZED_ACCEPT System V 세마포어 사용
posixsem -DUSE_POSIXSEM_SERIALIZED_ACCEPT POSIX 호환 세마포어 사용
pthread -DUSE_PTHREAD_SERIALIZED_ACCEPT POSIX 쓰레드 mutex 락을 사용


1.5. HTTP 관련 성능 지시어

서버 풀을 제어하는 것과 더불어, 아파치는 TCP/IP와 HTTP 프로토콜 수준에서 성능 관련 이슈들을 제어할 수 있는 지시어들을 제공한다. 이들은 모두 플랫폼 독립적이기 때문에, 아파치가 동작하는 플랫폼에 관계 없이 사용 가능하다.

Table 5. HTTP 관련 성능 지시어

지시어 설명
KeepAlive <on|off>

영속 연결은 클라이언트가 하나의 동일한 연결을 사용하여 여러 요청을 보낼 수 있도록 허용한다. 이러한 아이디어에 기초한 것이 바로 HTTP/1.1이다. 클라이언트가 서버에 대한 연결을 계속 유지할 수 있게 허용함으로써 기존 클라이언트의 성능을 향상시킬 수 있다. 하지만 서버가 매우 바쁜 경우 다른 새로운 클라이언트를 서비스할 수 없는 상황이 발생할 수 있다.

기본 설정은 다음과 같다.

KeepAlive on

KeepAlive는 클라이언트와 서버 간에 매우 빠른 대화를 제공하는 것으로서, 클라이언트가 접속을 끊기 전까지 해당 서버 프로세스가 다른 요청을 처리할 수 없다는 비용이 초래된다. 이 문제를 해결하기 위해, 아파치는 두 가지 추가 지시어를 제공하여 영속적인 연결에 대한 생명주기를 관리할 수 있도록 한다.

KeepAliveTimeout <seconds>

이 지시어는 아파치 프로세스 (또는 쓰레드) 가 클라이언트가 또다른 HTTP 요청을 보내기 전까지 대기해야 하는 시간을 설정한다. 이 시간이 경과하기까지 새로운 요청을 보내지 않으면, 서버는 연결을 끊게 된다. 이 값은 상대적으로 작은 값으로 설정되어야 하며, 기본 값으로 15초로 설정되어 있다.

KeepAliveTimeout 15

이 값을 컴파일 시에 설정하고 싶다면, CFLAGS-DDEFAULT_KEEPALIVE_TIMEOUT=15 과 같이 설정하면 된다.

MaxKeepAliveRequests <number>

타임 아웃 값과는 관계 없이, 영속 연결은 또한 MaxKeepAliveRequests로 설정된 요청 개수에 도달하게 되면 자동으로 연결을 끊게 된다. 서버 성능을 관리하기 위해서, 이 값은 높은 값으로 유지해야 하며, 기본 값은 100으로 설정되어 있다.

MaxKeepAliveRequests 100

이 값을 0으로 설정하게 되면, 타임 아웃 시간을 초과하지 않고 클라이언트가 연결을 끊지 않는 이상, 이 영속 연결은 영원히 활성화된 상태로 남아 있게 된다. 이는 약간 위험하다. 왜냐하면 이는 서버가 서비스 거부 (Denial of Service; DOS) 공격으로부터 무력하게 되기 때문이다. 따라서, 이 값은 높게 설정하되, 유한한 값으로 설정하는 것이 바람직하다.

이 값을 컴파일 시에 설정하려면, CFLAGS 변수에 -DDEFAULT_KEEPALIVE=100와 같이 설정하면 된다.

TimeOut <seconds>

이 지시어는 비활성화된 연결에 대하여 얼마나 오랫동안 HTTP 연결을 유지하고 있을지를 설정하며, 다음과 같은 기준에 의해 결정된다.

  • 연결이 확립되고 GET 요청이 접수되기 전까지의 시간. 이는 KeepAliveTimeout가 사용되는 영속 연결에는 영향을 끼치지 않는다.

  • PUT 또는 POST 요청에서 데이터의 마지막 패킷이 도착한 이후의 시간

  • 서버가 더 기다릴 게 있는 경우에, 마지막 ACK가 수신된 이후의 시간

TimeOut의 기본 값은 5분으로 설정되어 있으며, 다음과 동일하다.

TimeOut 300

이 값을 컴파일 시에 설정하고 싶다면, CFLAGS 변수에 -DDEFAULT_TIMEOUT=60와 같이 추가하면 된다. 참고로 ProxyTimeOut 지시어는 TimeOut 값을 오버라이드한다.


1.6. HTTP 제한 지시어

위에서 언급된 프로토콜 관련 지시어들과 더불어, 아파치는 클라이언트에 의해 요청되는 HTTP 요청의 크기를 제한할 수 있는 네 가지의 지시어들을 제공한다. 이들은 기본적으로 클라이언트들이 서버의 자원들을 소진시켜 DOS 문제들이 발생하지 않도록 하며, 그러므로 서버 보안에도 관련된다.

Table 6. 네트워크 및 IP 관련 성능 지시어

지시어 설명
LimitRequestBody <bytes>

이 지시어는 HTTP 요청의 Body 크기 (PUT 또는 POST 메소드에 의해 전송) 를 제한한다. 기본 값은 0으로 설정되며, 이는 아무런 것도 제한하지 않게 된다. 최대 값은 2147483647 바이트, 즉 2 GB 이다. 클라이언트가 Body 제한 값을 초과하여 데이터를 보내게 되면, 서버는 그 요청을 서비스하지 않고 오류 메시지를 전달하게 된다.

LimitXMLRequestBody <bytes>

아파치 내에서 XML 처리를 유발하는 HTTP 요청들 (즉, 내부의 Expat 파서에 전달되는 요청들) 은 이 지시어를 통해 별도로 크기를 제한할 수 있다. 이에는 mod_dav에 의해 처리되는 WebDAV 요청이 포함된다. LimitRequestBody와 마찬가지로 LimitXMLRequestBody는 제한 바이트 크기를 설정한다. 예를 들어, 500 KB 제한 크기는 다음과 같이 설정된다.

LimitXMLRequestBody 512000

이의 기본 값은 1000000 바이트이다. 참고로 LimitRequestBody의 기본 값은 원래 무제한(0)이었던 것과 차이가 있다는 점이 주목할 만하다.

LimitRequestFields <number>

이 지시어는 클라이언트가 HTTP 요청에 포함할 수 있는 헤더의 개수를 제한하며, 기본 값은 100이다. 실제 환경에서 클라이언트가 보내는 헤더의 개수는 대략 20개 내외이다. 이 값을 50개로 줄이려면 다음과 같이 설정할 수 있다.

LimitRequestFields 50

이 값을 컴파일 시에 적용하고 싶다면, CFLAGS 변수에 -DDEFAULT_LIMIT_REQUEST_FIELDS=50와 같이 설정하면 된다.

LimitRequestFieldSize <length>

이 값은 클라이언트에 의해 전송되는 개별 HTTP 헤더의 크기를 제한하며, 이 크기에는 헤더의 이름의 크기까지 포함된다. 이의 기본 값은 동시에 최대값이기도 한데, 8190 바이트이다. 이 값을 만일 100으로 바꾸고 싶다면 다음과 같이 설정한다.

LimitRequestFieldSize 100

이 값을 컴파일 시에 설정하고 싶다면 CFLAGS-DDEFAULT_LIMIT_REQUEST_FIELDSIZE=100와 같이 설정하면 된다.

LimitRequestLine <bytes>

이 값은 HTTP 요청 자체의 최대 길이를 제한하며, HTTP 메소드, URL, 그리고 프로토콜까지를 모두 포함한다. 기본 제한 값은 8190 바이트이다. 만일 이 값을 500 바이트로 줄이고 싶다면 다음과 같이 설정한다.

LimitRequestLine 500

이 지시어의 효과는 클라이언트가 전송할 수 있는 URL의 크기를 제한하는 것이다. 그래서 서버에 오직 유효한 URL만을 클라이언트가 요청할 수 있도록 한다. 이에는 GET 요청에서의 QUERY_STRING도 포함한다. 이 값을 너무 작게 설정하게 되면, 클라이언트가 GET 메소드에 의한 HTML 폼 전송을 막을 수도 있다.

이 값을 컴파일 시에 적용하고 싶다면, CFLAGS 변수에 -DDEFAULT_LIMIT_REQUEST_LINE=500 와 같이 설정하면 된다.


2. 보다 나은 성능을 위한 아파치 서버 설정

아파치의 많은 설정들은 적절한 처리 비용이 고려되지 않으면 성능에 큰 영향을 끼칠 수 있다. 어떤 것들은 주의하지 않으면 성능을 떨어뜨릴 수도 있고, 어떤 것들은 특정 환경에서 성능을 향상시킬 수도 있다.

2.1. 성능에 영향을 끼치는 지시어들

어떻게 사용되는가에 따라 성능을 향상시키거나 떨어뜨릴 수 있는 많은 지시어들이 존재한다. 어떤 것들은 명확하기도 하지만, 또 어떤 것들은 덜 명확한 것들이 있다.

2.1.1. DNS 및 호스트명 Lookup

DNS를 사용하는 것은 어떤 것이든 아파치 성능에 영향을 준다. 특히 다음 두 가지 지시어의 사용은 가능한 한 피해야 한다.

Table 7. DNS 및 호스트명 Lookup

지시어 설명
HostNameLookups <on|off|double> 이는 아파치가 IP 주소 대신 호스트 이름을 로그에 정보로 기록하도록 한다. 하지만, 이 과정은, 아파치가 성능을 위해 DNS 캐시를 사용함에도 불구하고, 시간이 많이 소요되는 작업이다. Analog와 같은 로그 분석기는 나중에 통계를 산출할 때에 자체적인 DNS 이름을 찾을 수 있는 기능이 있으므로, 그러한 도구에 의존하는 것이 바람직하다.
UseCanonicalName <on|off|dns>

이는 아파치가, ServerNamePort 지시어를 사용하지 않고, 자신의 IP 주소로부터 서버 이름을 추출하게끔 설정할 수 있다. 이 옵션은 mod_vhost_alias 모듈을 사용하여 대규모의 가상 호스트를 운영하는 경우에 유용할 수 있다. mod_vhost_alias 모듈은 아파치에 의해 서비스되는 호스트 이름들에 대해서만 캐시를 하는 것이지, 전체 인터넷에 대해서 캐시를 제공하지는 않기 때문에, HostNameLookups 옵션은 사용할 필요가 거의 없으며, 가능한 피해야 한다.

또한, 전체로서 또는 부분으로서 호스트 이름을 사용하게 되면, 호스트 이름을 통해 IP 주소를 찾는다든가, IP 주소를 통해 호스트 이름을 찾는다든가 하는 식의 DNS Lookup이 발생할 수 있다. 또한 이 옵션은 mod_access 모듈의 allowdeny 지시어에 영향을 미치며, mod_proxy 모듈의 ProxyBlock, NoProxy, NoCache 지시어 등에도 영향을 끼치게 된다.


2.1.2. 심벌릭 링크와 권한 검사

아파치는 FollowSymLinks 옵션에 따라 심벌릭 링크를 따라가서 서비스를 할지 아니면 서비스를 거부할지 설정될 수 있다. 이 옵션이 켜 있지 않으면, 아파치는 파일을 읽거나 CGI 스크립트를 실행할 때마다, 상위 디렉터리 또는 해당 파일이 심벌릭 링크인지 확인하기 위하여, 루트 디렉터리로부터 하위 디렉터리까지 이르는 전체 경로를 검사하는 데에 별도의 시간을 소비하게 된다.

한편, 만일 심벌릭 링크가 SymLinksIfOwnerMatch에 의해 활성화된 경우에, 아파치는 오직 그 링크의 소유자가 서버 (또는 가상 호스트) 의 소유자와 같은 소유자인 경우에만 링크를 따라가게 된다. 이러한 경우, 아파치는 전체 경로를 검사함은 물론, 해당 링크의 소유자까지 검사하게 된다.

성능을 최대화하려면, 항상 FollowSymLinks 를 사용하고, SymLinksIfOwnerMatch를 사용하지 마라.

Options FollowSymLinks

하지만, 이 옵션들은 보안을 향상시키기 위해 존재하는 것들이다. 이 전략은 시스템 관리자가 성능보다는 보안이 더 걱정될 때 사용할 수 있다.

mod_autoindex 는 이 옵션을 준수하게 됨에 유의하기 바란다.

2.1.3. 동적 컨텐트의 캐시

일반적으로 아파치는 동적으로 생성되는 컨텐트들에 대해서는 프록시에게 문서를 캐시하라는 정보를 제공하지 않는다. mod_expires 모듈을 사용하게 되면, 문서에 대하여 만료 시간을 강제로 설정할 수 있으며, 그 시간이 매우 짧다 하더라도 서버의 부하를 줄일 수 있다.

ExpiresByType text/html 600

이 지시어는 주식 시황 페이지와 같이 매 십 분마다 갱신해야 하는 페이지들을 제공할 때에 적절할 수 있다. 해당 페이지가 매우 짧은 시간 안에 만료되어야 할지라도 만일 페이지가 빈번하게 접근되는 경우에는 클라이언트가 페이지에 직접 접근하지 않고 프록시를 대신 사용하게 할 수 있다.

그렇다 할지라도, 어떤 프록시들은 그들 스스로 동적 컨텐트라고 생각하는 것의 캐시를 아예 방지하는 경우도 있다. 그들을 속이고 싶다면, 다음과 같이 CGI 스크립트들을 보통의 HTML 페이지로 여기게 할 수 있다.

RewriteEngine on
RewriteRule ^(/pathtocgi/.*)\.html$ $1.cgi [T=application/x-httpd-cgi]

2.1.4. Negotiated Content의 캐시

HTTP/1.1 클라이언트들은 Content Negotiation을 통해 어떻게, 그리고 언제 이미 캐시된 문서들을 받을 수 있는가에 대한 정보를 알고 있다. 하지만 HTTP/1.0 프록시의 경우에는 이러한 정보를 알지 못하므로, 우리는 다음과 같이 그러한 경우에도 적용시킬 수 있다.

CacheNegotiateDocs

이 옵션은, 만일 우리가 다국어 지원 사이트를 사용하는 경우에는 예기치 않은 사이드 이펙트를 가져올 수 있는데, 클라이언트가 잘못된 페이지를 전송 받을 수 있기 때문이다. 따라서, 주의해서 사용해야 한다. HTTP/1.0 클라이언트의 수는 사실 매우 적을 뿐 아니라 계속 줄어들고 있기 때문에, 이 옵션 자체를 아예 무시할 수도 있다.

또한 Content Negotiation에서 고려해야 할 사항은 확장자가 없는 디렉터리 인덱스를 설정한 경우이다. 이런 경우에는, 설사 그런 파일을 사용하지 않는다 하더라도, 확장자가 포함된 모든 인덱스 파일들을 나열하는 것이 바람직하다.

DirectoryIndex index.html index.htm index.shtml index.cgi

만일 DirectoryIndex index 라고만 설정하게 되면, 아파치 서버는 MultiViews 옵션이 켜져 있을 때 다른 여러 인덱스 파일들을 사용할 수 있기 때문이다.

2.1.5. 로깅

디스크와 CPU 시간을 가장 많이 사용하는 것 중의 하나는 바로 로깅일 것이다. 만일 성능을 최대로 하고 싶다면 로그를 아무 것도 남기지 않으면 된다. 하지만 이는 좋은 방법이 아닌데, 오류 정보들까지도 남기지 않기 때문이다. 우리는 Access 로그를 남기지 않도록 설정할 수 있으며, 다음과 같이 로그 수준을 최소로 설정할 수도 있다.

LogLevel error

또 다른 접근 방법으로는 로그를 다른 서버에 남기도록 하는 것이 있을 수 있다. 웹 서버에서 접근 가능한 NFS 디렉터리나 시스템 로그 데몬을 통해 그렇게 할 수 있는데, 후자가 더 바람직하다. NFS는 성능이 좋지 않다고 알려져 있다. 또한 마운트된 디렉터리는 웹 서버의 모든 사용자들이 잠재적으로 접근할 수 있어서 보안 위험성이 있다.

2.1.6. 세션 추적 기능

어떤 종류의 세션 추적 기능이든 모두 시간을 소비하게 된다. 먼저, 아파치는 쿠키와 URL 요소들을 검사하고, 없어진 경우에는 설정을 해야 하며, 두번째로, 그런 유용한 추적 기능에 대해 어딘가에 로그를 남기게 되므로, 아파치에 추가적인 작업을 만드는 것이다.

중요한 것은, 절대 필요하지 않은 경우에는, mod_usertrack 모듈이나 mod_session 모듈을 아예 사용하지 않는 것이다. 사용한다 하더라도 Directory, Location, 또는 Files 과 같은 지시어들 내에 한정적으로 사용하는 것이다.

2.1.7. .htaccess 파일

AllowOverrideNone으로 설정되지 않은 경우에는, 아파치가 .htaccess 파일을 검사하게 된다. 아파치는 요청된 자원이 존재하는 각각의 모든 디렉터리들 (루트 디렉터리로부터 계층적 하위 디렉터리들 모두) 내에서 .htaccess 파일을 찾아 검사하게 된다. 이는 매우 시간을 소요하게 되는 것이므로, 다음과 같이 아파치가 URL 요청 시마다 매번 이 검사를 하지 않도록 하게 할 수 있다.

# AllowOverride is directory scope only, so we use the root directory
<Directory />
  AllowOverride None
</Directory>

.htaccess에 의한 설정은 서버를 보다 안전하게 보호할 수 있는 효과를 가져 올 수도 있다. 특정 위치에서 옵션을 오버라이드해야 하는 경우라 할지라도, 서버 수준의 설정에서 지시어를 사용하여 유사한 효과를 거둘 수 있다. 특정 디렉터리에서만 오버라이드하여 설정함으로써, 전체 경로들에 대해 검사하는 오버헤드를 막을 수 있다.

2.1.8. Extended Status

mod_status 모듈은 ExtendedStatus 지시어가 on 으로 설정된 경우, 확장된 상태 페이지를 생성해 준다. 하지만, 이는 아파치가 운영 체계 시스템에게 모든 클라이언트 요청 각각에 대해 시간 정보를 호출하도록 하기 때문에 성능을 저해할 수 있다.

시간 호출은 어떤 플랫폼에서든 매우 비싼 시스템 호출 중의 하나라고 볼 수 있다. 따라서 성능을 저해할 수 있으며, 가상 호스트 수준이 아니라, 서버 수준에 설정된 경우에 더욱 그러하다. 이 옵션을 활성화하지 않는 것이 바람직하다.

2.1.9. URL 재작성

mod_rewrite 모듈의 URL 재작성 기능을 사용하게 되면, 성능을 떨어뜨리게 된다. 특히 복잡한 재작성 규칙을 사용하는 경우에 더욱 그러하다. RewriteEngine 지시어는 디렉터리 별 또는 가상 호소트 별로 사용될 수 있어서, 규칙이 복잡하고 특정한 경우에만 적용해야 하는 경우 선택적으로 적용 가능하다.

또한, 어떤 규칙들은 내부적으로 HTTP 요청을 서버에 호출하도록 되어 있기 때문에 성능을 보다 더 떨어뜨릴 수 있다. [NS] 플래그 사용에 주의를 기울이고, -F-U 옵션을 사용하는 데에 주의를 기울여야 한다.

2.1.10. 사이즈가 큰 설정 파일

사이즈가 큰 설정 파일은 그 자체로 아파치를 굼뜨게 만들 수 있다. mod_rewrite 모듈과 같은 것들은 설정 파일의 라인 개수를 줄임으로써 성능 상의 효과를 볼 수 있다. mod_vhost_alias 모듈은 대규모의 가상 호스트들을 관리할 때에 유용하다.

2.1.11. CGI 성능 튜닝

CGI 스크립트로 사용되는 스크립트나 애플리케이션은 미리 성능을 고려하여 작성되어야 한다. 너무 많은 메모리나 CPU 시간을 사용하지 않아야 하며, 가능한 한 빨리 출력을 생성해야 하고, 가능한 한 결과를 캐시할 수 있어야 한다.

아울러 아파치는 다음과 같은 CGI 관련 지시어들을 제공한다.

  • RLimitCPU: CPU 시간을 얼마나 사용할 수 있는지를 제어

  • RLimitMEM: 메모리를 얼마나 할당할 수 있는지를 제어

  • RLimitNPROC: 얼마나 많은 CGI 인스턴스들이 동시에 수행될 수 있는지를 제어

아파치 2.0 서버에서는, mod_cgid 모듈에 의해 확립되는 CGI 데몬에 의해 생성되는 Listen 큐의 크기를, 컴파일 시에 CFLAGS 변수를 -DDEFAULT_CGID_LISTENBACKLOG=<queue size> 와 같이 제공함으로써 설정 가능하다. 기본 크기는 100이다.

2.2. 기타 성능 튜닝 지시어들

표준 아파치 실행 모듈의 기능은 아니지만, 아파치 및 써드파티 모듈들에는 몇 가지 서버 성능을 다양한 방식으로 향상시킬 수 있는 방법들이 포함되어 있다. 여기서는 mod_file_cache (예전에는 mod_mmap_static) 모듈과 mod_bandwidth 모듈을 다루기로 한다.

2.2.1. 캐싱 및 메모리맵

오늘날 웹 서버들이 제공하는 컨텐트들은 대부분 동적 컨텐트들인 경우가 많다. 그럼에도 불구하고, 여전히 정적인 컨텐트들에 대해 캐시를 사용하는 것은 성능을 매우 향상시킨다. 정적 컨텐트들을 캐시로부터 서비스하게 되면, 해당 컨텐트를 디스크로부터 두번 이상 읽을 필요가 없게 된다. 이는 해당 파일을 읽는 것만을 넘기는 것 뿐만 아니라, 디스크에 있는 파일들에 대한 다른 여러 가지 검사 작업이나 위치를 찾아내는 작업들이 그대로 넘겨질 수 있게 되는 것이다.

좀 더 생각해 보면, 동적인 웹 사이트에서 정적 구성 요소들을 여러 개의 분리된 파일들로 나눔으로써, 성능을 최적화 하는 것도 가능하다.

예를 들면, 웹 사이트의 상단 및 좌측 탐색 영역은 클라이언트 사이드에서 자바 스크립트를 사용하여 컨텐트를 변경할 수 있도록 변경할 수 있다. 실제 자바 스크립트는 정적일 것이다. mod_include모듈을 사용하는 기법을 쓰면, 이러한 정적 구성요소들과 다른 동적 구성요소들을 사용하여, 동적 컨텐트를 생성하는 효율성을 높일 수 있다. 대부분의 템플릿 기반 웹 서버 프레임워크들은 대부분이 정적 컨텐트인 프리젠테이션을 실제 동적인 컨텐트와 분리할 수 있게 함으로써, 이러한 목표를 달성하는 데에 도움을 주곤 한다.

우리는 정적 컨텐트들을 보다 효율적으로 다룰 수 있는 다음과 같은 네 가지 접근 방법을 가지고 있다.

  • 메모리 맵 파일을 공유 메모리에서 영속적인 상태로 두는 방법

    파일을 공유 메모리에 두게 되면, 모든 아파치 프로세스들로부터 접근 가능하다. 하지만, 프로세스에서 시스템 메모리를 소모하게 된다. 메모리와 처리 능력 간에 트레이드 오프를 해야 한다. 물리 메모리가 풍부한 웹 서버에서는 좋은 아이디어가 될 수 있다. 하지만 가상 메모리를 사용해야 하는 웹 서버에서는 물리 메모리가 충분하지 않기 때문에 이 방법을 사용하는 것은 좋지 않다. 이 기능은 mod_mmap_static (아파치 1.3) 모듈이나 mod_file_cache (아파치 2.0) 모듈의 MMapFile 지시어를 사용하여 설정하면 된다.

  • 파일 핸들을 하나 열고 유지하고, 그 파일이 디스크에서 영속적으로 사용 가능하도록 하는 방법

    이 방법은 파일을 메모리에 유지하지 않고, 파일 핸들이 열려진 상태로 유지하여, 즉각적으로 읽혀지고 클라이언트에 전달할 수 있는 방법으로 메모리 매핑의 한 변형이다. 아파치는 그 스스로 파일을 보내지는 않고, 파일 핸들을 운영체계에 전달하게 되면, 운영 체계는 해당 파일을 최적화된 방법으로 전송하게 된다. 대부분의 현대적 유닉스 시스템들에서는, sendfile 시스템 호출에 의해 가능하다. 캐시된 파일에 대해 파일 핸들이 하나씩 필요한 방법이다. 이는 커널 상에서 단일 프로세스 당 가용한 파일 핸들의 개수로 인해 문제가 발생할 수 있다. (커널의 제한 값은 변경될 수 있긴 하지만, 무한대로 증가시킬 수는 없다.)

  • 아파치 앞에 프록시를 두고, HTTP/1.1 캐시 헤더를 사용하여 캐시하도록 알려 주는 방법

    만일 정적 컨텐트의 전체 페이지들을 전송해야 하는 경우라면, 우리는 단지 mod_expiresmod_header 모듈을 사용하여 프록시가 해당 문서를 대신 캐시해 주도록 알려 줄 수 있다. 외부 프록시 서버가 없는 경우에는, 아파치를 하나 더 추가하여 프록시로 설정할 수 있다. Squid와 같은 전용 프록시 서버도 또한 사용될 수 있다. 하지만, 서버에서 부분적으로 정적 페이지들을 제공해야 하는 경우에는 이러한 옵션은 효과를 발휘할 수 없다.

  • 아파치를 정적 컨텐트를 효율적으로 서비스할 수 있는 다른 웹 서버와 함께 운영하는 방법

    약간 당황스럽게 들릴 수도 있겠지만, 어떤 상황에서는 아파치보다 성능이 좋은 다른 웹 서버가 존재할 수 있다. 아파치의 강력한 특징은 잘 모듈화되어 있다는 점과 동적 컨텐트들을 처리하는 효율성이다. 순수하게 정적인 컨텐트들을 위해서라면, 그런 목적의 효율성 높은 웹 서버들이 존재하는데, 다음과 같은 것들이다.

위와 같은 서버들을 프록시로서 아파치 서버 앞에 두거나 아파치와 정적 컨텐트들을 위한 웹 서버를 스위칭하는 로드밸런스 기능의 노드를 앞에 두거나할 수 있다. 이를 위해서는 정적 컨텐트와 동적 컨텐트를 URL로부터 판단할 수 있는 신뢰성 있는 방법이 필요하다. 가장 쉽게 구별할 수 있는 유형의 컨텐트는 이미지 파일들이다. 이들은 파일 확장자 이름에 따라 쉽게 구별된다. 만약 가능하다면, 두번째 웹서버를 별도의 물리적 하드웨어 박스에 두는 것도 보다 좋은 방법이 될 수 있다.

2.2.1.1. 메모리맵 파일

메모리 맵 파일은 MMapFile 지시어에 의해 설정되며, 이는 아파치 1.3에서는 mod_mmap_static 모듈에 의해, 아파치 2.0에서는 mod_file_cache 모듈에 의해 제공되는데, 어느 경우든 동일한 방식으로 적용 가능하다. 두 모듈은 각 아파치 버전에서 표준적으로 제공되긴 하나, 기본 옵션으로 함께 컴파일되어 활성화되지는 않는다. 주의할 점은 mod_file_cache 모듈은 아파치 2.0의 mod_cache 계열 모듈들과는 아무런 관련이 없다는 것이다. 아파치 2.0의 mod_cache 계열 모듈들은 아파치 2.0의 프록시 캐시 기능을 구현한 것들이다. 이에 대해서는 뒷 부분에 다루게 된다.

메모리 맵 파일들은 운영체계가 파일을 영속적으로 메모리에 저장할 수 있도록 제공하는 캐시 메커니즘이다. 메모리에 파일들을 저장함으로써, 아파치는 디스크로부터 읽어들이지 않고도, 그 파일들을 클라이언트에 신속하게 전달할 수 있다. 일례로, 인덱스 페이지와 배너 로고를 메모리 맵 파일에 두려면 다음과 같이 설정할 수 있다.

MMapFile /home/www/alpha-complex/index.html /home/www/alpha-complex/banner.gif

위 설정은 오직 정적 파일에 대해서만 캐시 처리를 하게 된다. 동적으로 생성되는 컨텐트나 CGI 스크립트들은 MMapFile 지시어로 설정할 수 없다. CGI 스크립트를 캐시하려면, FastCGI 모듈이나 mod_perl과 펄 스크립트를 위한 Apache::Registry를 사용해야 한다.

MMapFile은 문법 측면에서 융통성이 크지 않으며, 와일드카드들을 지원하지 않는다. MMapDirectory와 같은 형태의 지시어는 지원되지 않아 한번에 여러 파일들을 설정할 수 없다. 만일 전체 디렉터리들을 메모리 맵에 넣으려면, 파일들의 목록을 생성하여, 여러 라인의 MMapFile 지시어를 포함시켜야 한다. 다음은 그 예이다.

$ ls -1 /home/www/alpha-complex/*.html | perl -ne 'print "MMapFile $_"' > /usr/local/apache/conf/ac-cache.conf

위의 명령은 서버 루트 아래에 있는 모든 HTML 문서들을 캐시하게끔 유도한다. 위에서 생성된 파일을 Include 지시어를 사용하여 아파치 설정 파일에서 포함하면 된다. mod_file_cache 모듈을 사용한 좀 더 다른 솔루션은 해당 모듈의 매뉴얼 페이지를 참조하기 바란다.

주목해야 할 점은, 파일이 한번 매핑되고 나면, 그 파일이 변경된다 할지라도 그 파일은 다시 디스크로부터 읽혀지지 않는다는 것이다. 만약 특정 파일의 변경 상태를 반영 시키려면, 물리적으로 해당 파일을 삭제하고, 별도의 새 파일로 대체하거나 아파치를 재시작해야 한다. 이렇게 하지 않으면, 클라이언트 예전 컨텐트를 서비스 받는 문제가 발생하게 된다.

2.2.1.2. 캐시 파일

캐시 파일들은 오직 아파치 2.0의 mod_file_cache 모듈을 통해서만 사용 가능하다. 파일을 메모리에 캐시하는 대신에, 파일 핸들을 열려진 상태로 서버가 계속 유지하는 방식이다. 파일이 요청되면, 해당 핸들은 운영체계에 전달되고, sendfile 시스템 호출에 의해 전송된다. 이는 일반적으로 파일을 전송하는 데에 매우 빠른 방법인데, 이는 운영체계가, 사용자 공간이 아닌, 커널 수준 오퍼레이션으로서 복사를 수행하기 때문이다.

이 방식에서 '캐시된 파일'이라는 용어는 이런 관점에서 잘못된 용어일 수 있다. 즉, 사실 캐시된 것은 파일이 아니라, 파일에 대한 핸들을 캐시하는 것이다. 물론 만일 해당 파일이 최근에 읽혀졌고, 여전히 하드웨어나 파일 시스템에 의해 버퍼링되어 있는 경우도 있긴 하다. sendfile의 장점은, 이러한 모든 과정이 자동으로 처리되어 상세한 내용을 우리가 신경쓸 필요가 없다는 것이다.

파일을 캐시하기 위해서는 CacheFile 지시어를 사용해야 한다. MMapFile와 거의 유사한 방식으로 설정하지만, 그 결과는 다르다. 캐시하려고 하는 각 파일들에 대하여, 아파치는 읽기 전용 파일 핸들을 열어 유지하게 된다. 그리고 그 파일 요청이 왔을 때에, 그 파일 핸들을 운영체계에 전달한다. 다음은 설정 예제이다.

CacheFile /home/www/alpha-complex/index.html /home/www/alpha-complex/banner.gif

메모리 맵 파일 사용 예와 같이 이 방식도 동일한 제약사항들을 가지고 있다. 즉 파일의 변경 사항이 발생했을 때, 아파치가 그것을 알도록 할 수 없다는 점이다. 대신에, 우리는 그 파일을 삭제하고 다른 파일로 대체하거나, 아파치를 재시작함으로써 그런 목적을 달성할 수 있다. 이런 제약사항이 존재하는 이유는, 파일의 변경 상태를 검사하기 위해서는 파일 시스템 접근이 필요하게 되고, 이렇게 되면 애초의 캐시 목적과는 상충되기 때문이다.

2.2.2. 대역폭의 제한

mod_bandwidth 모듈은 아파치 미러 사이트에서 구할 수 있다. 또한 현재 버전은 http://www.cohprog.com/ 사이트에서 구할 수 있다. 이 모듈은 아파치 1.3 유닉스 서버에서 적용 가능하며, 아파치 2.0은 앞으로 지원될 예정이다. 이 모듈은 아파치가 원격 클라이언트의 도메인 주소 또는 IP 주소에 따라 1초 당 전송받는 데이터의 양이나 요청된 파일의 크기를 제한할 수 있게 한다.

또한 접속되는 클라이언트의 수에 따라 나누어 대역폭 제한을 설정할 수도 있다. 이는 이론적으로 모든 클라이언트들을 서비스할 수 있을 정도로 충분한 대역폭을 가지고 있지 않은 상황에서 클라이언트 연결들을 관리할 수 있는 수단을 제공하기도 한다. 대역폭의 제한은 디렉터리 별 또는 가상 호스트 별, 또는 이 두 가지를 혼용하여 설정할 수 있다. 예를 들어, 웹 사이트에서 안전성을 고려할 필요가 없는 부분들에 대해서는 대역폭을 제한 설정하고, 안전성이 고려되어야 하는 온라인 주문 시스템은 모든 대역폭을 항상 활용할 수 있도록 설정할 수 있다.

mod_bandwidth 모듈은 표준 배포 모듈에서 활성화되지 않으므로, 서버를 재빌드하거나, 별도 모듈을 배치함으로써 사용 가능하다. 설치되면, 대역폭 제한은 다음과 같이 활성화된다.

BandWidthModule on

가상 호스트 컨테이너들은 이 지시어를 상속받지 않는다. 반면 디렉터리 컨테이너들은 이 지시어를 상속받는다. 각 가상 호스트에 대역폭 제한을 설정하려면, 각 가상 호스트 컨테이너에 대역폭 제한을 따로 설정해야 한다.

대역폭의 결정은 단 한번 정확하게 수행되기 때문에, 다른 모듈들이 먼저 요청을 처리할 기회를 갖지 않도록 mod_bandwidth 모듈이 다른 모든 모듈들 보다 먼저 로드되어야 한다.

아파치 1.3에서는, mod_bandwidth 모듈에 대한 LoadModuleAddModule 지시어가 다른 것들 보다 먼저 나와야 한다는 것을 의미한다. configure 를 사용한다면, --permute-module=BEGIN:bandwidth가 추가되면 된다. 그렇게 하지 않은 경우는 httpd.conf 파일을 편집해야 한다.

TODO: 아파치 2.0에서 사용 가능하게 되면 아래 상세 내역 기술할 것.

2.2.2.1. 디렉터리 설정

mod_bandwidth 모듈이 동작하려면, 파일 시스템의 특정 공간에 임시 파일들을 저장할 수 있도록 설정해야 한다. 기본적으로 이 디렉터리는 /tmp/apachebw로 설정된다. 하지만, 이는 설정 파일에서 BandWidthDataDir 지시어를 통해 다음과 같이 변경할 수 있다.

BandWidthDataDir /var/run/apache/bandwidth

이 디렉터리는 미리 생성되어 있어야 하며 아파치에 의해 작성 권한이 있어야 한다. 또한 master 디렉터리와 link 디렉터리가 이 디렉터리 내에 하위 디렉터리로 생성되어 있어야 한다. 만일 link 디렉터리 내에 죽은 링크인 경우에는 (즉, 링크 대상이 옮겨졌거나 삭제된 컨텐트인 경우에는), 아파치가 재시작될 때, 이 모듈이 정상 동작하지 않을 수 있다. mod_bandwidth 모듈과 함께 제공되는 cleanlink.pl 스크립트는 이를 자동으로 처리해 주는데, apachectl 스크립트를 수정하여 이 스크립트를 호출하도록 하면 좋다.

2.2.2.2. 클라이언트에 따른 대역폭 제한

대역폭 제한 설정은 다음과 같이 설정될 수 있다.

<Directory />
  BandWidth localhost 0
  BandWidth friendly.com 4096
  BandWidth 192.168 512
  BandWidth all 2048
</Directory>
                                        

위 설정은, 값을 0으로 설정함으로써, 아파치가 로컬 요청들을 제한하지 않도록 한다. (CGI 스크립트에 의한 내부 호출 등과 같은 경우.) 그리고 내부 네트워크 클라이언트들은 초당 512 바이트로 제한하며, 선호되는 도메인은 초당 4Kb까지 허용한다. 기타 나머지(all 키워드)는 2Kb까지 허용된다. 이 순서는 중요하다. 가장 먼저 일치하는 패턴이 적용된다.

2.2.2.3. 파일 크기에 따른 대역폭 제한

대역폭 제한은 LargeFileLimit 지시어를 사용하여 파일 크기에 따라 설정될 수 있다. 이렇게 함으로써, 크기가 큰 파일들은 작은 파일들에 비해 보다 더 점차적으로 전송될 수 있게 할 수 있다. 크기가 큰 파일이 보통의 작은 정적 페이지와 동일한 방식으로 전송되어 문제가 되는 경우에 이러한 설정은 큰 가치를 줄 수 있다. LargeFileLimitBandWidth 지시어가 동일한 URL에 적용되는 경우에는, 그 둘 중에 가장 작은 것이 선택된다.

LargeFileLimit 지시어는 두 개의 매개변수를 취한다. 하나는 킬로바이트 단위의 파일 크기이고, 다른 하나는 전송률이다. 예는 다음과 같다.

<Directory /home/www/alpha-complex>
  LargeFileLimit 50   8092
  LargeFileLimit 1024 4096
  LargeFileLimit 2048 2048
</Directory>
                                        

위 설정은 50Kb 보다 작은 파일들은 대역폭 제한을 하지 않도록 한다. 그리고 50Kb 이상 1Mb 미만의 파일은 초당 4Kb로 제한하며, 2Mb 이상의 파일들은 초당 2Kb로 제한한다.

BandWidth 지시어와 마찬가지로 순서가 중요한데, 그 순서에 따라 처음 일치하는 패턴이 발견되었을 때, 그 제한 설정을 취하게 되므로, 반드시 위 지시어들은, 파일 크기가 작은 것에서 큰 것으로 순서를 유지하여 설정해야 한다.

복수의 클라이언트들이 동시에 접속하는 경우에는, mod_bandwidth 모듈은 또한 각 클라이언트 별로 사용 가능한 대역폭을 비율로 계산하여 할당한다. 만일 열 개의 클라이언트가 전체 대역폭 제한 값 초 당 4096 바이트로 설정된 곳에 동시에 접속하는 경우, 각 클라이언트는 초 당 410 바이트를 할당 받는다. 주의할 점은, 예를 들어, 비디오 자료 웹 사이트와 같은 곳에서는, 이 값을 너무 작게 설정하면 사용자의 경험에 큰 타격을 줄 수 있다는 것이다.

2.2.2.4. 최소 대역폭과 클라이언트들 간의 대역폭 분할

대역폭은 mod_bandwidth 모듈에 의해, 개별 클라이언트 대역폭 설정에 기반하여, 클라이언트들 간에 공유된다. 따라서, 두 클라이언트 모두 초 당 4Kb라는 대역폭 제한을 가지고 있는 경우에, mod_bandwidth 모듈은 이를 나누어, 각 클라이언트에게 초 당 2Kb라는 대역폭을 선사한다. 하지만, 할당된 대역폭은 MinBandWidth로 설정 가능한 최소 대역폭 이하로 떨어지지는 않게 된다. 이의 기본 값은 초 당 256 바이트이다.

MinBandWidth 256

MinBandWidth는 첫번째 매개변수로 도메인 이름이나 IP 주소를 취하는데, 이는 BandWidth 지시어와 동일하다. BandWidth와 마찬가지로 순서에 따라 적용된다.

<Directory />
  BandWidth    localhost 0
  BandWidth    friendly.com 4096
  MinBandWidth friendly.com 2096
  BandWidth    192.168 512
  BandWidth    all 2048
  MinBandWidth all 512
</Directory>
                                        

한편, 클라이언트 별 대역폭 할당은, 설정 값을 -1로 설정하게 되면, 클라이언트 별 대역폭 할당 기능을 완전히 비활성화시킬 수 있다. 이렇게 하게 되면, 설정된 값이 그대로 사용되어 각 클라이언트에 할당되며, 전체를 비율에 의해 재계산되어 할당되지 않게 된다. 아래 예와 같다.

MinBandWidth all -1

위와 같이 설정하는 경우, 만일 열 개의 클라이언트가 초 당 4096 바이트의 대역폭 제한으로 접속하는 경우에, mod_bandwidth 모듈은 모든 각각의 클라이언트들에게 초 당 4096 바이트의 대역폭을 선사하게 된다.

2.2.2.5. 전송 알고리즘

mod_bandwidth 모듈은 두 개의 다른 알고리즘에 의해 클라이언트에게 데이터를 전송할 수 있다. 보통은 데이터들을 1Kb 짜리 패킷들로 나눈 다음 그것들을 대역폭이 허용할 때에 전송하게 된다. 만일 가용한 대역폭이 겨우 512 바이트에 불과한 경우에는, 1Kb 짜리 패킷은 대략 2초 간격으로 보내진다.

이에 대한 다른 대안은 BandWidthPulse 지시어를 사용하는 것인데, 이 지시어는 마이크로 초(microseconds)를 매개변수로 취한다. 이 지시어가 활성화된 경우, mod_bandwidth 모듈은 패킷의 크기와 상관 없이 설정된 각 주기마다 패킷을 보내게 된다. 예를 들면, 다음과 같이 초 당 주기율을 설정할 수 있다.

BandWidthPulse 1000000

위의 설정은 클라이언트가 초 당 512 바이트의 대역폭을 할당 받은 경우, 512 바이트 짜리 패킷 하나가 초 당 한 번 전송되게 한다. 이 설정의 장점은, 부하가 매우 많고 패킷 간의 시간 차가 클 때에, 보다 부드러운 방식의 통신을 가능하게 한다는 것이다. 반면 단점은, 실제 데이터에 대한 전송 보다 네트워크 연결에 대한 대역폭이 상대적으로 커진다는 점이다.

3. 아파치 서버 성능 벤치마킹

아파치 서버 성능을 개선할 수 있도록 설정하는 것은 훌륭한 것이지만, 그러한 노력이 어떻게 서버 성능에 영향을 주었는지에 대한 증거가 없이는 설정의 변경이 유용한 결과를 가져 왔는지 알 도리가 없다. 어떤 경우는 성능을 개선하려고 한 설정의 변경이 오히려 더 성능을 떨어뜨리는 결과를 가져 오기도 한다.

확실하게 상황을 파악하기 위해, 우리는 서버 성능을 벤치마크할 수 있으며 통계를 산출할 수 있다. 아파치는 ab 유틸리티를 제공하여 이러한 목적을 달성할 수 있도록 한다. 이외에도 다른 외부 벤치마킹 서비스들을 활용하는 것에 대해서도 살펴 보기로 한다.

3.1. ab를 활용한 아파치 벤치마킹

아파치는 자체적으로 ab라는 벤치마크 도구를 제공한다. 이 도구는 단순한 HTTP 클라이언트로서, 우리가 정한 특정 기준들에 따라 대상 서버에 반복된 요청을 보낸다. 이런 관점에서 ab 도구는 ping이나 spray와 같은 스타일의 도구를 HTTP를 위해 만든 것이라고 볼 수 있다. 이 도구는 어떤 웹 서버라도 쉽게 벤치마크할 수 있으므로, 벤치마크 비교 테스트에서 이 도구를 사용할 수 있다.

이 도구는 많은 옵션들을 제공하지만, 기본적으로 ab 도구 사용 방법은 슬래시로 끝나는 호스트 이름만으로도 바로 사용할 수 있다. 끝에 슬래시를 붙이지 않으면, 경고 메시지를 출력한다.

$ ./ab www.alphacomplex.com/

또한 다음 명령 수행도 위의 결과와 동일하다.

$ ./ab http://www.alphacomplex.com:80/

위와 같이 수행하게 되면, 대상 서버에 포트 번호 80번으로 한 번의 요청을 수행하게 될 것이다. 이는 대상 서버가 접근 가능한지를 확인할 수 있는 간단한 방법이 될 수도 있을 것이다. 하지만, 통계적으로는 별 의미가 없다. 다음과 같이 -n 옵션을 지정하여 요청 회수를 지시할 수 있다.

$ ./ab -n 1000 www.alphacomplex.com/

위 명령은 대상 서버로 1000번의 요청을 한번에 하나씩 보내게 될 것이다. 동시에 복수의 요청들을 보내 서버가 얼마나 견뎌내는지를 알고 싶다면, -c 옵션을 사용할 수 있다. 아래 명령은 동시에 10개의 요청들을 보내게 된다.

$ ./ab -n 1000 -c 10 www.alphacomplex.com/

ab는 매 100개의 요청 시마다의 진척 상황을 출력해 줄 것이다. 요청이 모두 종료되면, 시간에 따른 누적 요청 비율을 보여주는 표를 출력한다.

기본적으로 abGET 요청을 사용하며, 사람이 읽을 수 있는 형태의 공백으로 구별된 표를 출력하는데, 이 두 가지는 모두 옵션으로 변경할 수 있다. 또한 쿠키, 인증, 그리고 프록시 우회와 같은 옵션들도 설정할 수 있다. 전체적으로 ab는 22개의 옵션들을 제공하는데, 이들에 대해 아래에서 설명한다.

3.1.1. 사용법 옵션

아래 옵션들은 ab에 대한 정보를 출력하며 테스트를 수행하지는 않는다.

Table 8. 사용법 옵션

옵션 의미
-h ab 도움말 표시
-V ab의 버전 출력

3.1.2. 연결 옵션

이 연결 옵션들은 ab가 어떻게 요청을 생성할 것인지를 결정한다.

Table 9. 연결 옵션

옵션 의미
-c requests 한번에 동시에 연결 및 관리해야 하는 요청의 개수
-n response 전체 요청의 개수
-t seconds 하나의 요청이 완료되기까지 대기하는 최대 시간
-k HTTP/1.1의 KeepAlive 연결을 사용

-c 옵션과 -k 옵션의 선택은 통계를 산출하는 데에 있어서 큰 차이를 가져온다. 즉, 개별 요청을 잘 처리하던 서버가 한번에 스무 개의 요청을 처리할 때에는 끔찍한 결과를 가져오는 경우가 있다. 잘못 작성된 CGI 애플리케이션 같은 것들이 이런 현상을 보일 수 있다. KeepAlive 옵션을 사용하면 ab가 연결을 유지하여, 매번 요청이 완료될 때마다 연결을 끊지 않고, 복수의 요청에서 계속 사용하게 된다.

3.1.3. 인증 및 프록시 옵션

인증 및 프록시 옵션은 인증과 프록시 설정을 가능하게 한다.

Table 10. 인증 및 프록시 옵션

옵션 의미
-A user:password 제공된 인증 유형을 사용하여 Authorization 헤더를 보냄
-X host:port host:port로 프록시를 사용함.
-P user:password (-X 옵션이 함께 사용되는 경우에만) Proxy-Authorization 헤더를 보냄.

쿠키 기반 인증을 사용하려면 -C/-H 옵션을 대신 사용해야 한다. 세션 쿠키를 사용하는 사이트를 벤치마크하려면, -v 4 옵션을 사용하여 연속적인 벤치마크에서 하나의 세션 쿠키를 사용할 수 있도록 한다.

Note

셸 스크립트나 펄 스크립트를 사용하여 Cookie 또는 Cookie2 헤더의 값을 추출하게 할 수도 있으며, 그것을 이어지는 ab 명령에 계속해서 전달할 수 있다. ab는 서버로부터 전달된 쿠키를 저장하거나 반환하지 않는다.

3.1.4. 요청 방법, 헤더, 바디 옵션

이 옵션들은 요청의 내용을 결정한다.

Table 11. 요청 방법, 헤더, 바디 옵션

옵션 의미
-C cookie 제공된 값으로 Set-Cookie 헤더를 보냄
-H header:value 제공된 값으로 임의의 헤더를 보냄
-i GET이 아니라 HEAD 요청을 보냄
-p file GET이 아니라, 제공된 파일을 사용하여 POST 요청을 보냄
-T mime/type POST 요청일 때 미디어 타입을 설정

쿠키에 대해서는, 설정 값이 서버가 받아들일 수 있는 스타일의 쿠키로 설정되어야 한다. 예를 들면 다음과 같다.

-C "Troubleshooter=Roy-G-BIV; path=/; expires=Fri, 01-Aug-03 09:05:02 GMT; domain=.alpha-complex.com"

주목할 점은 ab는 지정되지 않은 경우 경로나 도메인을 추론하지 않는다는 점이다. 즉, ab는 오직 우리가 지정한 것만을 전송한다. 전체 쿠키는 공백과 세미콜론을 포함하므로, 쌍따옴표로 둘러싸여야 한다. 기본적인 쿠키 값들은 아래와 같이 쌍따옴표를 생략할 수도 있다.

-C Troubleshooter=Roy-G-BIV

위는 아래와 동일하다.

-H "Set-Cookie: Troubleshooter=Roy-G-BIV"

가능한 쿠키 포맷에 대한 상세 내역에 대해서는, mod_userattack 모듈의 CookieStyle 지시어에 대한 설명과 옵션들에 대해 참조해 보기 바란다. 또한 우리는 Set-Cookie2 헤더를 전송할 수도 있는데, 이 때에는 -C 옵션보다는 -H 옵션을 사용하여야만 한다. 즉, -C 옵션은 오직 Set-Cookie 헤더만을 전송한다.

-C 옵션과 -H 옵션은 -i 옵션이나 -p 옵션을 사용할 수 있다. 하지만, -i 옵션과 -p 옵션은 상호 배타적이다. -T 옵션은 오직 -p 옵션이 설정된 경우에만 의미가 있다. 왜냐하면 Content-Type 요청 헤더가 오직 바디를 포함한 경우에만 적용 가능하기 때문이다. 이 헤더는 GET이나 HEAD 요청에서는 적용할 수 없다.

-v 옵션은 Verbosity 수준을 증가시키기 위해 사용할 수 있으며, 이를 통해 실제 요청된 내역을 확인할 수 있다. 이는 요청이 우리가 의도한 바대로 전송되는지를 확인할 수 있는 좋은 수단이 된다. -n 옵션이 생략되어야만, 단일 요청만으로 이런 확인 작업을 보다 용이하게 할 수 있을 것이다.

3.1.5. 통계 옵션

이 옵션들은 계산되고 출력되는 통계 관련 옵션들을 지정한다.

Table 12. 통계 옵션

옵션 의미
-d 누적 비율 대 시간표의 보고서 생성
-S mean 대신에 avg 컬럼을 생성하며, 연결 회수 표에서 표준편차(sd)를 생성

3.1.6. 출력 옵션

이 옵션들은 ab가 무엇을 출력하고 어떤 포맷으로 생성할 것인가를 지정한다.

Table 13. 출력 옵션

옵션 의미
-e csvfile 부가적으로 csvfile 파일에 CSV 포맷으로 비율 통계를 기록
-g gnuplotfile 부가적으로 gnuplotfile 파일에 GNUPlot 포맷의 누적 통계를 기록
-w 통계를 일반 텍스트 파일이 아닌 HTML 표 포맷으로 출력
-x table_attrs HTML 테이블 출력일 때 표의 속성들을 설정
-y tr_attrs HTML 테이블 출력일 때 표의 행에 대한 속성들을 설정
-v verbosity Verbosity 수준 설정

ab는 네 가지의 서로 다른 포맷으로 출력할 수 있다. -e 옵션은 완전한 누적 비율 표를 작성하는데, 모든 퍼센트 포인트를 기록한다.

-g 옵션은 GNUPlot 파일을 생성하며, 이 파일에는 각 개별 요청의 시작 시간과 성능 통계들을 포함한다. 다음은 그 예이다.

starttime   seconds ctime   dtime   ttime   wait
Fri Aug 1 04:07:54      200     1014713074914875        0       26      26      0
Fri Aug 1 04:07:54      200     1014713074942100        0       27      27      0
Fri Aug 1 04:07:54      200     1014713074969613        0       26      26      0
                                

이 파일은 GNUPlot 파일을 인식할 수 있는 어떤 애플리케이션에서도 사용될 수 있다. 또한 고정 길이 포맷을 인식할 수 있는 스프레드 시트 프로그램에서도 로드할 수 있을 것이다.

-w 옵션은 일반 텍스트 파일이 아니라, HTML 파일로 출력하게 한다. 또한 -x 옵션, -y 옵션, 그리고 -z 옵션은 표의 모양새를 지정한다. 다음은 그 예이다.

$ ./ab -n 1000 -w -x "cellspacing=2 cellpadding=1 bgcolor=black border=0" -y "valign=top" -z "bgcolor=white"

위의 명령은 다음과 같은 스타일의 HTML을 생성한다.

    <table cellspacing=2 cellpadding=1 bgcolor=black>
      <tr valign=top>
        <td bgcolor=white>
                                

-w 옵션을 사용하게 되면, 현재로서는, -e 옵션과 -g 옵션을 통해 파일을 생성하는 기능을 꺼버리게 된다. 이는 조만간 변경될 것이다.

Verbosity 수준은 1부터 4까지 설정할 수 있으며, 0으로 설정하게 되면, 1로 설정한 것과 동일하게 취급된다. 그리고 4보다 큰 값을 설정하게 되면, 4로 설정한 것과 동일하게 취급된다. Verbosity 수준은 다음과 같다.

Table 14. Verbosity 수준

수준 설명
1 일반적인 통계 출력. 개별 요청 별로 출력하지 않음.
2 전송된 요청들을 출력
3 요청들을 출력하되, LOG: 행들을 추가
4 요청과 응답을 출력

주의

Verbosity 수준이 높을 수록 ab의 성능은 떨어지게 되며, 결과에도 영향을 끼치게 된다. 하지만, 단일 요청을 테스트할 때에 Verbosity 수준을 4로 설정하는 것은 벤치마크 테스트 자체에 대한 중요하고 정확한 정보를 얻는데 매우 유용한 것이다.

3.2. 외부 벤치마킹 도구

많이 사용되는 벤치마크 도구들로는 다음과 같은 것들이 있다.

  • httpperf

    단일 파일에 대한 단순한 요청 테스트라든가 클라이언트 세션들을 시뮬레이션해 보는 데에 매우 유용한 도구임.

  • Apache Bench

    단순한 벤치마크 도구로서, 현재 설치된 아파치 서버에 대한 성능을 측정해 보는 데에 좋은 정보를 제공한다.

3.3. 벤치마킹 전략

ab나 외부 벤치마크 서비스를 사용하여 몇 개의 대표 URL들을 테스트하여 필요한 벤치마크 정보를 얻을 수 있다고 생각하는 것은 매우 쉽게 떠올릴 수 있는 것이다. 하지만 불행히도, 이러한 것은 실제 경우와는 많이 다르다. 다음과 같은 이유에서다.

  • 우리가 대표 URL들이라고 생각한 것들이 실제로는 아닌 경우들이 있다.

  • CGI나 서버 사이드 애플리케이션에 전달되는 매개변수들이 변하게 되면, 그에 따라 실제 성능이 크게 좌우되는 경우들이 있다.

  • 어디서 테스트했는가에 따라 서로 다른 결과를 얻기도 한다. 또 네트워크 환경에 의존하기도 한다.

  • 실제 운영 서버를 벤치마크하게 되면 완전히 다른 결과를 가져오기도 한다.

  • 한번에 단 하나의 URL을 테스트하는 것은 실제 사용 패턴을 대표할 수 없는 경우가 많다.

마지막 경우는 좀더 설명될 필요가 있는데, ab와 같은 도구는 한번에 오직 하나의 URL만을 테스트할 수 있다. 그 URL이 우리가 테스트하고자 하는 JSP나 EJB 애플리케이션인 경우에, 여러 동시 연결 테스트를 수행하더라도, 꽤 만족스러운 결과를 얻을 수도 있다. 하지만 이는 잘못된 판단으로 판명되는 경우가 있다.

만일 우리가 동시에 여러 개의 ab 인스턴스를 동작시키게 되면, 동일한 애플리케이션에 대해서도 완전히 다른 결과를 얻을 수도 있다. 서버가 매우 느려질 수 있는 것이다. 이러한 이유는 많은 것들이 존재하지만, 전형적인 이유 중의 하나는 대규모 애플리케이션의 각 부분이 데이터베이스 연결을 필요로 하고, 각각 자원들을 필요로 하며, 또 각각 로깅 기능 등등이 필요하기 때문이다. 각 기능들을 한번에 하나씩 테스트하게 되면 서버를 전체적으로 한번 사용하는 것이기는 하다.

하지만, 그 기능들을 한번에 전체적으로 동시에 테스트하게 되면, 갑자기 메모리가 충분치 않아질 수도 있고, 데이터베이스가 열려진 연결의 최대치를 초과해 버릴 수도 있는 것이다.

결론적으로, 우리는 우리 서버에 대해 어떻게 벤치마크해야 할 것인가에 대해 더 계획을 세워야 한다는 것이다. 단순히 URL에 대해 ab 도구를 사용하여 단순한 테스트 결과를 얻는 것은 전체를 대표하지 못하며, 통계가 우리를 잘못된 판단으로 이끌 수 있는 것이다.

4. 성능 체크리스트

아래는 시스템 관리자가 동시 연결 요청들을 처리하는 데에 있어서 가장 높은 수준의 성능을 보장하기 위해 확인해 볼 수 있는 아이디어들과 기법들에 대한 간단한 목록이다.

  • 서버에 빠른 SCSI 디스크를 단 RAID 0를 사용한다.

  • 리눅스 플랫폼이라면, 하드 디스크 상호작용에 대해 hdparm 유틸리티를 사용하여 정교한 튜닝을 한다. (예를 들면, DMS 설정, Read-ahead, 그리고 Multiple-write 등)

  • 대규모의 정적 컨텐트들이 존재한다면, Tux나 kHTTPd와 같은 Non-forking 커널 공간 웹 서버들을 사용한다.

  • 모든 불필요한 로깅을 비활성화 시킨다. 예를 들면 LogLevelerror로 설정한다.

  • 로깅을 비활성화하기 전에 서버는 잘 테스트되어야 한다.

  • HARD_SERVER_LIMIT/HARD_THREAD_LIMIT/MAX_SERVER_LIMIT/MAX_THREAD_LIMIT 값이 적절한지를 확인한다.

  • 서버를 웹 서버 기능만 수행하게 한다. 다른 불필요한 애플리케이션들과 프로세스들을 비활성화 시킨다.

  • 불필요한 모듈들이 포함되지 않도록 한다.

  • 아파치를 완전히 정적 서버로 빌드하고, mod_so를 제거한다.

  • ExpiryCache-Control 헤더를 설정하여 가능한 캐시로부터 전송될 수 있도록 한다.

  • KeepAlive 값을 5초 이내로 설정하여, 가능한 한, 아파치가 기존 연결이 아니라 새로운 연결에 대해 응답할 수 있도록 한다.

  • 애플리케이션들을 영속적으로 만든다. (예를 들면 mod_fastcgi를 사용하는 방식) 그리고 가능한 한, 정적 페이지로 바꾼다. 데이터베이스를 변경하지 않는 간단한 데이터베이스 검색은 정적인 페이지로 교체할 수 있다.

  • mod_mmap_static (아파치 1.3) 이나 mod_file_cache (아파치 2.0) 모듈을 사용하여 빈번하게 접근되는 파일들을 메모리나 파일 핸들로부터 전송될 수 있도록 한다.

  • DNS Lookup과 같이 중복적인 작업을 하지 않도록 한다.

  • 커널의 네트워크와 프로세스 관련 매개변수들을 튜닝하여 아파치의 기대되는 부하를 처리할 수 있도록 한다.

  • 가능한 한, SSL을 사용하지 않는다. SSL은 서버에 처리 부하를 준다. 안전한 트랜잭션은 별도 서버로 이전하는 것을 검토해 보라.

  • 부하 분산을 위해 서버 클러스터를 구축하는 것을 검토해 보라.

참고 문헌

도서 또는 웹 문서들

[Wainwright02] Peter Wainwright. Copyright © 2002 Wrox Press. Wrox Press. Professional Apache 2.0.

 

Posted by redkite
, |

최근에 달린 댓글

최근에 받은 트랙백

글 보관함