PHP 최적화 하기

PHP 2007. 1. 20. 22:44
신현삼 (sami@cnettech.co.kr )
개발자는 완료된 시스템이나 프로젝트가 가장 최적화되고 안정적으로 운영되기를 바란다. 이런 몫은 시스템 운영자에게도 있지만, 결정적인 원인을 제공하는 사람은 바로 개발자들이다. 문제를 만났을 때 원인을 해결하고, 시스템을 안정적으로 유지시키는 것도 필수적인 개발자들의 수양 코스라고 해도 과언이 아닐 것이다. 이번 호에서는 시스템을 최적화시키는 방법에 대해 다루도록 하겠다. 물론 시스템은 그 운용과 사용측면에서 절대적으로 다를 수 있다는 것을 말하고 싶고, 구체적인 사례보다는 전반적인 고려사항에 대해 다루도록 하겠다.
많은 개발자들이 PHP가 최대의 성능과 함께 최적화되기를 꿈꾼다. 개발자들은 많은 프로젝트에서 시스템 부하나 오류, 속도적인 문제를 만나서 고생하는 경우가 다수 발생한다. 일반적으로 설계 측면이나 개발 측면이 주요 원인이라고 볼 수 있겠지만, 아주 다양한 조건들이 성립돼야 올바른 서비스가 구현되기도 한다. 얼마 전 PHP는 버전 4에서 버전 5로 업그레이드되면서 새로운 도약을 준비하고 있다. 이것은 PHP의 최적화에서 중요한 변수가 될 수 있다. 개발 측면에서 볼 때 OOP(Object Oriented Programming)를 지원하면서 오류 문제를 해결할 수 있을 것으로 본다. PHP는 빠른 속도를 보장한다는 장점이 있지만 이것 또한 개발자의 기본적인 설계와 개발이 보장돼야 가능할 것이다. 여기서는 서비스 개발의 핵심인 설계 측면과 보안 측면, 개발자의 개발 측면, 시스템 운영 측면을 다루기로 한다.

설계적 측면에서 최적화
시스템을 구축할 때 가장 우선시되는 것이 바로 설계이다. 이 설계가 확장적 측면과 개발 내구성에 대해 고려하지 않는다면, 추후 재개발이라는 크나큰 과오를 만나게 된다. 그러지 않으려면 많은 시간을 투자해 설계에 중심을 두고 프로젝트를 진행해야 할 것이다.

올바른 DB 설계
데이터베이스의 설계는 가장 핵심적인 시스템 최적화의 요건이다. 우리는 DB 튜닝에서 프로그램 튜닝이라는 말을 종종 듣는다. 필자는 일전에 튜닝을 전문으로 하는 개발자를 만났는데, 이 튜닝이 얼마나 중요하고 또한 이를 위한 비용이 엄청나다는 것을 알았다. 문제의 사이트는 게시판을 전문으로 서비스하는 업체인데, 데이터량이 1만개를 넘어서면 부하가 많이 걸리고 속도가 떨어지는 문제가 있었다. 이 문제는 DB 관계에서 게시판의 정보를 담고 있는 테이블을 하나 추가하고 SQL 쿼리 문을 바꿈으로써 해결했다. 여기서 우리는 작은 설계하나가 확장성을 고려하지 않음으로써 얼마나 많은 재개발 비용이 발생하는가를 볼 수 있다.

프로젝트라는 것이 그렇듯이 처음 진행과 나중에 고객의 요구를 재반영했을 때의 형태는 50%가 넘게 바뀌어 있는 것을 보게 된다. 한 예로 초기 데이터를 모으는 테이블을 설계했는데 처음엔 15개의 필드였다가 프로젝트가 끝나고 나니 32개의 필드가 되어 있었다. 다행히 초기 데이터 테이블이라서 확장이 가능했지만 중간에 밀접하게 결합되어 있는 테이블이었다면 손을 쓰기가 곤란했을 것이다.

DB 서버와의 분리
서버를 구성하는 데 DB를 중요하게 사용하는 사이트라면 반드시 DB 서버와의 분리를 권장하고 있다. 즉 프로그램 소스와 데이터를 분리시키는 것이다. DB에서 무거운 쿼리를 던지게 되면 시스템의 부하가 많이 나타난다. 이런 경우 DB와 프로그램을 분리시켜서 부하를 나누는 것도 좋은 방법이다.

<그림 1>의 경우 발생될 수 있는 상황을 생각해 보자. 먼저 DB가 두 개로써 비용이 2배로 발생하게 된다. 또한 DB의 내용이 같다면 DB의 동기화도 문제가 될 수 있다. <그림 1>과 같은 사항은 오히려 두 개의 독립적인 서비스를 하면서 서로 링크로 서비스를 하는 개념에는 적합할 수 있으나, 서비스를 하나로 하는 사이트에서 분리 정책으로 만든 것이라면 문제가 발생될 소지가 높다.



<그림 1> 두 개의 웹 서버와 DB 서버를 사용하는 경우



<그림 2>를 살펴보자. <그림 2>는 우리가 쉽게 범할 수 있는 실수이기도 하지만 잘 적용하면 괜찮을 것 같은 서비스이기도 하다.



<그림 2> 하나의 웹 서버와 DB 서버를 같이 사용하는 웹 서버



이 시스템 구성은 하나의 DB와 웹 서버를 같이 사용하고, 또 다른 독립적인 웹 서버를 사용하는 경우이다. 실제로 이러한 사이트를 구축한 사례가 있었는데, 원래 처음 서비스는 DB와 웹 서버를 사용하고 있다가 시스템이 커지면서 하나의 웹 서버를 더 구축해 로드밸런싱을 시도했다. 하지만 로드밸런싱이 실제로 원활하게 이뤄지지 않는 문제가 발생했다. 왜냐하면 A 고객은 웹 서버에 접속하여 서비스를 하고, B 고객은 DB와 웹 서버에 접속돼 서비스를 받게 되는데 실제로 두 명의 고객의 웹 처리 속도가 달랐기 때문이다. 이것은 DB는 일정한 자원을 공유하여 서비스를 하고 있지만, DB가 있는 웹 서버는 데이터베이스의 로드된 시스템 자원을 제외하고 서비스를 하기 때문에 부하가 걸렸던 것이다.

이 시스템 구조는 DB를 전문으로 사용하는 프로그램과 자체로 처리되는 프로그램을 분리해 사용한다면 적합할 것이다. DB를 사용하는 고객은 웹 서버에 접속해 DB와 웹 서버의 서버로 옮겨가서 서비스를 받기 때문에 앞단의 웹 서버에는 부하를 덜 수 있다.
그렇다면 <그림 3>의 시스템 구조는 어디서 어울릴 수 있을까? 데이터베이스를 많이 사용하는 사이트에 적합하다고 볼 수 있다.



<그림 3> 하나의 DB 서버와 하나의 웹 서버



실제로 데이터베이스를 적극 활용하게 된다면 시스템 리소스를 엄청나게 가져간다. MS SQL의 예를 들면 동적 메모리에서 시스템의 자원 95% 이상을 점유해 DB 서비스를 최적화하려고 한다. 이렇게 했을 때 원활한 웹 서비스를 하기가 어렵다. 이와 같이 데이터베이스만을 위한 서버를 따로 준비해 두고 웹 서비스를 운영한다면 데이터베이스가 가지는 부담을 분리시킴으로써 원활한 서비스를 할 수 있을 것이다.
앞서 보았듯이 시스템 설계는 해당 서비스가 어떻게 구성되느냐에 따라 많은 차이점이 나타난다. 고객이 원하는 주서비스가 무엇인지. 메인 프로세스가 어떻게 기동돼야 하는지를 잘 구분해 시스템을 설계해야 할 것이다.

서버간의 분리 정책
서비스가 방대해지기 시작하면 서버들이 늘어나게 된다. 여기서도 우리는 분리 정책과 설계에 중심을 두어야 한다. 간단한 예를 들어보자. 사이트가 기본으로 DB를 핵심적으로 사용하면서 파일 다운로드 서비스를 자주 한다고 가정하자. 즉, 이 서비스는 웹 사이트에서 로그인한 후 사용자가 애플리케이션을 다운로드해 계속 DB와 통신하면서 정보를 주고받고 또한 이 정보에 의해 데이터를 다운받는다. 처음에는 하나의 서버에서 운영하다가 결국 과부하가 심하게 걸려서 서버를 두 대로 확장하려고 했으나, 다시 심사숙고한 끝에 결정한 결과가 <그림 4>와 같이 DB와 파일 서버를 분리시킨 것이다.



<그림 4> 각 서버를 분리한 모습



이렇게 함으로써 고객 애플리케이션의 통신과 DB와의 부담을 DB 서버가 가져가고 다운로드로 걸리는 웹 서버의 부하를 파일 서버가 가져가므로 안정적인 서비스가 가능하다. 여기서 우리가 주목해야 할 부분은 서비스의 용도와 시스템의 부하를 예측하고 설계하는 방법이다. 만일 서버를 두 대로 분할하면 어떻게 될까? DB 서버와 다운로드 서비스의 부하가 분명히 웹 서비스의 핵심적인 문제로 발생될 것이다. 이렇듯 설계는 시스템 최적화의 초석이라는 것을 알 수 있다.

보안적 측면에서 최적화
보안 문제가 웹 서비스의 화두로 나온지 오래다. 하지만 기본적인 보안 사항도 고려되지 않은 사이트가 아직도 많이 있다. 보안적인 안정화는 자칫 방심하면 엄청난 문제가 발생할 수도 있다. 실제로 웹 서비스를 제공하는 사이트는 로그인이나 게시판의 정보가 흐르는 로직을 해킹당해 손해를 보는 경우가 많다. 기초적이지만 보안적인 옵션들과 방법을 준비해 적용해야만 이후에 있을 문제에 대응할 수 있다. 특히, PHP나 서버의 보안 패치는 주기적으로 점검해야만 한다.

세션 vs. 쿠키
잘 알다시피 세션은 서버에서 생성돼 사용자의 로그인 후 사용을 보장하는 기술이다. 쿠키 또한 마찬가지로 로그인 후 사용을 보장하지만 세션은 서버에서 생성해 가지고 있고, 쿠키는 클라이언트에서 로그인 정보를 가지고 있다는 점이 다르다. 일반적으로 보안 문제를 위해 세션 사용을 권유하고 있다. 서버에서 사용자의 정보를 다루는 게 더 안정적이기 때문이다. 하지만 대용량 서비스(사용자가 많은 서비스)를 개발할 때는 서버의 부하가 큰 문제일 수 있다. 대용량 서비스를 할 때는 일반적으로 상용 보안 서비스를 사용하지 않는다면 쿠키를 사용해 서버의 부하를 줄이는 것도 하나의 방법일 것이다.

전역 변수 처리
전역 변수는 PHP에서도 보안상 치명적인 결함을 가지고 있다는 판단에 기본적으로 막고 있는 방법이다. 일반적으로 프로그램을 진행할 때 이러한 불편함 때문에 보안상 결함을 풀어놓는 경우가 많다. 하지만 정보가 중요하게 다뤄지는 곳에서는 반드시 이러한 문제를 고려해야 한다. 전역 변수의 옵션과 관련된 프로그램을 모듈화해 처리하는 방법도 개발 속도 개선에 도움이 될 것이다.

파일 업로드 보안
파일 업로드가 보안 문제의 화두로 떠오른 적이 있다. 이것은 필자도 한번 해킹(?)해 본 경험으로 파일 업로드에 실행가능한 코드를 업로드시켜서 다운로드 주소로 호출하면 업로드 프로그램이 실행된다. 이러한 문제를 막기 위해 개발자들은 다양하게 개발 옵션을 시도하고 있다. 먼저 서버에서 해당 디렉토리 실행 권한을 막는 방법을 들 수 있겠다.

웹 서버 컨트롤 파일에서 httpd.conf나 .htaccess 파일을 수정해 업로드된 파일이 저장되는 디렉토리에 php_engine off하면 프로그램이 해석되지 않는다. 다음은 매뉴얼에 나와 있는 코드이다.
Example 5-4. More secure file name checking

<?php
$username = $_SERVER[‘REMOTE_USER’]; // using an authentication mechanism
$homedir = “/home/$username”;

if (!ereg(‘^[^./][^/]*$’, $userfile))
die(‘bad filename’); //die, do not process

if (!ereg(‘^[^./][^/]*$’, $username))
die(‘bad username’); //die, do not process
//etc...
?>


SSL 처리
일반적으로 폼에서는 어떤 언어든지 암호화되지 않고 전송된다. 암호화는 전송받은 값을 받은 후에 그것을 가지고 가공하는 것이라고 볼 수 있다. 보통 SSL(Secure Socket Layer)을 사용하는데 결재 관련 정보들은 SSL로 81번 포트를 이용해 전송한다. SSL은 기본으로 프로토콜 차원에서 보안해 주는 개념이다. 일반적으로 https://로 사용되며 http://보다는 훨씬 안정적인 보안 기능을 수행하고 있다. 크게 어려운 설정 부분이 아니므로 로그인이나 개인정보 입력시에는 SSL을 사용하는 것을 권한다.

암호화 처리
암호화를 하는 방법은 다양하다. 사용자의 정보 전송을 위해 사용하는 방법으로 금융권에서는 SEED 방식을 채택하고 있고, 일반적으로 DES(Data Encryption Standard)도 많이 사용한다. PHP에서 가장 간단하게 사용할 수 있는 방법은 md5이다. 물론 md5가 안정성이라고 말하고 싶지는 않다. 하지만 최소한 암호화되지 않은 원문 데이터보다는 안정될 것이다. 암호화 관련해서는 깊이 있는 학습이 이뤄져야 한다. 자세한 정보는 보안 관련 사이트에서 정보를 얻기 바란다.
PHP 최적화하기 - 2
신현삼 (sami@cnettech.co.kr )
개발적 측면에서 최적화
객체지향적 개발 방식

개발적 측면에서 가장 화두가 된 것은 객체지향적 코드의 활용이다. 이것은 이미 논의되고 활용된 지가 십년이 넘는다. 자바가 성장할 수 있는 원동력이었다고 해도 과언이 아니다. PHP도 객체지향적 개발을 위해 많은 클래스 작업과 함께 결과물을 도출해냈지만 완벽한 OOP를 지원하지 못함으로써 여기에 대한 한계점이 많았었다. 하지만 PHP 5가 베타 버전을 출시하면서 완전한 객체지향적 개발 방식을 도입할 수 있게 되었다. 이것은 두 가지의 의미로도 해석할 수 있다.

하나는 엔터프라이즈 개발이 가능하다는 점이다. 이 말은 완벽한 OOP 지원으로 PHP도 이제 자바의 영역으로만 인식되어온 엔터프라이즈급 개발 프로젝트를 수행할 수 있다는 점이고, 아직은 많은 보완이 필요하겠지만 머지않아 다수의 프로젝트가 발생할 것이라고 믿는다. 두 번째로는 개발자의 개발 방식의 변동을 들 수 있겠다. 이 말은 기존의 PHP 개발자는 현재까지 진행해온 방식으로도 개발을 진행할 수 있지만, OOP에 맞는 개발 방식을 선택함으로써 폭넓고 안정적인 개발 패턴을 수행할 수 있게 되었다는 뜻으로 해석할 수 있겠다. 이처럼 객체지향적 방식을 채택함으로써 최적화된 서비스를 개발할 수 있을 것이라고 생각한다.

재사용 코드 모듈화
코드 재사용은 다수의 프로젝트를 진행하는 개발자들에게 필수적인 안정화 조건이고 최적화 조건이다. 버그에 대한 처리, 조건에 대한 확장은 여러 페이지나 프로그램에서 요구된다. 이러한 요구 조건을 각 페이지마다 적용하고 튜닝을 하다보면 개발 시간보다 훨씬 오래 걸리고, 오류나 문제를 발생시킬 확률이 훨씬 높다. 뛰어난 개발자들을 보면 데이터 처리에 대한 모듈화를 정형적으로 해놓고, 이 모듈에 대한 확장을 다시 모듈화해 기능 확장을 아주 쉽게 하며 확장 모듈만 검증함으로써 코드의 안정을 가져오는 경우가 있다. 이렇게 했을 때 처음 만든 모듈의 개발 시간보다는 이후 재사용돼 만들어져 나오는 코드들이 개발 속도와 안정성에서 훨씬 우수하다. 모듈화는 결국 개발자의 끈기와 설계에 의해 이뤄지는 것이다. 다시 한번 자신의 반복되는 수행 과정을 살펴보고 업무 프로세스든 프로그램이든 모듈화시키는 것을 빼놓지 말자.

쿼리문 최적화
웹 서비스에서 가장 많이 나타나는 속도와 최적화의 문제는 무엇일까? 그것은 바로 쿼리이다. 쿼리의 인덱스나 주 키 그리고 서브 쿼리를 제대로 활용하는 것이 얼마나 중요한 것인지 알 수 있다. 필자는 얼마 전에 데이터가 50만건이 넘어선 사이트에서 조회가 PHP의 실행 시간(기본 30초로 설정)을 넘어 ‘max time out’ 에러가 나는 장애를 만났었다. 쿼리를 보니 인덱스를 제대로 활용하지 못한 쿼리를 사용하고 있었다. 실제로 쿼리는 던져보니 1분 정도 걸렸었다. 약간의 튜닝을 진행하고 나서 쿼리를 던져보니 0.00초가 나오는 것이었다. 아주 단순한 작업이면서도 충격적이었다. 쿼리에 대한 약간의 테스트(스트레스 테스트)를 진행해 보지 않은 과실이 이런 장애를 일으킨다는 소중한 교훈을 얻게 되었다. 쿼리에 대한 최적화를 반드시 염두에 두고 SQL문을 만들기를 바란다. 또한 시스템에 부하가 많이 가지 않는다면 인덱스를 많이 만들어 두는 것도 나중에 확장적 측면에서 괜찮을 듯 싶다.

파일 운용 기법의 최적화
웹 개발을 진행하다 보면 많은 문제가 페이지 로딩 속도와 관련되어 있다. 페이지 로딩에 대한 최적화는 어떻게 이뤄질까. 주 메뉴를 파일로 분리시켰을 경우를 생각해 보자. 방법은 다음과 같이 세 가지의 방법으로 나눌 수 있다.

① PHP에서 include시키는 방법 : 예) include “menu.php”;
② 자바 스크립트에서 링크시키는 방법 : 예) <script src=”menu.php”></script>
③ HTML에서 iframe으로 링크시키는 방법 : 예) <iframe src=”menu.php”></iframe>

이렇게 했을 때 속도 차이가 날까? 동적으로 자꾸 바뀌는 파일이 아니라 정적인 파일이라면 두 번째 방식이 가장 빠르다고 볼 수 있다. 사용자가 한번 호출하면 다음 페이지부터는 이 부분의 추가 로딩이 전혀 필요없기 때문이다.

   [ 새로운 선택, PHP 5 ] 

PHP 5가 베타 버전을 출시하면서 본격적인 엔터프라이저로의 위용을 갖추기 시작했다. 자세한 내용은 기회가 닿으면 다루기로 하고, 새로운 변화 내용만 짚고 넘어가자.

새로운 아키텍처로 재구성된 Zend 엔진
2
엔진이 완전히 새롭게 재구성됐으며 완벽한 객체지향과 대용량 개발을 지원한다. 기존의 빠른 개발을 위해 지원되던 간단한 문법도 호환 가능해 버전업시켰을 시에 크게 문제가 없다
.

SQLite
기본 제공
SQLiteDB 서버가 별도로 존재하지 않고, API를 통해 파일 데이터베이스에 작업하는 형태이다. SQLite 자체가 하나의 DB 서버라고 할 수 있다. 문법을 살펴보면 트랜잭션, 서브쿼리, , 트리거 등을 지원하며 한 데이터베이스당 한 파일로 지원된다. 스토어드 프로시저 등은 지원하지 않는다. 말 그대로 간단히 사용할 수 있는 개념의 데이터베이스라고 보면 된다
.

MySQL
디폴트 제공 삭제
MySQL이 번들에서 제외되고 대신 SQLite를 탑재시켰다. MySQL의 정책과 관련있는 듯 하다. 하지만 개발자들은 MySQL을 추가 모듈로 컴파일해 이전과 같이 사용할 수 있다
.

완벽한 XML 지원
XML 지원 코드가 완전히 다시 만들어졌다. Simplexml 익스텐션을 지원하고 DOM, XSL쪽도 새로 만들었으며 expat 라이브러리를 더 이상 사용하지 않는다. 보다 안정적인 XML 관련 사이트를 개발할 수 있게 된 것이다
.

향상된 스트림 지원
스트림 관련 함수가 강화됐다. stream_sock_client(), stream_server(), stream_accept() 등의 함수를 지원하기 때문에 더욱 나은 서버 스트리밍 서비스를 개발할 수 있게 됐다
.

향상된 GD 기능
GD에 이미지를 사용할 수 있는 기능과 안티 알리아싱을 사용하여 그림을 그릴 수 있는 기능이 추가됐다. 동적인 이미징 서비스 개발이 유용해졌다.
 


시스템 측면에서 최적화
시스템과 관련있는 최적화 요소가 굉장히 많다. 이것은 시스템 운영의 최적화 조건을 성립시키기 위해 필수적인 것이다.

DB 서버 관리
데이터베이스 시스템 관리는 개발자나 운영자에게 아주 중요한 요소이다. 시스템은 시간이 지날수록 데이터베이스의 용량과 시스템의 부하를 일으키기 때문에 속도와 밀접한 관계를 가지게 된다. 데이터베이스 시스템에 대한 유지관리도 서비스 향상에 크게 도움이 된다. 예를 들면 MySQL의 쿼리 속도를 빠르게 하는 방법이다. 이것은 캐시를 사용해 쿼리를 빠르게 하여 속도를 향상시키는 방법으로 /etc/my. cnf 파일을 열어 다음과 같이 옵션을 바꾸면 된다.

# The MySQL server
[mysqld]
port = 3306
socket = /tmp/mysql.sock
skip-locking
set-variable = key_buffer=16M
set-variable = max_allowed_packet=1M
set-variable = table_cache=64
set-variable = sort_buffer=512K
set-variable = net_buffer_length=8K
set-variable = myisam_sort_buffer_size=8M
set-variable = query_cache_limit=1048576 <==결과 값이 주어진 값보다 크면 캐시에 저장안함
set-variable = query_cache_size=16768060 <==캐시 크기
set-variable = query_cache_type=1
log-bin
server-id = 1

이렇듯 MySQL뿐만 아니라 많은 데이터베이스 시스템은 시스템에 최적화될 수 있는 옵션을 제공하고 있다. 사용하는 데이터베이스의 운영 관련 조건을 잘 살펴봐야 한다.

로드밸런싱
대용량 서비스를 하게 됨으로써 필수 불가결로 로드밸런싱을 채택한다. 소프트웨어적인 로드밸런싱과 하드웨어적인 로드밸런싱이 있는데, 일반적으로 소프트웨어 로드밸런싱을 먼저 채택한 후 서비스가 더 커졌을 때 하드웨어적인 로드밸런싱을 채택한다. 앞서 말했듯이 시스템 설계에서 로드밸런싱을 고려한 시스템 분할이 있어야 한다. 보통 DB 서버의 앞단에 웹 서버를 두어서 로드밸런싱을 많이 시도한다. 이런 시스템에 대한 설계는 비용적인 측면과 밀접하기 때문에 시스템 운영자와 긴밀한 협의가 이뤄져야 한다.

로그 관리
로그 관리는 필수적인 조건을 가지고 있지만 중요한 정보가 아닐 때는 로그 옵션을 다양하게 주어서 서버의 성능을 조절해 주면 된다. 시스템 부하가 걸리거나 문제가 발생하는 사이트를 보면 로그가 꽉 차서 문제를 일으키는 경우가 허다하다. 또한 로그가 너무 많이 쌓여서 시스템이 아주 늦어지는 경우도 있다. 로그 관리부터 로그를 필요한 것만 쌓이게 하는 방법을 강구함으로써 예견된 장애를 미리 막아 원활한 서비스를 제공해야 할 것이다.

웹 서버 설정
웹 서버 설정은 개발자나 운영자의 중요한 학습과제이다. 웹 서버 설정을 잘하면 아주 쉽게 서비스의 성능을 향상시킬 수 있을뿐더러 문제 해결에도 많은 도움이 된다. 필자는 웹 서비스 첫 페이지로 로딩이 오래 걸리는 문제를 접했었다. 문제의 키는 웹 서버에 있었다. 웹 서버의 옵션을 보면 로깅을 사용하거나 필요 없는 로그를 남기거나 정보를 체크하는 여러 가지 옵션들이 있다. 이 옵션을 조절함으로써 문제를 해결할 수 있었다. 이처럼 웹 서버는 갈수록 강력해지고 많은 조건 및 옵션과 성능을 제시하고 있다. 필수적으로 시스템 운영과 개발에 참여하는 개발자들은 웹 서버 설정에 대해 많은 지식을 가져야 하겠다.

파일 백업
프로그램에 대한 백업은 개발자들이 자주 하면서도 귀찮아하는 일에 속한다. 하지만 이것만큼 중요한 일도 없을 것이다. 필자는 개발해 놓은 사이트에서 프로그램을 백업받지 않았는데 하드디스크 오류가 발생해 모든 프로그램이 삭제되는 경우를 접한 적이 있다. 사사로운 개발자의 실수가 엄청난 장애를 일으킬 수 있다는 것을 잊지 말도록 하자. 또한 주기적인 데이터베이스 및 로그, 프로그램을 백업하는 프로그램을 만들어서 사용하는 방법도 이러한 귀찮은 일들을 일시에 처리할 수 있는 지혜가 될 수도 있다.

고객의 요구 조건과 시스템 사양이 최우선
이번 호에서는 PHP로 시스템을 최적화시키는 방법에 대해 간단하게나마 다루었다. 시스템 최적화는 각 분야에서 세부적으로 책이 나올 정도로 중요하게 다뤄지고 있다. 시스템 최적화의 몫은 무엇보다 고객의 요구조건과 시스템 사양이 결정적인 역할을 한다. 더 세부적인 것은 자세한 서적을 참조하는 것이 좋을 것이다. 시스템을 구축하는 가장 기본적인 원칙이 안정성과 최적화라는 것을 잊지 말고 프로젝트를 진행하길 바란다. PHP는 이제 5.0의 시대를 맞이하면서 새롭게 도약하려고 하고 있다. 다음 회에서는 PHP 5의 실제 설치와 새롭게 바뀌는 아키텍처를 가지고 간단한 프로그래밍을 해보도록 하겠다. 바뀌는 시대에 빨리 적응하는 앞서가는 개발자가 되었으면 한다.

'PHP' 카테고리의 다른 글

트랙백  (0) 2007.01.24
계층형 댓글 자료들  (0) 2007.01.24
exif_imagetype  (0) 2007.01.22
$PATH_INFO와 ForceType을 이용한 검색엔진 친화적 URL  (0) 2007.01.20
move_uploaded_file  (0) 2007.01.20
Posted by 다엘
,

출처 : phpschool

이전에 한 번 올라왔었던 '야후스타일 URL'에 관련된 내용으로, ReWrite 엔진 대신
ForceType과 PATH_INFO를 이용한 방법입니다. 야후스타일 URL은 흔히 검색엔진 친화적
URL이라고 하는데, 이런 이름이 붙은 이유는 검색 엔진들이 ?가 포함된 URL을 싫어하기(했
기) 때문입니다..


목표는, 이런 복잡한 URL을

http://myhost.com/zboard.php?id=main_story&page=1&sn1=&divpage=1
&sn=off&ss=on&sc=on&select_arrange=headnum&desc=asc&no=3959

이렇게 단순화시키는 것입니다.

http://myhost.com/Board/ZeroReaD3959.html

1. ForceType

ForceType은 서버에 있는 파일들 중 특정한 확장자를 특정 MIME 형식으로 강제하는 아파
치 지시자입니다. 예를 들어 확장자가 html이나 htm인 HTML 파일은 기본적으로  text/html
으로 브라우저에 전달되나, ForceType을 이용하면 그 외의 형식으로 전달되게 할 수 있습니
다.

야후 스타일 URL을 위해 ForceType을 쓰는 이유는,  위의 예의 경우 Board가 zboard.php를
대신하게 만들어야 하기 때문입니다. 즉 php 코드가 들어 있는 Board라는 파일이 php
스크립트로써 실행되게 만들어야 하는 것입니다. 이를 위한 ForceType 지시자의 형태는 다
음과 같습니다.

<Files Board>
ForceType application/x-httpd-php
</Files>

Board라는 파일에 대한 요청을 받았을 때, MIME 형식을 application/x-httpd-php로 해서 처
리하라는 뜻입니다. 이러한 구문은 아파치 서버 자체의 설정(httpd.conf)의 <Directory> 등에
넣어도 되지만, 웹 호스팅 등 서버 설정을 바꿀 수 없는 경우 그리고 설정 변경의 용이성을 생
각한다면 Board가 있는 디렉토리의 .htaccess 에 넣는 것이 더 나은 방법일 것입니다.

2. ForceType 테스트

우선 빈 텍스트 문서를 열고, 위에 나온 <Files Board>.. </Files> 부분을 그대로 입력한 후
.htaccess라는 이름으로   웹 서버의 임의의 디렉토리에 저장합니다. .htaccess가 이미 존재
한다면 추가해 주면 되구요. (여기서는 웹 사이트의 루트 디렉토리를 기준으로 하겠습니다.
그리고 웹 사이트는 http://localhost/라고 가정하겠습니다)

그런 다음 , 아래를 그 디렉토리에 Board라는 이름으로 저장합니다. 확장자 .php를 붙이면
안 됩니다.

<?
echo "소스 전체가 보인다면 실패, 이 문장만 보인다면 성공";
?>

이제 브라우저로 http://localhost/Board 를 열었을 때, 제대로 되지 않았다면 소스 전체가
보이거나 아니면 다운로드 창이 뜰 것입니다. 그렇지 않고 따옴표 안의 문장만 보인다면 성공
입니다.

실패의 원인은 대부분 해당 디렉토리에 대해 서버 설정 오버라이드가 허용되지 않기 때문입
니다. 웹 호스팅이라면 관리자에게 문의를 해야 하구요. 서버 설정을 직접 고칠 수 있다면,
httpd.conf 등에서 해당 디렉토리에 대한 <Directory> 항목 중 AllowOverride를 All로 해주면
됩니다. 예를 들면 다음과 같습니다(루트 디렉토리의 경우)

<Directory />
    Options Indexes FollowSymLinks
    AllowOverride All
    Order allow,deny
    Allow from all
</Directory>

실패의 경우 아마 AllowOverride가 None일 것입니다. 그것을 All로 바꿔주면 ForceType을
비롯한 다른 설정들의 변경이 가능합니다.

3. PATH_INFO

$PATH_INFO는 PHP가 설정해 주는 전역 변수로, URL 중 스크립트 이름 다음부터 물음표 전
까지의 모든 것들을 담고 있습니다.

Board 파일을 다음과 같이 수정해 봅시다.

<html>
<body>
<pre>
<?

echo '$SCRIPT_NAME : ' . $SCRIPT_NAME . "\n";
echo '$PATH_INFO : '. $PATH_INFO . "\n";

?>
</pre>

</body>
</html>

이제 브라우저로 http://localhost/Board 를 열면

$SCRIPT_NAME : /Board
$PATH_INFO :

이제 진짜 테스트로, 원래의 목표였던 http://localhost/Board/ZeroReaD3959.html 를

$SCRIPT_NAME : /Board
$PATH_INFO : /ZeroReaD3959.html

중요한 것은, 웹 서버는 ForceType에 의해 Board를 실행할 뿐이며, ZeroReadD3959.html를
파일 이름으로 보는 것이 아니라 Board에 주어지는 정보로만 인식한다는 점입니다. 이것이
ForceType과 PATH_INFO를 결합해서 사용할 때의 핵심입니다. 이것만 이해한다면 나머지
는 상상력을 발휘해서 얼마든지 활용할 수 있을 것입니다.

아파치 버전이나 플랫폼에 따라서는 PATH_INFO를 지원하지 않을 수도 있는데(옛날 이야기
인가요^^), 그럴 때에는 $REQUEST_URI로 대신할 수 있습니다. 단 $REQUEST_URI에는 스
크립트 이름과 ? 이후의 URL 인자들까지 모두 포함되므로... 정보를 걸러내는 과정이 아주 조
금 복잡해 질 수 있습니다. Board에 echo '$REQUEST_URI : ' . $REQUEST_URI . "\n"; 을
추가하고, http://localhost/Board/ZeroReaD3959.html?hello=world 등 복잡한 URL을 시험
해 보세요.

3. PATH_INFO로부터 정보 추출

이 부분은 뭐 정규 표현식으로 끝낼 수 있습니다. 이 예에서, /ZeroReaD3959.html에서 필요
한 정보는 3959 뿐입니다.  이것을 뽑아내는 정규표현식은 ZeroReaD([0-9]+) 가 되겠죠...

테스트를 위해 Board에 다음을 추가...


preg_match("/ZeroReaD([0-9]+)/", $PATH_INFO, $match);

echo "글 번호 : $match[1] \n";

이제 http://localhost/Board/ZeroReaD3959.html를 열면, 결과는:

$SCRIPT_NAME : /Board
$PATH_INFO : /ZeroReaD3959.html
글 번호 : 3959

4. 실제 스크립트 호출

이 부분부터는 사실 제가 제로보드를 써 보지 않아서 테스트할 수는 없었습니다. 다만 PHP와
HTTP의 작동 방식을 생각한다면 그리 어렵지 않게 해결할 수 있을 것입니다.

한 가지 방법은, 글 번호로 실제 URL을 만들고, (이런 식으로..)

$url = "http://localhost/zboard.php?id=main_story&page=1&sn1=&divpage=1
&sn=off&ss=on&sc=on&select_arrange=headnum&desc=asc&no={$match[1]}";

그런다음 redirect 헤더를 이용해서 실제 zboard.php를 호출하는 것입니다. 단점은 서버-브
라우저 간의 전송이 두 번 일어난다는 것. 장점은 아래에서 이야기하겠지만 상대 경로의 문제
가 없다는 것...

또 다른 방법은 zboard.php에 전달될 GET 인자들을 직접 설정한 후 Board에서 zboard.php
를 include 하는 것입니다.... 예를 들면


$HTTP_GET_VARS['id'] = 'main_story';

...

$HTTP_GET_VARS['no'] = $match[1];

include "..경로../zboard.php";

이런 식으로요... 가능할 것이라고 생각합니다. (또는 zboard.php 자체를 Board로 바꾸고 제
일 윗부분에 정규 표현식 처리를 집어 넣어도 될 것입니다...)

5. 주의할 점 및 확장

http://localhost/Board/ZeroReaD3959.html 라는 URL을 보시면 Board가 마치 디렉토리 이
름처럼 되어 있습니다. 서버쪽에서는 ForceType을 이용하니 문제가 없는데 브라우저는
Board를 디렉토리로 인식하므로 <a> 등에서 상대 경로를 사용할 때 문제가 생길 수 있습니
다... 이를 해결하기 위해서는 생성되는 html 안에서 항상 절대 경로를 사용하거나, 아니면
<base>를 사용해서 기준 경로를 지정해 줘야 합니다.

한 가지 확장으로, 이 예에서는 글 번호만 취급했는데,
http://localshot/Board/main_story/style_simple/3959.html 처럼 Board 밑에 글 번호 이외
의 여러가지 정보를 넣게 할 수도 있을 것입니다. 이 경우 PHP 함수 split()을 이용해서
$PATH_INFO에 담긴 문자열을 '/'의 기준으로 나누면 각 정보를 쉽게 뽑아낼 수 있을 것입니
다.

6. 결론

이상의 방법은 ReWrite 엔진을 사용하는 것보다는 느리겠지만 구현하기가 간단하며 유연성
도 뛰어난 방법이라고 생각합니다. 개인적으로 저는 이 방법을
http://www.gpgstudy.com/gpgiki/ (여기서 gpgiki가 바로 ForceType을 이용한 스크립트
입니다)에서 유용하게 쓰고 있습니다.

그리고, 3번까지는 제가 실제로 시험해 본 것이지만, 마지막에서 이야기한 제로보드 또는 기
타 게시판 시스템과의 통합을 실제로 구현해 보지 못했다는 점이 아쉬운데요. 아마 세부적인
부분에서 몇 가지

'PHP' 카테고리의 다른 글

트랙백  (0) 2007.01.24
계층형 댓글 자료들  (0) 2007.01.24
exif_imagetype  (0) 2007.01.22
PHP 최적화 하기  (0) 2007.01.20
move_uploaded_file  (0) 2007.01.20
Posted by 다엘
,