메뉴 바로가기 검색 및 카테고리 바로가기 본문 바로가기

한빛출판네트워크

IT/모바일

PHP Data Access 3. 캐싱

한빛미디어

|

2004-09-17

|

by HANBIT

12,539

저자: 한동훈(traxacun @ unitel.co.kr)

이번시간에는 PHP Data Access 라이브러리 작성의 마지막 시간으로 캐싱에 대해 살펴볼 것입니다. 캐싱을 위한 방법은 여러가지가 있습니다. 직접 개발하는 방법도 있고, PHP의 설정을 이용한 방법도 있고, PEAR의 Cache 클래스를 이용하는 방법도 있습니다. 여기서는 주로 데이터베이스의 쿼리를 캐싱하기 위한 목적으로 사용되는 SqlRelay를 이용한 캐싱을 다룹니다.

sqlrelay는 오픈 소스 프로젝트로 C/C++/PHP/Python/Perl을 비롯한 다양한 언어들을 지원하고 있으며, freeTDS, MySQL, ODBC, Postgresql, sqlite와 같은 다양한 데이터베이스에 대한 캐싱을 지원합니다. sqlrelay를 사용하기 위한 설치 방법이나 PHP 설정에 대해서는 각자의 재량에 맡기겠습니다. ? 사실, 제가 각 리눅스 배포판을 아는 것도 아니며, 데비안 리눅스를 쓰고 있기 때문에 다른 배포판에 대해서는 설명하기가 어렵습니다.

데비안에서는 apt-get install sqlrelay sqlrelay-mysql php4-sqlrelay를 실행하면 바로 사용할 수 있는 상태가 됩니다.

각 데이터베이스 연결 문자열 설정은 /etc/sqlrelay/sqlrelay.conf에서 합니다.(데비안) 정상적으로 설정을 마쳤다면 전에 작성했던 PHP4 MySQL API를 이용한 함수를 sqlrelay API를 이용한 함수로 만들어 보겠습니다.

$dc = da_openConnection();
$cursor = da_openCursor();

// open db connection
function da_openConnection()
{
   global $da_config;

   $dc = sqlrcon_alloc( $da_config["dbhost"],
         9000, "", $da_config["dbuser"], $da_config["dbpassword"], 0, 1);

   return $dc;
}


// open cursor
function da_openCursor()
{
   global $dc;

   $cursor = sqlrcur_alloc( $dc );

   return $cursor;
}

여기서는 미리 생성된 연결을 사용하기 위해 $dc와 $cursor만을 사용합니다. 두번째 시간에 작성했던 라이브러리와 달리 da_openCursor() 함수가 추가되었지만 sqlrelay를 이용한 PHP 응용 프로그램을 작성하기 위해 기존에 작성한 코드를 변경할 필요는 없습니다. da_openCursor()는 라이브러리 내부에서 커서를 할당하기 위해서만 사용합니다.

sqlrelay는 sqlrelay.conf에 설정된 연결 풀(Connection Pool)에 있는 연결을 사용하며, 데이터베이스 쿼리에 대한 결과는 커서(cursor)에 저장합니다. - 아마도, 관계형 DB 이전에 데이터베이스를 다루었던 분들은 커서라는 개념에 매우 익숙할 것입니다. 간단히 커서란 하나의 행 단위로 레코드를 처리하는 것을 의미합니다.

다음으로 살펴볼 것은 커서를 닫는 부분과 연결을 닫는 부분입니다.

function da_closeCursor()
{
   global $cursor;
   sqlrcur_free( $cursor );
}

// close db connection, but never close because we use a persistent connection
// and delegate connection management to php4.
function da_closeConnection()
{
   global $dc;
   da_closeCursor();
   sqlrcon_free( $dc );
}

실제로 연결을 닫기 전에 커서를 닫아야 하기 때문에 da_closeConnection()에서 커서도 닫습니다. 이 때문에 내부적으로 커서를 사용하더라도 커서를 열고, 닫는데 특별한 문제가 발생하지 않게 되며, MySQL API에서   sqlrelay API로 변경하더라도 기존에 작성된 코드에 변화가 발생하지 않습니다. da_closeConnection()의 주석에 설명한 것은 연결을 닫더라도 연결 유지 기능(persistent connection)을 사용하기 때문에 연결이 정말 닫히는게 아니라는 것을 설명하기 위한 것입니다. 실제로, 연결은 연결 풀로 돌아갑니다. ^^

하나의 값을 가져오기 위해 사용했던 da_queryValue() 함수를 살펴볼 차례입니다.

function da_queryValue($query)
{
   global $dc;
   global $cursor;
   $query = stripSlashes($query);
   sqlrcur_sendQuery( $cursor, $query );
   sqlrcon_endSession( $dc );
   $row = sqlrcur_getField( $cursor, 0, 0 );

   return $row;
}

여기서 살펴본 것처럼 sqlrcur_sendQuery(), sqlrcon_endSession()을 사용한다는 점을 제외하면 그다지 크게 바뀌는 점은 없습니다. sqlrcur_getField( $cursor, 0, 0 )에서 두 숫자는 각각 행과 열을 뜻합니다.

레코드의 유무와 같은 단순 크기를 알아내기 위한 da_queryScalar()도 다음과 같이 작성합니다.

// sqlrelay API를 사용한 da_queryScalar
function da_queryScalar($query)
{
   global $dc;
   global $cursor;

   sqlrcur_sendQuery( $cursor, $query );
   sqlrcon_endSession( $dc );

   return sqlrcur_rowCount( $cursor );
}

여기서도 함수의 정의나 함수가 반환하는 값에 대해서는 변경되지 않습니다. 두번째 글에서 소개한 da_queryScalar()를 살펴보면 그 내부구현은 완전히 변해버렸다는 것을 알 수 있습니다.

// MySQL API를 사용한 da_queryScalar
function da_queryScalar($query)
{
   global $dc;
   $rows = @mysql_query($query, $dc);
   return @mysql_num_rows($rows);
}

그럼에도 불구하고 함수의 역할과 인자, 반환값을 그대로이기 때문에 da_queryScalar() 함수를 이용하여 작성한 기존의 코드를 변경할 필요가 전혀 없다.

INSERT, UPDATE, DELETE 등의 결과집합(레코드)을 갖지 않는 쿼리를 위해 사용했던 da_queryNonResult() 함수는 다음과 같이 작성할 수 있습니다.

function da_queryNonResult($query)
{
   global $dc;
   global $cursor;

   sqlrcur_sendQuery( $cursor, $query );
   sqlrcon_endSession( $dc );
   return sqlrcur_affectedRows( $cursor );
}

결과 집합을 가져오기 위해 사용했던 da_getRecordsByFields() 함수는 다음과 같이 작성할 수 있습니다.

function da_getRecordsByFields($query)
{
   global $dc;
   global $cursor;

   $query = stripSlashes($query);

   // buffering the result set all at once
   sqlrcur_setResultSetBufferSize( $cursor, 0 );

   sqlrcur_sendQuery( $cursor, $query );
   sqlrcon_endSession( $dc );

   if ( 0 == $cursor )
   {
      return false;
      exit;
   }

   for( $loopctr = 0; $loopctr < sqlrcur_rowCount( $cursor ); $loopctr++)
   {
      $row_array = sqlrcur_getRow( $cursor, $loopctr );

      for( $ctr = 0; $ctr < sqlrcur_colCount( $cursor ); $ctr++ )
      {
         $field_name = sqlrcur_getColumnName( $cursor, $ctr );
         $data_set[$loopctr][$field_name] = $row_array[$ctr];
      }
   }
   // end da_getRecords
   return $data_set;
}

이 함수에서도 큰 로직은 변화가 없는 듯한데, 기존의 MySQL API와 달리 눈에 띄는 함수가 보일 것입니다.

sqlrcur_setResultSetBufferSize( $cursor, 0 );

이 함수가 의미하는 것처럼 커서가 캐싱할 버퍼 크기를 설정하는 것으로 0으로 크기를 지정하는 것은 모든 결과를 한 번에 버퍼에 담아서 처리한다는 것을 의미합니다.

mysql_fetcharray()를 사용했던 da_fetchArray() 함수를 sqlrelay에서 어떻게 구현할지 어려워하는 분이 있을 것이다. 대부분 이런 문제는 sqlrelay에 해당 API가 없다기 보다는 MySQL API와 함수 이름이 다르다는데 기인합니다. 밑에 소스를 보면 알 수 있는 것처럼 mysql_fetchArray()에 대응되는 함수는 sqlrcur_getRowAssoc() 함수입니다. 얼마나 극적으로 이름이 다르단 말인가!!

function da_fetchArray( $res, $row )
{
   return sqlrcur_getRowAssoc( $res, $row );
}

사용할 API 셋을 변경하더라도 우리는 늘 동일한 da_로 시작하는 함수를 사용할 것이기 때문에 코드를 변경할 필요가 없다고 했으며, 이를 위해 database.inc.php를 사용했습니다. 이전 기사의 기억을 되살리기 위해 여기에 다시 옮겨보겠습니다.

if( $da_config["dbms"] == "php5mysql" )
{
   require( "/lib/database-php5mysql.inc.php");
}
else if( $da_config["dbms"] == "php4mysql" )
{
   require( "/lib/database-php4mysql.inc.php" );
}
else
{
   require( "/lib/database-php4mysql.inc.php" );
}

여기에 sqlrelay라는 이름을 추가하여 다음과 같은 코드를 더 추가할 수 있을 것입니다.

else if( $da_config["dbms"] == "sqlrelay" )
{
   require( "/lib/database-sqlrelay.inc.php" );
}

이것으로 지금까지 PHP에서 Data Access Layer를 작성하는 것에 대해서 살펴보았습니다. 전체 소스는 따로 다운 받을 수 있습니다.
TAG :
댓글 입력
자료실