mirror of
				https://github.com/apache/httpd.git
				synced 2025-11-03 17:53:20 +03:00 
			
		
		
		
	git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@1330844 13f79535-47bb-0310-9956-ffa450edef68
		
			
				
	
	
		
			1043 lines
		
	
	
		
			40 KiB
		
	
	
	
		
			XML
		
	
	
	
	
	
			
		
		
	
	
			1043 lines
		
	
	
		
			40 KiB
		
	
	
	
		
			XML
		
	
	
	
	
	
<?xml version="1.0" encoding="EUC-KR" ?>
 | 
						|
<!DOCTYPE manualpage SYSTEM "../style/manualpage.dtd">
 | 
						|
<?xml-stylesheet type="text/xsl" href="../style/manual.ko.xsl"?>
 | 
						|
<!-- English Revision: 105989:1330291 (outdated) -->
 | 
						|
 | 
						|
<!--
 | 
						|
 Licensed to the Apache Software Foundation (ASF) under one or more
 | 
						|
 contributor license agreements.  See the NOTICE file distributed with
 | 
						|
 this work for additional information regarding copyright ownership.
 | 
						|
 The ASF licenses this file to You under the Apache License, Version 2.0
 | 
						|
 (the "License"); you may not use this file except in compliance with
 | 
						|
 the License.  You may obtain a copy of the License at
 | 
						|
 | 
						|
     http://www.apache.org/licenses/LICENSE-2.0
 | 
						|
 | 
						|
 Unless required by applicable law or agreed to in writing, software
 | 
						|
 distributed under the License is distributed on an "AS IS" BASIS,
 | 
						|
 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
						|
 See the License for the specific language governing permissions and
 | 
						|
 limitations under the License.
 | 
						|
-->
 | 
						|
 | 
						|
<manualpage metafile="perf-tuning.xml.meta">
 | 
						|
  <parentdocument href="./">Miscellaneous Documentation</parentdocument>
 | 
						|
 | 
						|
  <title>아파치 성능향상</title>
 | 
						|
 | 
						|
  <summary>
 | 
						|
 | 
						|
    <p>아파치 2.0은 기능과 포팅가능성과 성능의 균형이 맞도록
 | 
						|
    설계한 범용 웹서버이다. 벤치마크 기록을 세우기위해 설계하지
 | 
						|
    않았지만 아파치 2.0은 실제 많은 경우 높은 성능을 낸다.</p>
 | 
						|
 | 
						|
    <p>아파치 1.3과 비교해서 2.0 버전은 처리량과 확장성(scalability)을
 | 
						|
    높이기위해 많은 최적화를 했다. 기본값으로 대부분 최적화한
 | 
						|
    값을 사용한다. 그러나 컴파일시 혹은 실행시 설정이 성능에
 | 
						|
    큰 영향을 줄 수 있다. 이 문서는 아파치 2.0의 성능을 향상하기위해
 | 
						|
    서버 관리자가 설정할 수 있는 옵션을 설명한다. 어떤 설정
 | 
						|
    옵션은 웹서버가 하드웨어와 운영체제의 기능을 더 잘 활용하도록
 | 
						|
    하는 반면, 어떤 옵션은 속도를 위해 기능을 희생한다.</p>
 | 
						|
 | 
						|
  </summary>
 | 
						|
 | 
						|
  <section id="hardware">
 | 
						|
 | 
						|
    <title>하드웨어와 운영체제에 대해서</title>
 | 
						|
 | 
						|
    <p>웹서버 성능에 가장 큰 영향을 주는 것은 메모리다. 스왑은
 | 
						|
    요청당 지연시간을 사용자가 "충분히 빠르다고" 생각하지 못하게
 | 
						|
    늘리기때문에 웹서버는 스왑을 하면 안된다. 느려지면 사용자는
 | 
						|
    정지하고 다시 접속하여 부하가 계속 증가한다. <directive
 | 
						|
    module="mpm_common">MaxClients</directive> 지시어를 조절하여
 | 
						|
    웹서버가 스왑을 할 정도로 많은 자식을 만들지않도록 해야
 | 
						|
    한다. 방법은 간단하다: <code>top</code>과 같은 도구에서
 | 
						|
    프로세스 목록을 보고 아파치 프로세스의 평균 메모리 사용량을
 | 
						|
    알아낸후, 전체 사용가능한 메모리에서 다른 프로세스들이 사용할
 | 
						|
    공간을 뺀 값에서 나눈다.</p>
 | 
						|
 | 
						|
    <p>나머지는 평범하다: 충분히 빠른 CPU, 충분히 빠른 네트웍카드,
 | 
						|
    충분히 빠른 디스크, 여기서 "충분히 빠른"은 실험을 해서 결정해야
 | 
						|
    한다.</p>
 | 
						|
 | 
						|
    <p>운영체제는 보통 각자 알아서 선택할 일이다. 그러나 일반적으로
 | 
						|
    유용하다고 판명된 몇가지 지침이 있다:</p>
 | 
						|
 | 
						|
    <ul>
 | 
						|
      <li>
 | 
						|
        <p>선택한 운영체제의 최신 안정 버전과 패치를 실행한다.
 | 
						|
        많은 운영체제 제작사는 최근 TCP 스택과 쓰레드 라이브러리에
 | 
						|
        많은 속도향상을 했다.</p>
 | 
						|
      </li>
 | 
						|
 | 
						|
      <li>
 | 
						|
        <p>운영체제가 <code>sendfile(2)</code> 시스템호출을
 | 
						|
        지원한다면, 이를 사용하기위한 버전이나 패치를 설치하였는지
 | 
						|
        확인한다. (예를 들어, 리눅스라면 2.4 이상 버전을 뜻한다.
 | 
						|
        Solaris 8 초기 버전은 패치가 필요하다.) 지원하는 시스템이라면
 | 
						|
        아파치 2는 <code>sendfile</code>을 사용하여 CPU를 덜
 | 
						|
        사용하며 정적 파일을 더 빨리 전송할 수 잇다.</p>
 | 
						|
      </li>
 | 
						|
    </ul>
 | 
						|
 | 
						|
  </section>
 | 
						|
 | 
						|
  <section id="runtime">
 | 
						|
 | 
						|
    <title>실행시 설정에 대해서</title>
 | 
						|
 | 
						|
    <related>
 | 
						|
      <modulelist>
 | 
						|
        <module>mod_dir</module>
 | 
						|
        <module>mpm_common</module>
 | 
						|
        <module>mod_status</module>
 | 
						|
      </modulelist>
 | 
						|
      <directivelist>
 | 
						|
        <directive module="core">AllowOverride</directive>
 | 
						|
        <directive module="mod_dir">DirectoryIndex</directive>
 | 
						|
        <directive module="core">HostnameLookups</directive>
 | 
						|
        <directive module="core">EnableMMAP</directive>
 | 
						|
        <directive module="core">EnableSendfile</directive>
 | 
						|
        <directive module="core">KeepAliveTimeout</directive>
 | 
						|
        <directive module="prefork">MaxSpareServers</directive>
 | 
						|
        <directive module="prefork">MinSpareServers</directive>
 | 
						|
        <directive module="core">Options</directive>
 | 
						|
        <directive module="mpm_common">StartServers</directive>
 | 
						|
      </directivelist>
 | 
						|
    </related>
 | 
						|
 | 
						|
    <section id="dns">
 | 
						|
 | 
						|
      <title>HostnameLookups와 DNS에 대해 고려할 점들</title>
 | 
						|
 | 
						|
      <p>아파치 1.3 이전에 <directive
 | 
						|
      module="core">HostnameLookups</directive>의 기본값은
 | 
						|
      <code>On</code>이였다. 요청을 마치기전에 DNS 검색이 끝나야
 | 
						|
      하므로 요청마다 지연이 생겼다. 아파치 1.3에서 이 설정의
 | 
						|
      기본값이 <code>Off</code>로 변경되었다. 로그파일의 주소를
 | 
						|
      호스트명으로 변환하려면 여러 로그처리 프로그램중 하나인,
 | 
						|
      아파치에 포함된 <a
 | 
						|
      href="../programs/logresolve.html"><code>logresolve</code></a>
 | 
						|
      프로그램을 사용하라.</p>
 | 
						|
 | 
						|
      <p>로그처리 작업이 서버 성능에 악영향을 미치므로 실제
 | 
						|
      사용하는 웹서버가 아닌 다른 컴퓨터에서 로그파일을 후처리하길
 | 
						|
      바란다.</p>
 | 
						|
 | 
						|
      <p><code><directive module="mod_access">Allow</directive>
 | 
						|
      from domain</code>이나 <code><directive 
 | 
						|
      module="mod_access">Deny</directive> from domain</code>
 | 
						|
      지시어를 사용한다면 (즉, IP 주소가 아닌 호스트명이나 도메인명을
 | 
						|
      사용한다면) 부득이 중복-역 DNS 검색을 (역검색을 한후 악의로
 | 
						|
      변경되었는지 확인하기위해 다시 검색) 해야 한다. 그러므로
 | 
						|
      성능을 높이기위해 이런 지시어에는 가능하면 이름대신 IP
 | 
						|
      주소를 사용한다.</p>
 | 
						|
 | 
						|
      <p><code><Location /server-status></code> 섹션 등으로
 | 
						|
      지시어의 적용범위를 제한할 수 있음을 기억하라. 이 경우
 | 
						|
      조건에 맞는 요청에만 DNS 조회를 한다. 다음은
 | 
						|
      <code>.html</code>과 <code>.cgi</code> 파일만 DNS 검색을
 | 
						|
      하는 예제다:</p>
 | 
						|
 | 
						|
      <example>
 | 
						|
        HostnameLookups off<br />
 | 
						|
        <Files ~ "\.(html|cgi)$"><br />
 | 
						|
        <indent>
 | 
						|
          HostnameLookups on<br />
 | 
						|
        </indent>
 | 
						|
        </Files>
 | 
						|
      </example>
 | 
						|
 | 
						|
      <p>그러나 CGI에서 DNS명이 필요할 뿐이라면, 필요한 특정
 | 
						|
      CGI에서만 <code>gethostbyname</code> 호출을 하도록 고려해볼
 | 
						|
      수 있다.</p>
 | 
						|
 | 
						|
    </section>
 | 
						|
 | 
						|
    <section id="symlinks">
 | 
						|
 | 
						|
      <title>FollowSymLinks와 SymLinksIfOwnerMatch</title>
 | 
						|
 | 
						|
      <p>URL 공간에서 <code>Options FollowSymLinks</code>를
 | 
						|
      사용하지않고 <code>Options SymLinksIfOwnerMatch</code>를
 | 
						|
      사용하면 아파치는 심볼링크를 검사하기위해 시스템호출을
 | 
						|
      한번 더 해야 한다. 파일명의 각 부분마다 한번씩 더 호출을
 | 
						|
      한다. 예를 들어, 설정이 다음과 같고:</p>
 | 
						|
 | 
						|
      <example>
 | 
						|
        DocumentRoot /www/htdocs<br />
 | 
						|
        <Directory /><br />
 | 
						|
        <indent>
 | 
						|
          Options SymLinksIfOwnerMatch<br />
 | 
						|
        </indent>
 | 
						|
        </Directory>
 | 
						|
      </example>
 | 
						|
 | 
						|
      <p><code>/index.html</code> URI에 대한 요청이 있다고 가정하자.
 | 
						|
      그러면 아파치는 <code>/www</code>, <code>/www/htdocs</code>,
 | 
						|
      <code>/www/htdocs/index.html</code> 각각에 대해
 | 
						|
      <code>lstat(2)</code>를 호출한다. <code>lstats</code>
 | 
						|
      결과를 캐싱하지 않기때문에 요청이 들어올 때마다 매번 같은
 | 
						|
      작업을 한다. 진짜 심볼링크 보안 검사를 원한다면 다음과
 | 
						|
      같이 할 수 있다:</p>
 | 
						|
 | 
						|
      <example>
 | 
						|
        DocumentRoot /www/htdocs<br />
 | 
						|
        <Directory /><br />
 | 
						|
        <indent>
 | 
						|
          Options FollowSymLinks<br />
 | 
						|
        </indent>
 | 
						|
        </Directory><br />
 | 
						|
        <br />
 | 
						|
        <Directory /www/htdocs><br />
 | 
						|
        <indent>
 | 
						|
          Options -FollowSymLinks +SymLinksIfOwnerMatch<br />
 | 
						|
        </indent>
 | 
						|
        </Directory>
 | 
						|
      </example>
 | 
						|
 | 
						|
      <p>이 경우 최소한 <directive
 | 
						|
      module="core">DocumentRoot</directive> 경로는 검사하지
 | 
						|
      않는다. DocumentRoot 밖에 있는 경로로 <directive
 | 
						|
      module="mod_alias">Alias</directive>나 <directive
 | 
						|
      module="mod_rewrite">RewriteRule</directive>을 사용한
 | 
						|
      경우에도 위와 비슷한 섹션이 필요하다. 심볼링크 보안을
 | 
						|
      고려하지 않고 최고의 성능을 얻으려면,
 | 
						|
      <code>FollowSymLinks</code>를 설정하고,
 | 
						|
      <code>SymLinksIfOwnerMatch</code>는 절대로 안된다.</p>
 | 
						|
 | 
						|
    </section>
 | 
						|
 | 
						|
    <section id="htacess">
 | 
						|
 | 
						|
      <title>AllowOverride</title>
 | 
						|
 | 
						|
      <p>URL 공간에서 overrides를 허용한다면 (보통
 | 
						|
      <code>.htaccess</code> 파일) 아파치는 파일명의 각 부분마다
 | 
						|
      <code>.htaccess</code>를 열길 시도한다. 예를 들어,</p>
 | 
						|
 | 
						|
      <example>
 | 
						|
        DocumentRoot /www/htdocs<br />
 | 
						|
        <Directory /><br />
 | 
						|
        <indent>
 | 
						|
          AllowOverride all<br />
 | 
						|
        </indent>
 | 
						|
        </Directory>
 | 
						|
      </example>
 | 
						|
 | 
						|
      <p><code>/index.html</code> URI에 대한 요청이 있다고 가정하자.
 | 
						|
      아파치는 <code>/.htaccess</code>, <code>/www/.htaccess</code>,
 | 
						|
      <code>/www/htdocs/.htaccess</code>를 열려고 시도한다.
 | 
						|
      해결책은 앞의 <code>Options FollowSymLinks</code> 경우와
 | 
						|
      비슷하다. 최고의 성능을 얻으려면 파일시스템에 대해서 항상
 | 
						|
      <code>AllowOverride None</code>을 사용한다.</p>
 | 
						|
 | 
						|
    </section>
 | 
						|
 | 
						|
    <section id="negotiation">
 | 
						|
 | 
						|
      <title>내용협상</title>
 | 
						|
 | 
						|
      <p>가능하고 진짜 조금의 성능향상에도 관심이 있다면 내용협상을
 | 
						|
      막는다. 실제로 협상의 이득은 성능저하보다 작다. 서버를
 | 
						|
      빠르게 할 수 있다. 다음과 같이 와일드카드를 사용하는 대신:</p>
 | 
						|
 | 
						|
      <example>
 | 
						|
        DirectoryIndex index
 | 
						|
      </example>
 | 
						|
 | 
						|
      <p>완전한 목록을 사용한다:</p>
 | 
						|
 | 
						|
      <example>
 | 
						|
        DirectoryIndex index.cgi index.pl index.shtml index.html
 | 
						|
      </example>
 | 
						|
 | 
						|
      <p>가장 흔한 것을 앞에 둔다.</p>
 | 
						|
 | 
						|
      <p>또, 디렉토리에서 파일들을 찾는 <code>MultiViews</code>
 | 
						|
      보다는, 한 파일만 읽으면 필요한 정보를 얻을 수 있는
 | 
						|
      <code>type-map</code> 파일을 직접 만드는 것이 더 빠름을
 | 
						|
      명심하라.</p>
 | 
						|
 | 
						|
    <p>사이트에 내용협상이 필요하다면 협상을 위해 <code>Options
 | 
						|
    MultiViews</code> 지시어를 사용하기보다 <code>type-map</code>
 | 
						|
    파일을 고려하라. 협상방법에 대한 자세한 설명과
 | 
						|
    <code>type-map</code> 파일을 만드는 방법은 <a
 | 
						|
    href="../content-negotiation.html">내용협상</a> 문서를 참고하라.</p>
 | 
						|
 | 
						|
    </section>
 | 
						|
 | 
						|
    <section>
 | 
						|
 | 
						|
      <title>메모리대응 (memory-mapping)</title>
 | 
						|
 | 
						|
      <p>예를 들어, server-side-include를 처리하는 등 아파치
 | 
						|
      2.0이 전송할 파일을 읽을때 운영체제가 <code>mmap(2)</code>
 | 
						|
      등을 지원한다면 파일을 메모리대응한다.</p>
 | 
						|
 | 
						|
      <p>여러 플래폼에서 메모리대응을 성능을 향상한다. 그러나
 | 
						|
      메모리대응이 서버의 성능을 떨어트리고 심지어 안정성을
 | 
						|
      해치는 경우가 있다:</p>
 | 
						|
 | 
						|
      <ul>
 | 
						|
        <li>
 | 
						|
          <p>어떤 운영체제에서 <code>mmap</code>은 CPU 개수가
 | 
						|
          많아질때 <code>read(2)</code> 만큼 확장성이 좋지 않다.
 | 
						|
          예를 들어, 다중프로세서 Solaris 서버에서 아파치 2.0은
 | 
						|
          종종 <code>mmap</code>을 사용하지 않을때 서버가 처리한
 | 
						|
          파일을 더 빨리 전송한다.</p>
 | 
						|
        </li>
 | 
						|
 | 
						|
        <li>
 | 
						|
          <p>NFS 마운트한 파일시스템에 있는 파일을 메모리대응하는
 | 
						|
          도중에 다른 NFS 클라이언트에 있는 프로세스가 파일을
 | 
						|
          지우거나 파일크기를 줄이면, 웹서버 프로세스가 다음
 | 
						|
          번에 메모리대응한 파일내용을 읽을때 bus error가 발생할
 | 
						|
          수 있다.</p>
 | 
						|
        </li>
 | 
						|
      </ul>
 | 
						|
 | 
						|
      <p>위의 조건에 해당하면 전송하는 파일을 메모리대응하지
 | 
						|
      않도록 <code>EnableMMAP off</code>를 사용해야 한다. (주의:
 | 
						|
      이 지시어는 디렉토리별로 변경할 수 있다.)</p>
 | 
						|
 | 
						|
    </section>
 | 
						|
 | 
						|
    <section>
 | 
						|
 | 
						|
      <title>Sendfile</title>
 | 
						|
 | 
						|
      <p>아파치는 운영체제가 <code>sendfile(2)</code>을 지원하면
 | 
						|
      커널 sendfile을 사용하여 -- 예를 들어, 정적 파일을 서비스할때
 | 
						|
      -- 전송할 파일을 직접 읽지않을 수 있다.</p>
 | 
						|
 | 
						|
      <p>여러 플래폼에서 sendfile을 사용하면 read와 send를 따로
 | 
						|
      할 필요가 없어서 빨라진다. 그러나 sendfile을 사용하면
 | 
						|
      웹서버의 안정성을 해치게되는 경우가 있다:</p>
 | 
						|
 | 
						|
      <ul>
 | 
						|
        <li>
 | 
						|
          <p>sendfile 지원이 잘못되었고 컴파일 시스템이 이점을
 | 
						|
          발견하지 못하는 플래폼이 있다. 특히 다른 컴퓨터에서
 | 
						|
          실행파일을 컴파일하여 sendfile 지원이 잘못된 컴퓨터로
 | 
						|
          가져온 경우에 가능하다.</p>
 | 
						|
        </li>
 | 
						|
        <li>
 | 
						|
          <p>커널은 자신의 캐쉬를 사용하여 NFS로 마운트한 파일을
 | 
						|
          안정적으로 서비스할 수 없는 경우가 있다.</p>
 | 
						|
        </li>
 | 
						|
      </ul>
 | 
						|
 | 
						|
      <p>위의 조건에 해당하면 파일을 sendfile 전송하지 않도록
 | 
						|
      <code>EnableSendfile off</code>를 사용해야 한다. (주의:
 | 
						|
      이 지시어는 디렉토리별로 변경할 수 있다.)</p>
 | 
						|
 | 
						|
    </section>
 | 
						|
 | 
						|
    <section id="process">
 | 
						|
 | 
						|
      <title>프로세스 생성</title>
 | 
						|
 | 
						|
      <p>아파치 1.3 이전에는 <directive
 | 
						|
      module="prefork">MinSpareServers</directive>, <directive
 | 
						|
      module="prefork">MaxSpareServers</directive>, <directive
 | 
						|
      module="mpm_common">StartServers</directive> 설정이 모두
 | 
						|
      벤치마크 결과에 큰 영향을 미쳤다. 특히 아파치는 작업을
 | 
						|
      서비스하기위해 충분한 자식수에 다다를 때까지 "도달" 기간이
 | 
						|
      필요했다. 처음 <directive
 | 
						|
      module="mpm_common">StartServers</directive>개 자식을
 | 
						|
      만든후, <directive module="prefork">MinSpareServers</directive>
 | 
						|
      설정값까지 초당 자식을 하나씩 만들었다. 그래서 <directive
 | 
						|
      module="mpm_common">StartServers</directive> 기본값이
 | 
						|
      <code>5</code>인 서버에 클라이언트 100개가 동시에 접속하면
 | 
						|
      부하를 처리하기에 충분한 자식을 만들기까지 95초가 걸렸다.
 | 
						|
      자주 재시작하지 않는 실제 서버에서는 잘 동작하지만, 10분간만
 | 
						|
      실행하는 벤치마크 결과는 매우 나쁘게 나온다.</p>
 | 
						|
 | 
						|
      <p>초당 한개 규칙은 자식을 새로 시작하면서 서버에 무리를
 | 
						|
      주지 않으려고 정했다. 컴퓨터가 자식을 시작하느라 바쁘면
 | 
						|
      요청을 서비스할 수 없다. 그러나 이 규칙이 아파치의 체감
 | 
						|
      성능에 악영향을 주어 변경하였다. 아파치 1.3에서 초당 한개
 | 
						|
      규칙은 완화되었다. 코드는 자식 한개를 만들고, 1초 쉬고,
 | 
						|
      두개를 만들고, 1초 쉬고, 네개를 만들고, 이런 식으로 초당
 | 
						|
      자식을 32개 만들때까지 지수로 증가한다. 자식수가 <directive
 | 
						|
      module="prefork">MinSpareServers</directive> 설정에 다다르면
 | 
						|
      증가를 중단한다.</p>
 | 
						|
 | 
						|
      <p>이 경우 반응속도가 빨라져서 <directive module="prefork"
 | 
						|
      >MinSpareServers</directive>, <directive module="prefork"
 | 
						|
      >MaxSpareServers</directive>, <directive module="mpm_common"
 | 
						|
      >StartServers</directive>를 거의 설정할 필요가 없다. 일초에
 | 
						|
      자식을 4개 이상 생성하면 <directive
 | 
						|
      module="core">ErrorLog</directive>에 기록한다. 이런 오류문이
 | 
						|
      많이 보이면 이 설정들을 조절하길 바란다.
 | 
						|
      <module>mod_status</module> 결과가 도움이 될 것이다.</p>
 | 
						|
 | 
						|
    <p>프로세스 생성과 관련하여 <directive
 | 
						|
    module="mpm_common">MaxRequestsPerChild</directive> 설정은
 | 
						|
    프로세스를 종료한다. 기본값은 자식당 처리할 요청수에 제한이
 | 
						|
    없다는 <code>0</code>이다. 현재 설정이 <code>30</code>과
 | 
						|
    같이 매우 작은 값으로 설정되있다면, 값을 상당히 높힐 필요가
 | 
						|
    있다. SunOS나 오래된 Solaris 버전을 사용한다면, 메모리유출때문에
 | 
						|
    이 값을 <code>10000</code> 정도로 설정하라.</p>
 | 
						|
 | 
						|
    <p>연결유지(keep-alive)를 사용한다면 자식들은 이미 열린
 | 
						|
    연결에서 추가 요청을 기다리며 아무것도 하지않기때문에 계속
 | 
						|
    바쁘다. <directive module="core">KeepAliveTimeout</directive>의
 | 
						|
    기본값 <code>15</code> 초는 이런 현상을 최소화한다. 네트웍
 | 
						|
    대역폭과 서버 자원 간의 균형이 맞게 설정한다. <a
 | 
						|
    href="http://www.research.digital.com/wrl/techreports/abstracts/95.4.html">
 | 
						|
    연결유지의 대부분의 이점이 사라지기때문에</a> 어떤 경우에도
 | 
						|
    이 값을 <code>60</code> 초 이상으로 올리지 마라.</p>
 | 
						|
 | 
						|
    </section>
 | 
						|
 | 
						|
  </section>
 | 
						|
 | 
						|
  <section id="compiletime">
 | 
						|
 | 
						|
    <title>컴파일시 설정에 대해서</title>
 | 
						|
 | 
						|
    <section>
 | 
						|
 | 
						|
      <title>MPM 선택</title>
 | 
						|
 | 
						|
      <p>아파치 2.x는 <a href="../mpm.html">다중처리모듈</a>
 | 
						|
      (MPMs)이라는 교체할 수 있는 동기화 모델을 지원한다. 아파치를
 | 
						|
      컴파일할때 MPM을 선택해야 한다. <module>beos</module>,
 | 
						|
      <module>mpm_netware</module>, <module>mpmt_os2</module>,
 | 
						|
      <module>mpm_winnt</module>와 같이 특정 플래폼에서만 사용할
 | 
						|
      수 있는 MPM도 있다. 일반적인 유닉스류 시스템은 여러 MPM
 | 
						|
      중에 하나를 선택할 수 있다. 웹서버의 속도와
 | 
						|
      확장성(scalability)은 어떤 MPM을 선택했냐에 달렸다:</p>
 | 
						|
 | 
						|
      <ul>
 | 
						|
 | 
						|
        <li><module>worker</module> MPM은 여러 자식 프로세스가
 | 
						|
        각각 여러 쓰레드를 사용한다. 각 쓰레드는 한번에 한 연결을
 | 
						|
        담당한다. 일반적으로 worker는 prefork MPM 보다 적은
 | 
						|
        메모리를 사용하므로 통신량이 많은 서버에 적절하다.</li>
 | 
						|
 | 
						|
        <li><module>prefork</module> MPM은 쓰레드가 한개인 자식
 | 
						|
        프로세스를 여러개 사용한다. 각 프로세스는 한번에 한
 | 
						|
        연결을 담당한다. 여러 시스템에서 prefork의 속도는 worker와
 | 
						|
        비슷하지만, 더 많은 메모리를 사용한다. 다음과 같은 상황에서
 | 
						|
        쓰레드를 사용하지 않는 prefork 방식이 worker에 비해
 | 
						|
        이점을 가진다: 쓰레드에 안전하지 (thread-safe) 않은
 | 
						|
        제삼자가 만든 모듈을 사용할 수 있고, 쓰레드 디버깅 지원이
 | 
						|
        빈약한 플래폼에서 쉽게 디버깅할 수 있다.</li>
 | 
						|
 | 
						|
      </ul>
 | 
						|
 | 
						|
      <p>이 MPM들과 다른 MPM에 대해 더 자세한 정보는 MPM <a
 | 
						|
      href="../mpm.html">문서</a>를 참고하길 바란다.</p>
 | 
						|
 | 
						|
    </section>
 | 
						|
 | 
						|
    <section id="modules">
 | 
						|
 | 
						|
        <title>모듈</title>
 | 
						|
 | 
						|
        <p>메모리 사용량이 성능에서 가장 중요한 요인이기때문에
 | 
						|
        실제로 사용하지 않는 모듈을 제거해보자. 모듈을 <a
 | 
						|
        href="../dso.html">DSO</a>로 컴파일했다면 간단히 그
 | 
						|
        모듈에 대한 <directive
 | 
						|
        module="mod_so">LoadModule</directive> 지시어를 주석처리하면
 | 
						|
        된다. 그래서 모듈을 제거하고 실행하여 사이트가 모듈없이도
 | 
						|
        정상적으로 동작하는지 살펴볼 수 있다.</p>
 | 
						|
 | 
						|
        <p>반대로 모듈이 아파치 실행파일에 정적으로 링크되있다면
 | 
						|
        원하지 않는 모듈을 제거하기위해 아파치를 재컴파일해야
 | 
						|
        한다.</p>
 | 
						|
 | 
						|
        <p>여기서 당연히 어떤 모듈을 사용하고 사용하지 말지
 | 
						|
        의문이 생긴다. 정답은 웹사이트마다 다르다. 그러나 아마도
 | 
						|
        <em>최소한</em> <module>mod_mime</module>,
 | 
						|
        <module>mod_dir</module>, <module>mod_log_config</module>
 | 
						|
        모듈은 사용할 것이다. 물론 웹사이트에 로그파일이 필요없다면
 | 
						|
        <code>mod_log_config</code>는 없어도 된다. 그러나 추천하지
 | 
						|
        않는다.</p>
 | 
						|
 | 
						|
    </section>
 | 
						|
 | 
						|
    <section>
 | 
						|
 | 
						|
      <title>Atomic 명령</title>
 | 
						|
 | 
						|
      <p><module>mod_cache</module> 같은 모듈과 최근 개발중인
 | 
						|
      worker MPM은 APR의 atomic API를 사용한다. 이 API는 경량급
 | 
						|
      쓰레드 동기화를 위할 atomic 명령을 제공한다.</p>
 | 
						|
 | 
						|
      <p>기본적으로 APR은 각 운영체제/CPU 플래폼에서 가장 효율적인
 | 
						|
      방법을 사용하여 이 명령을 구현한다. 예를 들어, 여러 최신
 | 
						|
      CPU에는 하드웨어로 atomic compare-and-swap (CAS) 연산을
 | 
						|
      하는 명령어가 있다. 그러나 어떤 플래폼에서 APR은 이런
 | 
						|
      명령어가 없는 오래된 CPU와 호환성을 위해 더 느린 mutex기반
 | 
						|
      구현을 기본적으로 사용한다. 이런 플래폼에서 아파치를
 | 
						|
      컴파일할때 아파치를 최신 CPU에서만 실행할 계획이라면,
 | 
						|
      아파치를 구성할때 <code>--enable-nonportable-atomics</code>
 | 
						|
      옵션을 사용하여 더 빠른 atomic 구현을 선택할 수 있다:</p>
 | 
						|
 | 
						|
      <example>
 | 
						|
        ./buildconf<br />
 | 
						|
        ./configure --with-mpm=worker --enable-nonportable-atomics=yes
 | 
						|
      </example>
 | 
						|
 | 
						|
      <p><code>--enable-nonportable-atomics</code> 옵션은 다음과
 | 
						|
      같은 플래폼에 영향이 있다:</p>
 | 
						|
 | 
						|
      <ul>
 | 
						|
 | 
						|
        <li>SPARC에서 Solaris<br />
 | 
						|
            기본적으로 APR은 Solaris/SPARC에서 mutex기반 atomic을
 | 
						|
            사용한다. 그러나 구성할때
 | 
						|
            <code>--enable-nonportable-atomics</code>를 사용하면
 | 
						|
            APR은 빠른 하드웨어 compare-and-swap을 위한 SPARC
 | 
						|
            v8plus 명령어를 사용한다. 이 옵션을 사용하면 atomic
 | 
						|
            명령이 더 효율적이지만 (CPU를 덜 사용하고 더 높은
 | 
						|
            동기화가 가능하다), 컴파일한 실행파일은 UltraSPARC
 | 
						|
            칩에서만 실행할 수 있다.
 | 
						|
        </li>
 | 
						|
 | 
						|
        <li>Linux on x86<br />
 | 
						|
            기본적으로 APR은 리눅스에서 mutex기반 atomic을
 | 
						|
            사용한다. 그러나 구성할때
 | 
						|
            <code>--enable-nonportable-atomics</code>를 사용하면
 | 
						|
            APR은 빠른 하드웨어 compare-and-swap을 위한 486
 | 
						|
            명령어를 사용한다. 더 효율적인 atomic 명령이 가능하지만,
 | 
						|
            컴파일한 실행파일은 486 이상 칩에서만 (386은 안된다)
 | 
						|
            실행할 수 있다.
 | 
						|
        </li>
 | 
						|
 | 
						|
      </ul>
 | 
						|
 | 
						|
    </section>
 | 
						|
 | 
						|
    <section>
 | 
						|
 | 
						|
      <title>mod_status와 ExtendedStatus On</title>
 | 
						|
 | 
						|
      <p>아파치를 컴파일할때 <module>mod_status</module>를 포함하고
 | 
						|
      실행할때 <code>ExtendedStatus On</code>을 설정하면 아파치는
 | 
						|
      요청을 받을때마다 <code>gettimeofday(2)</code>(혹은 운영체제에
 | 
						|
      따라 <code>times(2)</code>)를 두번 호출하고 (1.3 이전에는)
 | 
						|
      <code>time(2)</code>도 추가로 여러번 호출한다. 상태 보고서에
 | 
						|
      동작시간이 필요하기 때문이다. 최상의 성능을 얻으려면
 | 
						|
      (기본값인) <code>ExtendedStatus off</code>를 설정한다.</p>
 | 
						|
 | 
						|
    </section>
 | 
						|
 | 
						|
    <section>
 | 
						|
 | 
						|
      <title>accept 직렬화 - 여러 소켓</title>
 | 
						|
 | 
						|
    <note type="warning"><title>주의:</title>
 | 
						|
      <p> 아래 문서는 아파치 웹서버 2.0 버전에서 변경된 내용을
 | 
						|
      담고 있지 않다. 아직도 유효한 정보가 있지만, 주의해서
 | 
						|
      사용하길 바란다.</p>
 | 
						|
    </note>
 | 
						|
 | 
						|
      <p>유닉스 소켓 API의 단점을 설명한다. 웹서버가 여러 포트
 | 
						|
      혹은 여러 주소를 기다리기위해 여러 <directive
 | 
						|
      module="mpm_common">Listen</directive>을 사용한다고 가정하자.
 | 
						|
      연결이 가능한지 각 소켓을 검사하기위해 아파치는
 | 
						|
      <code>select(2)</code>를 사용한다. <code>select(2)</code>는
 | 
						|
      소켓에 기다리고 있는 연결이 <em>없는지</em> 혹은 <em>최소한
 | 
						|
      한개</em> 있는지 알려준다. 아파치에는 여러 자식이 있고,
 | 
						|
      쉬고 있는 모든 자식은 동시에 새로운 연결을 검사한다. 원래
 | 
						|
      구현은 다음과 비슷하다 (이 예는 코드에서 가져오지 않았다.
 | 
						|
      단지 설명하기위한 용도로 만들었다.):</p>
 | 
						|
 | 
						|
      <example>
 | 
						|
        for (;;) {<br />
 | 
						|
        <indent>
 | 
						|
          for (;;) {<br />
 | 
						|
          <indent>
 | 
						|
            fd_set accept_fds;<br />
 | 
						|
            <br />
 | 
						|
            FD_ZERO (&accept_fds);<br />
 | 
						|
            for (i = first_socket; i <= last_socket; ++i) {<br />
 | 
						|
            <indent>
 | 
						|
              FD_SET (i, &accept_fds);<br />
 | 
						|
            </indent>
 | 
						|
            }<br />
 | 
						|
            rc = select (last_socket+1, &accept_fds, NULL, NULL, NULL);<br />
 | 
						|
            if (rc < 1) continue;<br />
 | 
						|
            new_connection = -1;<br />
 | 
						|
            for (i = first_socket; i <= last_socket; ++i) {<br />
 | 
						|
            <indent>
 | 
						|
              if (FD_ISSET (i, &accept_fds)) {<br />
 | 
						|
              <indent>
 | 
						|
                new_connection = accept (i, NULL, NULL);<br />
 | 
						|
                if (new_connection != -1) break;<br />
 | 
						|
              </indent>
 | 
						|
              }<br />
 | 
						|
            </indent>
 | 
						|
            }<br />
 | 
						|
            if (new_connection != -1) break;<br />
 | 
						|
          </indent>
 | 
						|
          }<br />
 | 
						|
          process the new_connection;<br />
 | 
						|
        </indent>
 | 
						|
        }
 | 
						|
      </example>
 | 
						|
 | 
						|
      <p>그러나 위의 단순한 구현에는 심각한 고갈(starvation)
 | 
						|
      문제가 있다. 여러 자식이 동시에 이 반복문을 실행하면,
 | 
						|
      요청을 기다리며 모두 <code>select</code>에서 멈춘다. 이때
 | 
						|
      어떤 소켓에 요청이 하나라도 들어오면 모든 자식이 깨어난다
 | 
						|
      (깨어나는 자식의 개수는 운영체제와 타이밍에 따라 다르다).
 | 
						|
      이들은 모두 연결을 <code>accept</code>하길 시도한다. 그러나
 | 
						|
      (아직도 한 연결만 대기중이라면) 한 자식만 성공하고, 나머지는
 | 
						|
      <code>accept</code>에서 <em>멈춘다.</em> 그러면 이 자식들은
 | 
						|
      한 소켓의 요청만을 서비스하도록 묶여서, 그 소켓으로 새로운
 | 
						|
      요청이 충분히 들어와서 모든 자식을 깨울때까지 정지해있다.
 | 
						|
      이런 고갈 문제는 <a
 | 
						|
      href="http://bugs.apache.org/index/full/467">PR#467</a>에
 | 
						|
      처음 보고되었다. 최소한 두가지 해결책이 있다.</p>
 | 
						|
 | 
						|
      <p>한가지는 소켓을 대기하지 않도록 (non-blocking) 만드는
 | 
						|
      방법이다. 이 경우 자식이 <code>accept</code>를 해도 멈추지
 | 
						|
      않고, 즉시 진행할 수 있다. 그러나 CPU 시간을 낭비한다.
 | 
						|
      <code>select</code>에서 쉬는 자식이 10개 있고, 새로 연결이
 | 
						|
      한개 들어왔다고 가정하자. 그러면 이 자식중 9개는 깨어나서
 | 
						|
      연결을 <code>accept</code>하길 시도하고 실패하면 아무
 | 
						|
      일도 하지 않고 다시 <code>select</code>를 반복한다. 다시
 | 
						|
      <code>select</code>로 돌아올 때까지 어떤 자식도 다른 소켓에
 | 
						|
      들어온 요청을 서비스하지 않는다. (다중프로세서 컴퓨터에서)
 | 
						|
      쉬는 자식 개수만큼 CPU 개수가 있는 드문 경우가 아니라면
 | 
						|
      이 해결책은 별로 좋아보이지 않는다.</p>
 | 
						|
 | 
						|
      <p>다른 방법은 아파치가 사용하는 방법으로 내부 반복문에
 | 
						|
      한 자식만을 들여보낸다. 반복문은 다음과 같다 (차이를
 | 
						|
      강조했음):</p>
 | 
						|
 | 
						|
      <example>
 | 
						|
        for (;;) {<br />
 | 
						|
        <indent>
 | 
						|
          <strong>accept_mutex_on ();</strong><br />
 | 
						|
          for (;;) {<br />
 | 
						|
          <indent>
 | 
						|
            fd_set accept_fds;<br />
 | 
						|
            <br />
 | 
						|
            FD_ZERO (&accept_fds);<br />
 | 
						|
            for (i = first_socket; i <= last_socket; ++i) {<br />
 | 
						|
            <indent>
 | 
						|
              FD_SET (i, &accept_fds);<br />
 | 
						|
            </indent>
 | 
						|
            }<br />
 | 
						|
            rc = select (last_socket+1, &accept_fds, NULL, NULL, NULL);<br />
 | 
						|
            if (rc < 1) continue;<br />
 | 
						|
            new_connection = -1;<br />
 | 
						|
            for (i = first_socket; i <= last_socket; ++i) {<br />
 | 
						|
            <indent>
 | 
						|
              if (FD_ISSET (i, &accept_fds)) {<br />
 | 
						|
              <indent>
 | 
						|
                new_connection = accept (i, NULL, NULL);<br />
 | 
						|
                if (new_connection != -1) break;<br />
 | 
						|
              </indent>
 | 
						|
              }<br />
 | 
						|
            </indent>
 | 
						|
            }<br />
 | 
						|
            if (new_connection != -1) break;<br />
 | 
						|
          </indent>
 | 
						|
          }<br />
 | 
						|
          <strong>accept_mutex_off ();</strong><br />
 | 
						|
          process the new_connection;<br />
 | 
						|
        </indent>
 | 
						|
        }
 | 
						|
      </example>
 | 
						|
 | 
						|
      <p><code>accept_mutex_on</code>과 <code>accept_mutex_off</code>
 | 
						|
      <a id="serialize" name="serialize">함수</a>는 mutex 세마포어를
 | 
						|
      구현한다. 한번에 오직 한 자식만이 mutex를 가질 수 있다.
 | 
						|
      mutex를 구현하는 방법은 여러가지이다. 구현 방법은 (1.3
 | 
						|
      이전) <code>src/conf.h</code>나 (1.3과 그 이후)
 | 
						|
      <code>src/include/ap_config.h</code>에 정의되있다. 어떤
 | 
						|
      아키텍쳐는 잠금(locking) 방법을 선택하지 않기때문에, 이런
 | 
						|
      아키텍쳐에서 여러 <directive
 | 
						|
      module="mpm_common">Listen</directive> 지시어를 사용하면
 | 
						|
      위험하다.</p>
 | 
						|
 | 
						|
      <p>실행시 <directive
 | 
						|
      module="mpm_common">AcceptMutex</directive> 지시어를 사용하여
 | 
						|
      mutex 구현을 변경할 수 있다.</p>
 | 
						|
 | 
						|
      <dl>
 | 
						|
        <dt><code>AcceptMutex flock</code></dt>
 | 
						|
 | 
						|
        <dd>
 | 
						|
          <p>이 방법은 잠금파일을 잠그기위해 <code>flock(2)</code>
 | 
						|
          시스템호출을 사용한다 (잠금파일 위치는 <directive
 | 
						|
          module="mpm_common">LockFile</directive> 지시어로 지정).</p>
 | 
						|
        </dd>
 | 
						|
 | 
						|
        <dt><code>AcceptMutex fcntl</code></dt>
 | 
						|
 | 
						|
        <dd>
 | 
						|
          <p>이 방법은 잠금파일을 잠그기위해 <code>fcntl(2)</code>
 | 
						|
          시스템호출을 사용한다 (잠금파일 위치는 <directive
 | 
						|
          module="mpm_common">LockFile</directive> 지시어로 지정).</p>
 | 
						|
        </dd>
 | 
						|
 | 
						|
        <dt><code>AcceptMutex sysvsem</code></dt>
 | 
						|
 | 
						|
        <dd>
 | 
						|
          <p>(1.3과 그 이후) 이 방법을 SysV식 세마포어를 사용하여
 | 
						|
          mutex를 구현한다. 불행히도 SysV식 세마포어는 나쁜
 | 
						|
          부작용이 있다. 하나는 아파치가 세마포어를 정리하지
 | 
						|
          않고 죽을 수 있는 점이다 (<code>ipcs(8)</code> manpage
 | 
						|
          참고). 다른 하나는 웹서버와 동일한 uid로 실행하는
 | 
						|
          CGI가 (<em>즉,</em> <code>suexec</code>나
 | 
						|
          <code>cgiwrapper</code>를 사용하지않는 한 모든 CGI)
 | 
						|
          세마포어 API를 사용하여 서비스거부공격을 할 수 있는
 | 
						|
          점이다. 이런 이유때문에 IRIX를 제외한 아키텍쳐에서
 | 
						|
          이 방법을 사용하지 않는다 (대부분의 IRIX 컴퓨터에서
 | 
						|
          앞의 두 방법은 지나치게 버겁다).</p>
 | 
						|
        </dd>
 | 
						|
 | 
						|
        <dt><code>AcceptMutex pthread</code></dt>
 | 
						|
 | 
						|
        <dd>
 | 
						|
          <p>(1.3과 그 이후) 이 방법은 POSIX mutex를 사용하기때문에
 | 
						|
          POSIX 쓰레드 규약을 완전히 구현한 아키텍쳐라면 모두
 | 
						|
          사용가능하지만, (2.5 이후) Solaris에서만 그것도 특정
 | 
						|
          구성에서만 동작하는 듯하다. 이 방법을 시도해본다면
 | 
						|
          서버가 멈춰서 응답을 안하는지 살펴봐야 한다. 정적
 | 
						|
          내용만 서비스하는 서버는 잘 동작하는 것 같다.</p>
 | 
						|
        </dd>
 | 
						|
 | 
						|
        <dt><code>AcceptMutex posixsem</code></dt>
 | 
						|
 | 
						|
        <dd>
 | 
						|
          <p>(2.0과 그 이후) 이 방법은 POSIX 세마포어를 사용한다.
 | 
						|
          mutex를 가진 프로세스의 쓰레드가 죽는다면(segfault)
 | 
						|
          세마포어 소유권이 회복되지 않아서 웹서버가 멈춘다.</p>
 | 
						|
        </dd>
 | 
						|
 | 
						|
      </dl>
 | 
						|
 | 
						|
      <p>시스템에 위 목록에 없는 직렬화(serialization) 방법이
 | 
						|
      있다면 그 방법을 사용하는 코드를 APR에 추가할 가치가 있다.</p>
 | 
						|
 | 
						|
      <p>고려는 해봤지만 구현하지않은 다른 방법은 부분적으로
 | 
						|
      반복문을 직렬화하는 방법이다. 즉, 프로세서를 몇개만 들여보내는
 | 
						|
      것이다. 이 방법은 여러 자식을 동시에 실행할 수 있어서
 | 
						|
      직렬화때문에 전체 대역폭을 활용하지 못하는 다중프로세서
 | 
						|
      컴퓨터에서만 관심을 가져볼 수 있다. 앞으로 살펴볼 부분이지만,
 | 
						|
      매우 병렬화된 웹서버가 흔하지 않아서 우선순위가 낮다.</p>
 | 
						|
 | 
						|
      <p>최상의 성능을 얻기위해서는 여러 <directive
 | 
						|
      module="mpm_common">Listen</directive> 문을 사용하지 않는
 | 
						|
      것이 이상적이다. 그러나 계속 설명한다.</p>
 | 
						|
 | 
						|
    </section>
 | 
						|
 | 
						|
    <section>
 | 
						|
 | 
						|
      <title>accept 직렬화 - 소켓 한개</title>
 | 
						|
 | 
						|
      <p>앞의 설명은 다중소켓 서버에는 좋지만, 소켓이 한개인
 | 
						|
      서버는 어떤가? 연결이 도착할때까지 모든 자식이
 | 
						|
      <code>accept(2)</code>에서 멈춰있기때문에 이론상 같은
 | 
						|
      문제가 발생하지 않고, 고갈 문제도 없다. 그러나 실제로는
 | 
						|
      앞에서 말한 대기하지 않는 (non-blocking) 방법에서 발생하는
 | 
						|
      "공회전(spinning)" 현상을 감추고 있다. 대부분의 TCP 스택은
 | 
						|
      연결이 도착하면 커널이 <code>accept</code>에서 멈춰있는
 | 
						|
      모든 자식을 깨우도록 구현되있다. 프로세스중 한개가 연결을
 | 
						|
      얻고 사용자영역으로 돌아가고, 나머지는 커널에서 공회전하여
 | 
						|
      연결이 없음을 발견하면 다시 잠을 잔다. 사용자영역 코드에서는
 | 
						|
      이런 공회전을 알 수 없지만, 분명히 존재한다. 그래서 다중소켓의
 | 
						|
      대기하지 않는 방법과 동일하게 부하를 높이는 불필요한 행동이
 | 
						|
      일어난다.</p>
 | 
						|
 | 
						|
      <p>그래서 우리는 여러 아키텍쳐에서 소켓이 한개인 경우에도
 | 
						|
      직렬화하면 더 "잘" 동작함을 발견했다. 그래서 거의 대부분의
 | 
						|
      경우 기본적으로 직렬화를 사용한다. 리눅스에서 (커널 2.0.30,
 | 
						|
      128Mb 메모리에 듀얼 Pentium pro) 실험한 결과 소켓 한개를
 | 
						|
      직렬화하면 하지 않은 경우에 비해 초당 요청이 3% 미만
 | 
						|
      줄어들었다. 그러나 직렬화를 하지 않은 경우 요청당 100ms
 | 
						|
      지연이 발생했다. 이 지연은 아마도 LAN에서 발생하는 긴
 | 
						|
      연결선때문일 것이다. 소켓이 한개인 경우 직렬화를 사용하지
 | 
						|
      않으려면 <code>SINGLE_LISTEN_UNSERIALIZED_ACCEPT</code>를
 | 
						|
      정의한다.</p>
 | 
						|
 | 
						|
    </section>
 | 
						|
 | 
						|
    <section>
 | 
						|
 | 
						|
      <title>Close 지연(lingering)</title>
 | 
						|
 | 
						|
      <p><a
 | 
						|
      href="http://www.ics.uci.edu/pub/ietf/http/draft-ietf-http-connection-00.txt">
 | 
						|
      draft-ietf-http-connection-00.txt</a> 8절에서 설명하듯이
 | 
						|
      <strong>안정적인</strong> 웹서버가 되려면, 통신의 양 방향을
 | 
						|
      독립적으로 닫을 수 있어야 한다 (TCP 연결은 쌍방향이고,
 | 
						|
      방향은 서로 독립적이다). 이점을 다른 서버에서는 자주
 | 
						|
      간과하지만, 아파치는 1.2부터 정확히 구현해왔다.</p>
 | 
						|
 | 
						|
      <p>이 기능을 부주의하게 아파치에 추가했을때 여러 유닉스
 | 
						|
      버전에서 많은 문제가 발생했다. TCP 규약은
 | 
						|
      <code>FIN_WAIT_2</code>에 타임아웃이 있다고 정하지 않았지만,
 | 
						|
      금지하지도 않았다. 타임아웃이 없는 시스템에서 아파치 1.2는
 | 
						|
      많은 소켓을 영원히 <code>FIN_WAIT_2</code> 상태로 만들었다.
 | 
						|
      많은 경우 이 문제는 제작사가 제공하는 최신 TCP/IP 패치를
 | 
						|
      적용하여 해결할 수 있다. 그러나 제작사가 패치를 발표하지
 | 
						|
      않는 경우가 (<em>즉,</em> SunOS4 -- 소스 라이선스가 있는
 | 
						|
      사람은 직접 패치할 수 있지만) 있기때문에 이 기능을 사용하지
 | 
						|
      않기로 결정했다.</p>
 | 
						|
 | 
						|
      <p>방법은 두가지다. 하나는 소켓 옵션 <code>SO_LINGER</code>를
 | 
						|
      사용하는 방법이다. 그러나 불행히도 대부분의 TCP/IP 스택은
 | 
						|
      이 옵션을 올바로 구현하지 않았다. 올바로 구현한 스택에서
 | 
						|
      조차도 (<em>즉,</em> 리눅스 2.0.31) 이 방법은 다음 방법보다
 | 
						|
      더 cpu를 잡아먹는다.</p>
 | 
						|
 | 
						|
      <p>아파치는 보통 (<code>http_main.c</code>에 있는)
 | 
						|
      <code>lingering_close</code>라는 함수를 사용한다. 이 함수는
 | 
						|
      대충 다음과 같다:</p>
 | 
						|
 | 
						|
      <example>
 | 
						|
        void lingering_close (int s)<br />
 | 
						|
        {<br />
 | 
						|
        <indent>
 | 
						|
          char junk_buffer[2048];<br />
 | 
						|
          <br />
 | 
						|
          /* shutdown the sending side */<br />
 | 
						|
          shutdown (s, 1);<br />
 | 
						|
          <br />
 | 
						|
          signal (SIGALRM, lingering_death);<br />
 | 
						|
          alarm (30);<br />
 | 
						|
          <br />
 | 
						|
          for (;;) {<br />
 | 
						|
          <indent>
 | 
						|
            select (s for reading, 2 second timeout);<br />
 | 
						|
            if (error) break;<br />
 | 
						|
            if (s is ready for reading) {<br />
 | 
						|
            <indent>
 | 
						|
              if (read (s, junk_buffer, sizeof (junk_buffer)) <= 0) {<br />
 | 
						|
              <indent>
 | 
						|
                break;<br />
 | 
						|
              </indent>
 | 
						|
              }<br />
 | 
						|
              /* just toss away whatever is here */<br />
 | 
						|
            </indent>
 | 
						|
            }<br />
 | 
						|
          </indent>
 | 
						|
          }<br />
 | 
						|
          <br />
 | 
						|
          close (s);<br />
 | 
						|
        </indent>
 | 
						|
        }
 | 
						|
      </example>
 | 
						|
 | 
						|
      <p>이 코드는 연결을 닫을때 더 CPU를 사용하지만, 안정적인
 | 
						|
      구현을 위해 필요하다. HTTP/1.1이 더 널리 퍼지고 모든 연결을
 | 
						|
      유지한다면(persistent), 연결을 받는 비용은 여러 요청을
 | 
						|
      처리하면서 상쇄될 것이다. 위험하게도
 | 
						|
      <code>NO_LINGCLOSE</code>를 정의하여 이 기능을 사용하지
 | 
						|
      않을 수 있지만, 절대로 권하지 않는다. 특히 HTTP/1.1
 | 
						|
      파이프라인 <transnote>연결유지 상태에서 응답을 기다리지
 | 
						|
      않고 여러 요청을 보내는 기술</transnote> 연결유지에는
 | 
						|
      <code>lingering_close</code>가 필수적이다 (그리고 <a
 | 
						|
      href="http://www.w3.org/Protocols/HTTP/Performance/Pipeline.html">
 | 
						|
      파이프라인 연결이 더 빠르기때문에</a> 사용하길 바랄 것이다).</p>
 | 
						|
 | 
						|
    </section>
 | 
						|
 | 
						|
    <section>
 | 
						|
 | 
						|
      <title>Scoreboard 파일</title>
 | 
						|
 | 
						|
      <p>아파치의 부모와 자식은 scoreboard라는 것을 통해 서로
 | 
						|
      통신한다. 이상적으로는 scoreboard를 공유메모리로 구현해야
 | 
						|
      한다. 우리 개발자가 해당 운영체제에 접근할 수 있거나 상세한
 | 
						|
      포팅 결과를 받은 경우 보통 공유메모리를 사용하여 구현한다.
 | 
						|
      나머지는 디스크에 있는 파일을 사용하여 구현한다. 디스크에
 | 
						|
      있는 파일은 느리고 신뢰도가 떨어진다 (기능도 더 적다).
 | 
						|
      <code>src/main/conf.h</code> 파일에서 사용하는 아키텍쳐를
 | 
						|
      찾아서 <code>USE_MMAP_SCOREBOARD</code> 혹은
 | 
						|
      <code>USE_SHMGET_SCOREBOARD</code>인지 확인한다. 둘중
 | 
						|
      하나를 (각각 함께 사용할 <code>HAVE_MMAP</code>이나
 | 
						|
      <code>HAVE_SHMGET</code>도 같이) 정의하면 공유메모리 코드를
 | 
						|
      사용한다. 시스템이 다른 종류의 공유메모리를 사용한다면
 | 
						|
      <code>src/main/http_main.c</code> 파일을 수정하여 아파치에서
 | 
						|
      공유메모리를 사용할 수 있도록 훅(hook)을 추가하라. (또한
 | 
						|
      패치를 우리에게 보내주길 바란다.)</p>
 | 
						|
 | 
						|
      <note>역사적 설명: 아파치의 리눅스 버전은 아파치 1.2 버전부터
 | 
						|
      공유메모리를 사용하기 시작했다. 리눅스에서 초기 아파치
 | 
						|
      버전이 느리고 신뢰도가 떨어졌기 때문이다.</note>
 | 
						|
 | 
						|
    </section>
 | 
						|
 | 
						|
    <section>
 | 
						|
 | 
						|
      <title>DYNAMIC_MODULE_LIMIT</title>
 | 
						|
 | 
						|
      <p>모듈을 동적으로 읽어들이지 않는다면 (가능한 조금이라도
 | 
						|
      성능을 높이기위해 이 글을 읽는다면 아마도 모듈을 동적으로
 | 
						|
      읽어들이지 않을 것이다), 서버를 컴파일할때
 | 
						|
      <code>-DDYNAMIC_MODULE_LIMIT=0</code>을 추가한다. 그러면
 | 
						|
      모듈을 동적으로 읽어들이기위해 할당하는 메모리를 절약한다.</p>
 | 
						|
 | 
						|
    </section>
 | 
						|
 | 
						|
  </section>
 | 
						|
 | 
						|
  <section id="trace">
 | 
						|
 | 
						|
    <title>부록: 시스템호출 기록을 자세히 분석하기</title>
 | 
						|
 | 
						|
    <p>다음은 Solaris 8에서 worker MPM을 사용한 아파치 2.0.38의
 | 
						|
    시스템호출 기록(trace)이다. 아래 명령어를 사용하여 기록을
 | 
						|
    얻었다:</p>
 | 
						|
 | 
						|
    <example>
 | 
						|
      truss -l -p <var>httpd_child_pid</var>.
 | 
						|
    </example>
 | 
						|
 | 
						|
    <p><code>-l</code> 옵션을 사용하면 truss는 시스템호출을
 | 
						|
    하는 LWP (lightweight process, 경량급 프로세스--Solaris의
 | 
						|
    커널수준 쓰레드) ID를 같이 기록한다.</p>
 | 
						|
 | 
						|
    <p>다른 시스템에는 <code>strace</code>, <code>ktrace</code>,
 | 
						|
    <code>par</code> 같은 시스템호출 추적 도구가 있다. 결과는
 | 
						|
    비슷하다.</p>
 | 
						|
 | 
						|
    <p>클라이언트는 웹서버에게 크기가 10KB인 정적 파일을 요청한다.
 | 
						|
    정적인 파일을 요청하지 않거나 내용협상하는 요청을 한 경우
 | 
						|
    기록이 매우 다르다 (때로는 매우 알아보기 힘들다).</p>
 | 
						|
 | 
						|
    <example>
 | 
						|
<pre>/67:    accept(3, 0x00200BEC, 0x00200C0C, 1) (sleeping...)
 | 
						|
/67:    accept(3, 0x00200BEC, 0x00200C0C, 1)            = 9</pre>
 | 
						|
    </example>
 | 
						|
 | 
						|
    <p>위에서 연결대기(listener) 쓰레드가 LWP #67에서 실행됨을
 | 
						|
    알 수 있다.</p>
 | 
						|
 | 
						|
    <note><code>accept(2)</code> 직렬화를 사용하지 않음을 주목하라.
 | 
						|
    여러 포트를 기다리지않는 경우 이 플래폼의 worker MPM은
 | 
						|
    기본적으로 직렬화하지 않은 accept를 사용한다.</note>
 | 
						|
 | 
						|
    <example>
 | 
						|
<pre>/65:    lwp_park(0x00000000, 0)                         = 0
 | 
						|
/67:    lwp_unpark(65, 1)                               = 0</pre>
 | 
						|
    </example>
 | 
						|
 | 
						|
    <p>연결은 받아들이고(accept) 연결대기 쓰레드는
 | 
						|
    worker 쓰레드를 깨워서 요청을 처리하게 한다. 아래 기록에서
 | 
						|
    요청을 처리하는 worker 쓰레드가 LWP #65임을 알 수 있다.</p>
 | 
						|
 | 
						|
    <example>
 | 
						|
<pre>/65:    getsockname(9, 0x00200BA4, 0x00200BC4, 1)       = 0</pre>
 | 
						|
    </example>
 | 
						|
 | 
						|
    <p>가상호스트를 구현하기위해 아파치는 연결을 받아들인
 | 
						|
    지역(local) 소켓 주소를 알아야 한다. (가상호스트를 사용하지
 | 
						|
    않거나 <directive module="mpm_common">Listen</directive>
 | 
						|
    지시어에 와일드카드 주소를 사용하지 않은 경우 등) 많은 경우
 | 
						|
    이 호출을 없앨 수 있다. 그러나 아직 이런 최적화 작업이
 | 
						|
    안되있다. </p>
 | 
						|
 | 
						|
    <example>
 | 
						|
<pre>/65:    brk(0x002170E8)                                 = 0
 | 
						|
/65:    brk(0x002190E8)                                 = 0</pre>
 | 
						|
    </example>
 | 
						|
 | 
						|
    <p><code>brk(2)</code> 호출은 힙(heap)에서 메모리를 할당한다.
 | 
						|
    웹서버는 대부분의 요청 처리시 자체 메모리
 | 
						|
    할당자(<code>apr_pool</code>과 <code>apr_bucket_alloc</code>)를
 | 
						|
    사용하기때문에 시스템호출 기록에서 이 시스템호출을 보기가
 | 
						|
    드물다. 이 기록에서 웹서버는 시작하자마자 자체 메모리 할당자가
 | 
						|
    사용할 메모리블록을 얻기위해 <code>malloc(3)</code>을 호출한다.</p>
 | 
						|
 | 
						|
    <example>
 | 
						|
<pre>/65:    fcntl(9, F_GETFL, 0x00000000)                   = 2
 | 
						|
/65:    fstat64(9, 0xFAF7B818)                          = 0
 | 
						|
/65:    getsockopt(9, 65535, 8192, 0xFAF7B918, 0xFAF7B910, 2190656) = 0
 | 
						|
/65:    fstat64(9, 0xFAF7B818)                          = 0
 | 
						|
/65:    getsockopt(9, 65535, 8192, 0xFAF7B918, 0xFAF7B914, 2190656) = 0
 | 
						|
/65:    setsockopt(9, 65535, 8192, 0xFAF7B918, 4, 2190656) = 0
 | 
						|
/65:    fcntl(9, F_SETFL, 0x00000082)                   = 0</pre>
 | 
						|
    </example>
 | 
						|
 | 
						|
    <p>다음 worker 쓰레드는 클라이언트의 연결(파일기술자 9)을
 | 
						|
    대기안함(non-blocking) 상태로 바꾼다. <code>setsockopt(2)</code>와
 | 
						|
    <code>getsockopt(2)</code> 호출은 Solaris의 libc가 소켓에
 | 
						|
    대한 <code>fcntl(2)</code>을 어떻게 처리하는지 보여준다.</p>
 | 
						|
 | 
						|
    <example>
 | 
						|
<pre>/65:    read(9, " G E T   / 1 0 k . h t m".., 8000)     = 97</pre>
 | 
						|
    </example>
 | 
						|
 | 
						|
    <p>worker 쓰레드는 클라이언트로 부터 요청을 읽는다.</p>
 | 
						|
 | 
						|
    <example>
 | 
						|
<pre>/65:    stat("/var/httpd/apache/httpd-8999/htdocs/10k.html", 0xFAF7B978) = 0
 | 
						|
/65:    open("/var/httpd/apache/httpd-8999/htdocs/10k.html", O_RDONLY) = 10</pre>
 | 
						|
    </example>
 | 
						|
 | 
						|
    <p>웹서버 설정은 <code>Options FollowSymLinks</code>와
 | 
						|
    <code>AllowOverride None</code>이다. 그래서 요청한 파일경로의
 | 
						|
    각 디렉토리에 대해 <code>lstat(2)</code>하거나
 | 
						|
    <code>.htaccess</code> 파일을 검사할 필요가 없다. 파일을
 | 
						|
    검사하기위해, 1) 파일이 있는지, 2) 디렉토리가 아닌 일반파일인지,
 | 
						|
    <code>stat(2)</code> 호출만 하면 된다.</p>
 | 
						|
 | 
						|
    <example>
 | 
						|
<pre>/65:    sendfilev(0, 9, 0x00200F90, 2, 0xFAF7B53C)      = 10269</pre>
 | 
						|
    </example>
 | 
						|
 | 
						|
    <p>이 경우 웹서버는 한번의 <code>sendfilev(2)</code> 시스템호출로
 | 
						|
    HTTP 응답헤더와 요청한 파일을 전송할 수 있다. Sendfile 지원여부는
 | 
						|
    운영체제마다 다르다. 다른 시스템이라면 <code>sendfile(2)</code>을
 | 
						|
    호출하기 전에 헤더를 보내기위해 <code>write(2)</code>나
 | 
						|
    <code>writev(2)</code> 호출을 한다.</p>
 | 
						|
 | 
						|
    <example>
 | 
						|
<pre>/65:    write(4, " 1 2 7 . 0 . 0 . 1   -  ".., 78)      = 78</pre>
 | 
						|
    </example>
 | 
						|
 | 
						|
    <p><code>write(2)</code> 호출은 접근로그(access log)에 요청을
 | 
						|
    기록한다. 이 기록에 <code>time(2)</code> 호출이 없음을 주목하라.
 | 
						|
    아파치 1.3과 달리 아파치 2.0은 시간을 알기위해
 | 
						|
    <code>gettimeofday(3)</code>를 사용한다.
 | 
						|
    <code>gettimeofday</code>를 최적화한 리눅스와 Solaris 같은
 | 
						|
    몇몇 운영체제에서는 일반적인 시스템호출 부담이 없다.</p>
 | 
						|
 | 
						|
    <example>
 | 
						|
<pre>/65:    shutdown(9, 1, 1)                               = 0
 | 
						|
/65:    poll(0xFAF7B980, 1, 2000)                       = 1
 | 
						|
/65:    read(9, 0xFAF7BC20, 512)                        = 0
 | 
						|
/65:    close(9)                                        = 0</pre>
 | 
						|
    </example>
 | 
						|
 | 
						|
    <p>worker 쓰레드는 연결을 지연닫기(lingering close)한다.</p>
 | 
						|
 | 
						|
    <example>
 | 
						|
<pre>/65:    close(10)                                       = 0
 | 
						|
/65:    lwp_park(0x00000000, 0)         (sleeping...)</pre>
 | 
						|
    </example>
 | 
						|
 | 
						|
    <p>마지막으로 worker 쓰레드는 방금 전송한 파일을 닫고,
 | 
						|
    연결대기(listener) 쓰레드가 다른 연결을 할당할 때까지
 | 
						|
    정지한다.</p>
 | 
						|
 | 
						|
    <example>
 | 
						|
<pre>/67:    accept(3, 0x001FEB74, 0x001FEB94, 1) (sleeping...)</pre>
 | 
						|
    </example>
 | 
						|
 | 
						|
    <p>그동안 연결대기 쓰레드는 연결을 (모든 worker가 작업중이면
 | 
						|
    연결대기 쓰레드를 멈추는 worker MPM의 흐름제어 기능에 따라)
 | 
						|
    worker 쓰레드에 할당하자마자 다른 연결을 받아들일 수 있다.
 | 
						|
    이 기록에는 나오지 않지만, worker 쓰레드가 방금 받은 연결을
 | 
						|
    처리하는 동안 다음 <code>accept(2)</code>가 (요청이 매우
 | 
						|
    많은 경우 항상) 일어날 수 있다.</p>
 | 
						|
 | 
						|
  </section>
 | 
						|
 | 
						|
</manualpage>
 | 
						|
 |