당 신이 게시판이나 메일링리스트 같은 것을 웹사이트에 게시하거나 CMS를 개발하고자 할때, 계층적 데이터 구조를 데이터베이스에 저장할 필요성이 발생 할 때가 있다. 그리고, XML기반 데이터베이스를 사용하지 않는 이상 테이블은 계층적이지 않으며 단지 평면적일 뿐이다. 당신은 계층적인 구조를 평면파일로 번역할 수 있는 방법을 찾아야만 한다.

트리(tree)를 저장하는 것은 공통된 관심사이며 여러가지 해법이 존재한다. 가장 주요한 두가지 접근법은 근접 리스트 모델(adjacency list model)과 수정된 순서화된 트리(modified preorder tree) 검색 알고리즘이다.

이 글에서는 계층적 데이터 저장의 이 두가지 방법을 설명할 것이다. 트리는 온라인 식료품점을 예로들어 설명할 것이다. 이 식료품점은 식품들을 카테고리별, 색상과 타입별로 정돈한다. 이 트리는 아래 그림과 같이 보일것이다.

 

이 글에서는 데이터를 저장하고 조회하는 코드 예제들을 볼 수 있다. 비록 예제코드는 PHP로 작성되었지만 당신이 사용하는 언어로도 쉽게 변경이 가능 할 것이다.

근접 리스트 모델 (The Adjacency List Model)
먼저, "근접 리스트 모델" 또는 "재귀호출 방법"이라 부르는 접근법에 대해 살펴 보겠다. 이것은 당신은 트리를 탐색하는 단순한 한가지 기능만을 필요로 하므로 가장 고상한 접근법이다.


위 그림에서 보듯이, 근접 리스트 방법에서는 각 노드가 "parent"를 가지고 있다. 우리는 "Pear"가 "Green"의 자식(child)임을 알 수 있다. 또 "Green"은 "Fruit"의 자식임을 또한 알 수 있다. 최상위 노드인 "Food"는 더이상 부모값을 가지지 않음을 아 수 있다. 나은 "title"을 각 노드의 구분값을로 사용했다. 물론, 실제 데이터베이스 에서는 숫자형태의 ID값을 사용할 것이다.

트리(Give Me the Tree)
이제 우리의 트리를 데이터베이스에 삽입하였다. 이제 보여주는 함수를 작성할 시간이다. 이 함수는 최상위(root)노드-부모(parent)를 가지지 않는 노드-에서 출발해야만 한다. 그리고, 모든 자식노드를 출력할 것이다. 모든 자식들은 함수에 의해 그 자식노드들 까지 모두 탐색될 것이다. 

당신은 이 함수에 대한 설명에서 이것이 정규화된 패턴이 있음을 눈치챘을 것이다. 우리는 단순히 하나의 함수만을 작성할 것이며 이것은 특정 부모에 대한 자식노드를 검색할 것이다. 이 함수는 다른 인스턴스로 다시 그들의 자식들을 모두 출력하기 위해 시작될 것이다. 이것을 재귀적인 메커니즘으로 "재귀호출 방법(recurtion method)"이라 부른다.


<?php
// $parent is the parent of the children we want to see
// $level is increased when we go deeper into the tree,
// used to display a nice indented tree
function display_children($parent, $level) {
// retrieve all children of $parent
$result = mysql_query('SELECT title FROM tree '.
'WHERE parent="'.$parent.'";');

// display each child
while ($row = mysql_fetch_array($result)) {
// indent and display the title of this child
echo str_repeat(' ',$level).$row['title']."\n";

// call this function again to display this
// child's children
display_children($row['title'], $level+1);
}
}
?>


모든 트리를 보여주기 위해 우리른 $parent에 빈공백과 $level = 0을 사용할 것이다. :

display_children('',0);

이 함수는 식료품점 트리를 아래와 같이 리턴할 것이다.

Food
   Fruit
    Red
      Cherry
    Yellow
      Banana

   Meat
    Beef
    Pork


만약, 특정 서브트리만 보고싶다면 함수에 다른 노드명을 적어주면 된다. 예를들어, "Fruit"의 서브트리를 출력하려면 display_children('Fruit',0); 과 같이 호출하면 된다.

노드의 경로
위 와 거의 비슷한 코드로 노드의 이름 또는 ID만으로 노드의 경로를 찾는것도 가능하다. 예를들어 "Cherry"의 경로는 "Food" > "Fruit" > "Red" 이다. 이 경로를 얻으려면 우리의 함수는 가장 깊은 레벨:"Cherry" 에서 시작해야만 한다. 이것은 현재노드의 부모를 찾고 경로에 추가시킨다. "Cherry"의 경우 "Red"가 된다. 우리가 "Cherry"의 부모가 "Red"라는것을 알고 있다면 우리는 "Red"를 이용해 "Cherry"의 경로를 계산할 수 있다. 그리고, 이것을 함수에 전달ㅎ해 재귀호출 함으로써 트리내의 어떤 노드든지 경로를 알아 낼 수 있다.


<?php
// $node is the name of the node we want the path of
function get_path($node) {
// look up the parent of this node
$result = mysql_query('SELECT parent FROM tree '.
'WHERE title="'.$node.'";');
$row = mysql_fetch_array($result);

// save the path in this array [5]
$path = array();

// only continue if this $node isn't the root node
// (that's the node with no parent)
if ($row['parent']!='') {
// the last part of the path to $node, is the name
// of the parent of $node
$path[] = $row['parent'];

// we should add the path to the parent of this node
// to the path
$path = array_merge(get_path($row['parent']), $path);
}

// return the path
return $path;
}
?>


이 함수는 주어진 노드의 경로를 리턴한다. 이것을 경로를 배열(array)형으로 리턴하므로 print_r(get_path('Cherry')); 명령어로 출력할 수 있다. :Cherry는 아래와 같다.

Array
(
[0] => Food
[1] => Fruit
[2] => Red
)

단점
이 것은 좋은 방법처럼 보인다. 이것은 이해하기 쉬고 코드도 단순하다. 근접 리스트 모델의 단점이 무엇일까? 대부분의 프로그래밍 언어에서 이런 방식은 느리며 비효율적이다. 주된 원인은 재귀호출에 있다. 우리는 트리내의 각 노드를 위한 데이터베이스쿼리가 필요하다. 

두번째 이유는 당신이 사용하는 거의 모든 언어에서 이 방법은 빠르지 않다는 것이다. Lisp와 같은 언어를 제외하고 대다수는 재귀호출 함수를 고려해 설계되지 않았다. 트리가 네단계의 레벨을 갖는 경우 동시에 함수의 네개 인스턴스가 동시에 실행될 것이다. 각 함수는 메모리조각을 사용하며 일정부분 초기화시간을 사용할 것이다. 이런 재귀호출은 트리가 클경우 매우 느려진다.



수정된 순서화된 트리 검색(Modified Preorder Tree Traversal)

이 번에는, 트리를 저장하는 다른 방법에 대해 살펴 보겠다. 재귀호출은 느려질 수 있으므로, 이번에는 재귀적인 방법은 사용하지 않기로 하겠다. 우리는 데이터베이스 쿼리를 최소화할 것이다. 되도록이면 각 액티비티에 한번의 쿼리를 사용할 것이다.

우 리는 트리를 가로접근법으로 시작할 것이다. 최상위(root)노드에서 시작해 왼쪽에 1을 쓴다. 그리고, Fruit의 왼쪽에 2를 쓴다. 이 방법으로 각 노드의 좌,우측에 숫자를 써내려간다. 맨 마지막 번호는 "Food"노드의 맨 오른쪽에 쓰여질 것이다. 아래 그림에서 전체 숫자와 순서에 따른 화살표를 확인 할 수 있다.


보시다시피, 이 숫자들은 각 노드간의 연관을 가리킨다. "Red"노드는 숫자 3과 6을 가지고 있다. 그러므로, 숫자 1과 18을 가진 "Food"노드의 자손이다. 같은 방법으로 노드의 왼쪽값이 "2"보다 크고 오른쪽 값이 "11"보다 작은 노드는 "Fruit" 노드의 자손이라고 할 수 있다. 트리구조는 이제 왼쪽값과 오른쪽값으로 저장될 수 있다. 이런 방법으로 트리를 조회하고 노드를 세는것을 "수정된 순서화 트리 검색(modified preorder tree traversal)" 알고리즘이라 부른다.

계속 진행하기 전에, 이 값들이 테이블에 어떻게 저장되는지 보자.


데이터베이스에서 "left"와 "right"는 특별한 의미를 가지므로 "lft", "rgt"로 컬럼명을 구분했다. 또한, 이제 더이상 "parent" 컬럼이 필요없음도 함께 주목하자. 이제 트리 구조를 구현하기 위한 lft 와 rgt 값을 저장했다.

트리 조회하기
왼쪽값과 오른쪽값으로 트리를 검색하려면, 먼저 조회하고자 하는 상위 노드부터 알아야 한다. 예를들어 "Fruit"서브트리의 경우, 당신은 외쪽값이 2와 11사이인 노드들만 가져와야 한다. 

SELECT * from tree WHERE lft BETWEEN 2 AND 11;

이것은 다음을 리턴할 것이다.


모든 트리를 한번의 쿼리로 가져왔다. 만약, 테이블에서 행을 추가하거나 삭제했다면 테이블이 올바른 순서로 정렬되어있지 않을것이므로 이 트리를 앞서했던 재귀함수와 같이 출력하려면 ORDER BY절을 쿼리에 추가해야 한다. 그러므로, 왼쪽값을 기준으로 정렬하도록 한다.

SELECT * FROM tree WHERE lft BETWEEN 2 AND 11 ORDER BY lft ASC;

이제 남은 문제는 왼쪽에 들여쓰기를 하는것 뿐이다.

트 리와 같이 보이기 위해서, 자식은 그의 부모보다 더 들여쓰면 된다. 이것은 오른쪽값의 스택을 유지하면 가능하다. 노드의 자식에 다다를때 마다 노드의 오른쪽 값을 스택에 추가한다. 당신은 노드의 오른쪽값은 항상 부모노드의 오른쪽 값보다 작다는것을 알 수 있다. 그러므로, 현재노드의 오른쪽값과 스택의 마지막 오른쪽 노드값을 비교하면, 당신이 아직 부모의 자식노드를 출력하고 있는지 알수 있다. 노드의 출력이 끝났을때, 스택으로부터 오른쪽값을 삭제한다. 만약, 당신이 스택의 요소를 세어보면 현재노드의 레벨을 알수있을 것이다.

<?php
function display_tree($root) {
// retrieve the left and right value of the $root node
$result = mysql_query('SELECT lft, rgt FROM tree '.
'WHERE title="'.$root.'";');
$row = mysql_fetch_array($result);

// start with an empty $right stack
$right = array();

// now, retrieve all descendants of the $root node
$result = mysql_query('SELECT title, lft, rgt FROM tree '.
'WHERE lft BETWEEN '.$row['lft'].' AND '.
$row['rgt'].' ORDER BY lft ASC;');

// display each row
while ($row = mysql_fetch_array($result)) {
// only check stack if there is one
if (count($right)>0) {
// check if we should remove a node from the stack
while ($right[count($right)-1]<$row['rgt']) {
array_pop($right);
}
}

// display indented node title
echo str_repeat(' ',count($right)).$row['title']."\n";

// add this node to the stack
$right[] = $row['rgt'];
}
}
?>


이 코드를 실행해보면 재귀적함수를 사용했을때와 같은 결과를 볼 수 있을 것이다. 우리의 새로운 함수는 더 빠르며, 재귀적이지 않고 단 두번의 쿼리만 사용한다.

노드의 경로
이 새로운 알고리즘으로 특정노드의 경로를 가져오는 새로운 방법도 찾을 수 있다. 이 경로를 얻기 위해서 우리는 모든 조상의 리스트를 필요로 한다. 

우리의 새로운 테이블 구조는 더 많은 일을 필요로 하지 않는다. 예를들어, 4-5를 갖는 "Cherry"노드의 경우, 노드의 왼쪽값이 4보다 작고, 오른쪽값이 5보다 큰값을 찾으면 된다. 

SELECT title FROM tree WHERE lft < 4 AND rgt > 5 ORDER BY lft ASC;

이 쿼리에서 정렬을 위해 ORDER BY절을 반드시 사용함을 주목하라. 이 쿼리는 다음을 리턴한다.

+-------+
| title |
+-------+
| Food |
| Fruit |
| Red |
+-------+

이제 이 값들을 "Cherry"와 연결하기만 하면 된다.

자손의 갯수
노드의 왼쪽값과 오른쪽값만 알려준다면, 몇개의 자손을 가지고 있는지 간단한 산술식으로 알아낼 수 있다.
각 자손은 노드의 오른쪽값을 2씩 증가시키므로, 자손의 숫자는 아래와 같이 계산될 수 있다:

자손수 = (오른쪽값 - 왼쪽값 - 1) / 2

이 간단한 식으로 나는 2-11의 "Fruits"노드가 4개의 자손노드를 가졌으며, 8-9인 "Banana"노드는 자식을 가지지 않은 노드임을 알 수 있다.

자동 트리 검색
지금까지 이 테이블로 할수있는 간단한 것들 몇가지를 보았다. 이제 우리가 어떻게 이 테이블을 자동을로 생성할 수 있는지 배워보겠다. 이제 우리를 위해 이 모든 카운팅과 트리조회를 수행하는 수크립트가 필요하다.

근접리스트를 수정된 순서화 트리검색 테이블로 변환하기 위한 스크립트를 작성하자.

<?php
function rebuild_tree($parent, $left) {
// the right value of this node is the left value + 1
$right = $left+1;

// get all children of this node
$result = mysql_query('SELECT title FROM tree '.
'WHERE parent="'.$parent.'";');
while ($row = mysql_fetch_array($result)) {
// recursive execution of this function for each
// child of this node
// $right is the current right value, which is
// incremented by the rebuild_tree function
$right = rebuild_tree($row['title'], $right);
}

// we've got the left value, and now that we've processed
// the children of this node we also know the right value
mysql_query('UPDATE tree SET lft='.$left.', rgt='.
$right.' WHERE title="'.$parent.'";');

// return the right value of this node + 1
return $right+1;
}
?>


이 것은 재귀함수이다. 당신은 반드시 이것을 rebuild_tree('Food',1); 과 같이 시작해야 한다. 함수는 "Food"노드의 모든 자식을 검색할 것이다. 더이상 자식이 없으면, 이것은 왼쪽과 오른쪽값을 설정한다. 왼쪽값에 값이 주어지면 오른쪽 값은 왼쪽값에 1을 더한다. 자식이 있을경우 반복하여 마지막 오른쪽 값을 리턴한다. 이 값이 "Food"노드의 오른쪽 값이 된다.

재귀는 이것을 이해하기 매우 복잡한 함수로 구현한다. 하지만, 이 함수는 이 섹션의 맨 처음에 구현했던것과 같은 결과를 달성한다. 이것은 트리를 조회하면서 노드를 하나씩 추가한다. 이 함수를 실행한 후에, 당신은 왼쪽과 오른쪽값이 그대로임을 볼 수 있을 것이다 (빠른 확인법: 최상위노드의 오른쪽값은 전체노드수의 2배이다).

노드 추가
트 리에 노드를 어떻게 추가할 것인가? 거기에는 두가지 방법이 있다: 테이블에 부모컬럼을 유지하고 단지 rebuild_tree()함수를 재실행하는것 -- 간단하지만 고상하지 않은 함수이다. 또는, 새로운 노드의 오른편에서 부터 모든 노드의 왼쪽과 오른쪽값을 갱신하는 것이다.

첫번째 방법은 단순하다. 당신은 근접 리스트 방법은 갱신에만 사용하고, 수정된 순서화 트리 검색 알고리즘을 조회를 위해 사용한다. 만약, 당신이 새로운 노드를 추가하려면 단지 테이블에 추가하고 부모컬럼을 설정하기만 하면 된다. 그리고 단순히 rebuild_tree() 함수를 재호출하는 것이다. 이것은 쉽다. 하지만, 큰 트리구조에서는 매우 비효율적이다.

두번째 방법은 새로운 노드의 오른쪽에 있는 노드의 왼쪽값과 오른쪽값을 변경하여 추가, 삭제하는것이다. 예를들어 보자. 우리는 새로운 과일종인 "Strawberry"를 "Red"의 마지막 자손에 추가하고 싶다. 먼저, 우리는 약간의 공간을 확보해야 한다. "Red"의 오른쪽 값은 6에서 8로 변경되어야 하며, 7-10인 "Yellow"노드는 9-12로 변경되어야 한다. "Red"노드를 갱신한다는 것은 왼쪽과 오른쪽값이 5보다 큰값들에 2를 더해야 함을 의미한다.

우리는 다음 쿼리를 사용할 것이다.

UPDATE tree SET rgt=rgt+2 WHERE rgt>5;
UPDATE tree SET lft=lft+2 WHERE lft>5;

이제 우리는"Strawberry"노드를 새로은 공간에 추가할수 있다. 이 노드는 왼쪽에 6, 오른쪽에 7을 가진다.

INSERT INTO tree SET lft=6, rgt=7, title='Strawberry';

display_tree()함수를 실행하면, 새로운 "Strawberry"노드가 추가된 것을 볼 수 있을 것이다.

Food
Fruit
Red
Cherry
Strawberry
Yellow
Banana
Meat
Beef
Pork

단점
수 정된 순서화된 트리조회 알고리즘(modified preorder tree traversal algorithm)은 이해하기 약간 어려워 보인다. 이것은 분명히 근접리스트방법 보다 덜 간단하다. 하지만, 한번 당신이 왼쪽과 오른쪽 속성을 사용하게 되면, 당신은 근접리스트방법으로 했던 모든것을 이 테크닉으로 할수 있음이 명확해 질것이다. 그리고, 수정된 순서화 트리조회 알고리즘은 훨씬 빠른다. 트리를 갱신하는데는 쿼리를 더해 느려지지만 노드를 조회하는것은 단지 하나우 쿼리로 할 수 있다.

결론
이제 당신은 데이터베이스테 트리를 저장하는 두가지 방법과 친숙해 졌다. 나는 수정된 순서화 트리 조회를 좀 더 선호하지만 당신은 때에 따라 근접리스트 방법이 더 나을수도 있다. 그 결정은 당신의 몫으로 남겨두겠다.

마지막 조언: 이미 언급한바와 같이 나는 노드의 제목(title)으로 노드를 참조하는것을 추천하지 않는다. 당신은 반드시 데이터베이스 일반화의 기본적인 규칙을 따라야 한다. 나는 예제가 읽기 편하게 하기 위해 숫자형태의 구분값을 사용하지 않았을 뿐이다.
 
원문주소 : http://www.sitepoint.com/print/hierarchical-data-database
번역 : 이원찬



    
    // category_id, name, parent
    
    function display_children($parent, $level) {
        // retrieve all children of $parent
        $result = mysql_query('SELECT * FROM category WHERE parent="'.$parent.'";');
        
       // $sql = 'SELECT category_id FROM category WHERE parent="'.$parent.'";';
//        echo $sql;
        
        // display each child
        while ($row = mysql_fetch_array($result)) {
            // indent and display the title of this child
            echo str_repeat('&nbsp;',$level).$row['name']."<Br>";
            
            // call this function again to display this
            // child's children
            display_children($row['category_id'], $level+1);
        }
    }
    
    
    
    display_children('',0);


function tree_set($index$tree)
{
    
$q=mysql_query("select * from categories where parent_id=$index");
    if(!
mysql_num_rows($q))
        return;
    
$tree[] = '<ul>';
    while(
$arr=mysql_fetch_assoc($q))
    {
        
$tree[] = '<li>';
        
$tree[] = $arr['name'];//you can add another output there
        
tree_set($arr['id'], &$tree[]);
        
$tree[] = '</li>';
    }
    
$tree[] = '</ul>';
    return 
implode(''$tree);
}  

tree_set(0, &$tree);

 function tree_set($index, $tree)
    {
        $q=mysql_query('SELECT * FROM category WHERE parent="'.$index.'";');
        if(!mysql_num_rows($q))
            return;
        echo '<ul>';
        while($arr=mysql_fetch_array($q))
        {
            echo '<li>';
            echo $arr['name'];//you can add another output there
            tree_set($arr['category_id'], &$tree[]);
            echo '</li>';
        }
        echo '</ul>';
        //return implode('', $tree);
        //return $tree;
    }  
    
    tree_set('', &$tree);

Codeigniter 용

function index()
    {          
        $sql = " select * from ci_category  order by parentid, name";
        $query = $this->db->query($sql);
                        
        $menuData = array(
            'items' => array(),
            'parents' => array()
        );
        
        foreach ($query->result_array() as $menuItem )
        {
          $menuData['items'][$menuItem['id']] = $menuItem;
          $menuData['parents'][$menuItem['parentid']][] = $menuItem['id'];
        }

        // output the menu
        $data['tree'] = $this->buildMenu(0, $menuData); 
        $this->load->view('main_v',$data);
        
        
    }
    
    function buildMenu($parentId, $menuData)
    {
        $html = '';        
              
        if (isset($menuData['parents'][$parentId]))
        {   
            if($parentId==0){
                $className =  'id="browser" class="filetree"';
                $className2 = 'folder';
            }else{
                $className = '';
                $className2 = 'file';
            }
        
            $html = '<ul '.$className.'>';
            foreach ($menuData['parents'][$parentId] as $itemId)
            {
                $html .= '<li><span class="'.$className2.'">' . $menuData['items'][$itemId]['name'] . '</span>';
    
                // find childitems recursively
                $html .= $this->buildMenu($itemId, $menuData);
    
                $html .= '</li>';
            }
            $html .= '</ul>';
        }
    
        return $html;
    } 



'Computer > PHP' 카테고리의 다른 글

트리구조 재귀호출  (0) 2011/11/17
qmail 흐름도  (0) 2011/10/02
아웅... Framework......  (0) 2009/03/30
아놔,스팸 필터링......  (2) 2009/03/24
Posted by 알찬돌삐

댓글을 달아 주세요

qmail 흐름도

Computer/PHP 2011/10/02 15:08



출처 :  qmail.kldp.org


'Computer > PHP' 카테고리의 다른 글

트리구조 재귀호출  (0) 2011/11/17
qmail 흐름도  (0) 2011/10/02
아웅... Framework......  (0) 2009/03/30
아놔,스팸 필터링......  (2) 2009/03/24
Posted by 알찬돌삐

댓글을 달아 주세요

아웅......

Framework.......

개인적으로 만들고자 하는 게시판..

그누보드나 제로보드 , XE 는 써보지 않았기에.

예전부터 하나 만들어보고자 했는데.

제대로 된 게시판 하나 만드는게 이리 힘들줄이야 --..

처음 생각은 100만건 이상에서도 목록에서 2초 이내 목록을 가져올수 있도록 했는데,

하면 할수록 이건 포기하고 이건 포기하고.. ㅠ.ㅡ

MVC... 70% 정도 해놓고 또 지우고 새로 적고..


MVC 패턴 따르면서 하니 더 힘들어 아웅.

헥헥.

지금 쓰는 framework 는 Codeigniter ... codeigniter 관련 한글메뉴얼은 http://www.day.pe.kr 참고 바람.

저작자 표시 비영리 변경 금지

'Computer > PHP' 카테고리의 다른 글

qmail 흐름도  (0) 2011/10/02
아웅... Framework......  (0) 2009/03/30
아놔,스팸 필터링......  (2) 2009/03/24
미국내 개발자 일자리 동향..  (2) 2008/12/15
Posted by 알찬돌삐
TAG MVC, 게시판

댓글을 달아 주세요

아놔.. 스팸 필터링.

회사 홈페이지에 자꾸 스팸게시물이 등록되어서

IP 로만 필터링 하다가 단어들도 걸러야겠다 싶어서,

스팸 단어들을 넣어두고 필터링 했는데, 계속 스팸이 들어왔음.

"아씨............. 이상하다. 따로 테스트 하면 잘 되는데 왜 스팸 게시물이 올라올까" 라는 고민을 해봤지만,

다른 일에 밀려서 잊어버림..

오늘 또 보니 스팸 게시물 두둥....... 열건이나 포스팅 되어 있음.

젠장. 원인을 찾자......

나름대로 자주 올라오는 스팸단어들.. 바카라, 섹스, 비아그라, 애인대행 등등.. 을 넣었다.



꺄올......
오타쳤다.. contents 인데 content 를 해놓으니,

걍 통과되는거야 뻔한거 ㅠ.ㅡ

PS. 주석에 $cnt 가 0 보다 크면인데.
      원래는 preg_match_all 로 했다가 주석을 변경안했습니다. --.
      오해하지 마세요 ㅋ.
 
저작자 표시 비영리 변경 금지

'Computer > PHP' 카테고리의 다른 글

아웅... Framework......  (0) 2009/03/30
아놔,스팸 필터링......  (2) 2009/03/24
미국내 개발자 일자리 동향..  (2) 2008/12/15
PHP 코드를 최적화하는 40가지 팁 (번역)  (0) 2008/08/21
Posted by 알찬돌삐

댓글을 달아 주세요

  1. 뽀라 2009/03/26 23:19  댓글주소  수정/삭제  댓글쓰기

    내메일도 필터링되는거 아녜요?
    내가 저기 해당하는 단어를 넣었던가?? ^^
    메일 보냈는데 댓꾸도 없고....
    씹힌건가.. 했는데^^

    • 알찬돌삐 2009/03/27 15:40  댓글주소  수정/삭제

      엥.
      메일 안 왔던데요 --;
      구글 메일이 걸러버렸나 ㅠ
      그때 뽀라님한테 알려준 메일은 구글메일이거든요 ㅠ.ㅡ


ㅋㅋ.

그러면 뭐하냐고..

영어를 못하는데..

영어 잘하면 걍 한국에서 살면 우왕ㅋ굳ㅋ..임..

http://www.simplyhired.com/a/jobtrends/trend/q-Perl%2C+Python%2C+PHP%2C+Ruby%2CASP%2CASP.NET%2CJava%2CAJAX%2CRuby+On+Rails%2C+Flash

저작자 표시 비영리 변경 금지
Posted by 알찬돌삐

댓글을 달아 주세요

  1. 모던보이 2008/12/15 19:48  댓글주소  수정/삭제  댓글쓰기

    쇼발 영어는 1000% 증가!

가끔 PHP로 웹페이지를 작성할 일이 있는데, 유용한 팁을 우연히 보게 되어 한글로 옮겨적어본다.
원본은 40 Tips for optimizing your php Code

1. If a method can be static, declare it static. Speed improvement is by a factor of 4.
메쏘드가 static이 될 수 있다면 static으로 선언하라. 4배 빨라진다.

2. echo is faster than print.
echo가 print보다 빠르다.

3. Use echo’s multiple parameters instead of string concatenation.
문자열을 이어붙이지 말고, echo를 이용하여 여러 개의 파라미터를 적어라.

4. Set the maxvalue for your for-loops before and not in the loop.
for 루프을 위핸 최대값(탈출조건)을 루프 안에서가 아니고 루프 시작 이전에 지정하라.

5. Unset your variables to free memory, especially large arrays.
메모리를 해제하기 위해 변수를 unset하라. 특히 커다란 배열은 그래야 된다.

6. Avoid magic like __get, __set, __autoload
__get, __set, __autoload와 같은 마법을 피해라.

7. require_once() is expensive
require_once()는 비싸다.

8. Use full paths in includes and requires, less time spent on resolving the OS paths.
include와 require를 사용할 때, 경로를 찾는데 시간이 적게 걸리는 full path를 사용하라.

9. If you need to find out the time when the script started executing, $_SERVER[’REQUEST_TIME’] is preferred to time()
스크립트가 언제 실행했는지 알고 싶으면 time()보다 $_SERVER['REQUEST_TIME']이 좋다.

10. See if you can use strncasecmp, strpbrk and stripos instead of regex
정규표현식보다는 가능하면 strncasecmp나 strpbrk, stripos를 사용하라.
* 역주
strncasecmp: 두 문자열의 앞쪽 일부가 대소문자 구분없이 일치하는지 확인할 때 사용
strpbrk: 문자 집합에 속한 특정 문자가 문자열에 나타나는지 확인할 때 사용
stripos: 대소문자 구분없이 특정 문자열이 다른 문자열에 포함되는지 확인할 때 사용

11. str_replace is faster than preg_replace, but strtr is faster than str_replace by a factor of 4
str_replace가 preg_replace보다 빠르지만, strtr은 str_replace보다 4배 빠르다.

12. If the function, such as string replacement function, accepts both arrays and single characters as arguments, and if your argument list is not too long, consider writing a few redundant replacement statements, passing one character at a time, instead of one line of code that accepts arrays as search and replace arguments.
만약 문자열 교체 같은 함수가 배열과 문자열을 인자로 받아들이면, 그리고 그 인자 리스트가 길지 않다면, 배열을 한 번에 받아들여서 처리하는 것 대신에 한 번에 문자열을 하나씩 넘겨서 처리하는 것을 고려해봐라.

13. It’s better to use select statements than multi if, else if, statements.
여러 개의 if/else if 문장 대신에 select 문장을 사용하는 게 더 좋다.

14. Error suppression with @ is very slow.
@를 이용한 에러 출력 방지는 매우 느리다.

15. Turn on apache’s mod_deflate
Apache의 mod_deflate를 켜라.
*역주
mod_deflate는 서버의 출력을 클라이언트에게 보내기 전에 압축하는 모듈임

16. Close your database connections when you’re done with them
DB를 다 사용했으면 연결을 닫아라.

17. $row[’id’] is 7 times faster than $row[id]
$row['id']가 $row[id]보다 7배 빠르다.

18. Error messages are expensive
에러 메시지는 비싸다.

19. Do not use functions inside of for loop, such as for ($x=0; $x < count($array); $x) The count() function gets called each time.
for 루프의 표현식 안에서 함수를 사용하지 마라.
for ($x = 0; $x < count($array); $x)에서 count() 함수가 매번 호출된다.

20. Incrementing a local variable in a method is the fastest. Nearly the same as calling a local variable in a function.
메쏘드 안에서 지역 변수를 증가시키는 것이 거의 함수 안에서 지역 변수를 호출(증가?)하는 것만큼 빠르다.

21. Incrementing a global variable is 2 times slow than a local var.
전역 변수를 증가시키는 것이 지역 변수를 증가시키는 것보다 2배 느리다.

22. Incrementing an object property (eg. $this->prop++) is 3 times slower than a local variable.
객체의 멤버변수를 증가시키는 것이 지역 변수를 증가시키는 것보다 3배 느리다.

23. Incrementing an undefined local variable is 9-10 times slower than a pre-initialized one.
값이 지정되지 않은 지역 변수를 증가시키는 것이 미리 초기화된 변수를 증가시키는 것보다 9~10배 느리다.

24. Just declaring a global variable without using it in a function also slows things down (by about the same amount as incrementing a local var). PHP probably does a check to see if the global exists.
전역 변수를 함수 안에서 사용하지 않으면서 그저 선언하기만 해도 (지역 변수를 증가시키는 것만큼) 느려진다. PHP는 아마 전역 변수가 존재하는지 알기 위해 검사를 하는 것 같다.

25. Method invocation appears to be independent of the number of methods defined in the class because I added 10 more methods to the test class (before and after the test method) with no change in performance.
메쏘드 호출은 클래스 안에서 정의된 메쏘드의 갯수에 독립적인 듯 하다. 왜냐하면 10개의 메쏘드를 테스트 클래스에 추가해봤으나 성능에 변화가 없었기 때문이다.

26. Methods in derived classes run faster than ones defined in the base class.
파생된 클래스의 메쏘드가 베이스 클래스에서 정의된 것보다 더 빠르게 동작한다.

27. A function call with one parameter and an empty function body takes about the same time as doing 7-8 $localvar++ operations. A similar method call is of course about 15 $localvar++ operations.
한 개의 매개변수를 가지고 함수를 호출하고 함수 바디가 비어있다면(함수 내부에서 아무것도 실행하지 않는다면) 그것은 7~8개의 지역변수를 증가시키는 것과 똑같은 시간을 차지한다. 비슷한 메쏘드 호출은 마찬가지로 15개의 지역변수를 증가시키는 연산쯤 된다.

28. Surrounding your string by ‘ instead of ” will make things interpret a little faster since php looks for variables inside “…” but not inside ‘…’. Of course you can only do this when you don’t need to have variables in the string.
문자열을 이중 따옴표 대신에 단일 따옴표로 둘러싸는 것은 좀 더 빠르게 해석되도록 한다. 왜냐하면 PHP가 이중 따옴표 안의 변수를 찾아보지만 단일 따옴표 안에서는 변수를 찾지 않기 때문이다. 물론 문자열 안에서 변수를 가질 필요가 없을 때만 이렇게 사용할 수 있다.

29. When echoing strings it’s faster to separate them by comma instead of dot. Note: This only works with echo, which is a function that can take several strings as arguments.
문자열을 echo할 때 마침표 대신에 쉼표로 분리하는 것이 더 빠르다.
주의: 이것은 여러 문자열을 인자로 받아들이는 함수인 echo로만 작동한다.

30. A PHP script will be served at least 2-10 times slower than a static HTML page by Apache. Try to use more static HTML pages and fewer scripts.
Apache에 의해 PHP 스크립트는 정적 HTML 페이지보다 최소 2에서 10배 느리게 서비스된다. 더 많은 정적 HTML 페이지와 더 적은 스크립트를 사용하려고 노력하라.

31. Your PHP scripts are recompiled every time unless the scripts are cached. Install a PHP caching product to typically increase performance by 25-100% by removing compile times.
PHP 스크립트는 캐시되지 않으면 매번 재 컴파일된다. 컴파일 시간을 제거함으로써 25~100%만큼의 성능을 증가시키기 위해 PHP 캐싱 도구를 설치하라.

32. Cache as much as possible. Use memcached - memcached is a high-performance memory object caching system intended to speed up dynamic web applications by alleviating database load. OP code caches are useful so that your script does not have to be compiled on every request
가능한 한 많이 캐시하라. memcached를 사용하라. memcached는 고성능 메모리 객체 캐싱 시스템이다.

33. When working with strings and you need to check that the string is either of a certain length you’d understandably would want to use the strlen() function. This function is pretty quick since it’s operation does not perform any calculation but merely return the already known length of a string available in the zval structure (internal C struct used to store variables in PHP). However because strlen() is a function it is still somewhat slow because the function call requires several operations such as lowercase & hashtable lookup followed by the execution of said function. In some instance you can improve the speed of your code by using an isset() trick.
문자열을 가지고 작업하며 문자열이 특정 길이인지 확인할 필요가 있을 때, strlen() 함수를 쓸 것이다. 이 함수는 계산없이 zval 구조체에서 사용할 수 있는 이미 알려진 문자열 길이를 반환하기 때문에 매우 빠르다. 그러나 strlen()이 함수이기 때문에 여전히 조금 느리다. 왜냐하면 함수 호출은 언급된 함수의 실행 뒤에 lowercase와 hashtable lookup같은 여러 개의 연산을 호출하기 때문이다. 어떤 경우에는 isset() 트릭을 이용하여 코드의 스피드를 증가시킬 수도 있다.

Ex.
if (strlen($foo) < 5) { echo "Foo is too short"; }
vs.
if (!isset($foo{5})) { echo "Foo is too short"; }

Calling isset() happens to be faster then strlen() because unlike strlen(), isset() is a language construct and not a function meaning that it's execution does not require function lookups and lowercase. This means you have virtually no overhead on top of the actual code that determines the string's length.
isset()을 호출하는 것은 strlen()과는 달리 isset()이 언어 기본문법이고 함수가 아니기 때문에 함수 찾와 lowercase 작업을 필요로 하지 않으므로 strlen()보다 더 빠를 수도 있다. 이것은 가상적으로 문자열의 길이를 결정하는 실제 코드에 과부하가 없다는 것을 의미한다.

34. When incrementing or decrementing the value of the variable $i++ happens to be a tad slower then ++$i. This is something PHP specific and does not apply to other languages, so don't go modifying your C or Java code thinking it'll suddenly become faster, it won't. ++$i happens to be faster in PHP because instead of 4 opcodes used for $i++ you only need 3. Post incrementation actually causes in the creation of a temporary var that is then incremented. While pre-incrementation increases the original value directly. This is one of the optimization that opcode optimized like Zend's PHP optimizer. It is a still a good idea to keep in mind since not all opcode optimizers perform this optimization and there are plenty of ISPs and servers running without an opcode optimizer.
변수 $i의 값을 증가시키거나 감소키킬 때, $i++은 ++$i보다 조금 더 느릴 수 있다. 이것은 PHP의 특징이고 다른 언어에는 해당되지 않으니 좀 더 빨라질 것을 기대하면서 C나 Java 코드를 바꾸러 가지 마라. 안 빨라질 것이다. ++$i는 PHP에서 좀 더 빠른데 그것은 $i++에 4개의 opcode가 사용되는 대신에 3개만 필요하기 때문이다. 후증가는 사실 증가될 임시변수의 생성을 초래한다. 반면에 전증가는 원래 값을 직접 증가시킨다. 이것은 opcode가 Zend의 PHP optimizer처럼 최적화하는 최적화 기법의 하나이다. 모든 opcode optimizer들이 이 최적화를 수행하는 것은 아니고 많은 ISP와 server들이 opcode optimizer없이 수행되고 있기 때문에 명심하는 게 좋을 것이다.

35. Not everything has to be OOP, often it is too much overhead, each method and object call consumes a lot of memory.
모든 것이 OOP일 필요는 없다. 종종 그것은 너무 많은 과부하가 된다. 각각의 메쏘드와 객체 호출은 메모리를 많이 소비한다.

36. Do not implement every data structure as a class, arrays are useful, too
모든 데이터 구조를 클래스로 구현하지 마라. 배열도 유용하다.

37. Don't split methods too much, think, which code you will really re-use
메쏘드를 너무 많이 분리하지 마라. 어떤 코드를 정말 재사용할지 생각해봐라.

38. You can always split the code of a method later, when needed
항상 메쏘드의 코드를 나중에 필요할 때 분리할 수 있다.

39. Make use of the countless predefined functions
수많은 미리 정의된 함수를 활용해라.

40. If you have very time consuming functions in your code, consider writing them as C extensions
매우 시간을 소비하는 함수가 있다면, C 확장으로 작성하는 것을 고려해봐라.

41. Profile your code. A profiler shows you, which parts of your code consumes how many time. The Xdebug debugger already contains a profiler. Profiling shows you the bottlenecks in overview
당신의 코드를 프로파일해봐라. 프로파일러는 코드의 어떤 부분이 가장 많은 시간을 소비하는지 보여준다. Xdebug 디버거는 이미 프로파일러를 포함하고 있다. 프로파일링은 전체적인 병목을 보여준다.

42. mod_gzip which is available as an Apache module compresses your data on the fly and can reduce the data to transfer up to 80%
Apache 모듈로 사용가능한 mod_gzip은 실행 중에 데이터를 압축하여 전송할 데이터를 80%까지 줄일 수 있다.

43. Excellent Article about optimizing php by John Lim
John Lim의 PHP를 최적화하는 것에 관한 뛰어난 글


출처 조영일의 자리매김




require_once 가 자원을 많이 소비한다니.. 이럴수가..
충격 ㅠ.
난 이거 자주 애용하는데.. 흑.......

Posted by 알찬돌삐
TAG php

댓글을 달아 주세요

제목 : 예전 클라이언트의 연락

나(글쓴이)
클(클라이언트)
후(후로그래머,프리랜서)

띠리리리링~

나 : 엽떼여~ 더뻬빠 이빈다~

클 : 안냐세연~ 예전에 XXX 이빈다~

나 : 아핳~ 안냐세연~ 장사는 잘 되세연?

클 : 그게... 쩜 수정할게 있어서효~

나 : 네에~ 돈만 많이 주시면 다 해드려염~

클 : 다른 사람한테 이미 맞겼는데연~ 머좀 물어몰라구용~

나 : 질문시간 10초 드리겠스빈다. 10초 지나면 전화 끊어지빈다. 8초, 9초 그런거 없지말임다.

클 : 그게여~ 이번에 일해줄 후리랜서가효~ 로긴 화면 나오는거 화면에 나오기는 하는데 소스보기 하면 하나도 안보인다고, 그거 소스좀 알려달래연~

나 : 그거 Ajax로 처리해서 그냥 소스보기 하면 안나오구연~ 잣스 쪽에 보심 해당 파일 받아오는거 있어연~

클 : 아나~ 아작쓰 써달란 말도 안했는뎀, php말고 다른건 쓰지 말라고 했잖아여~

나 : Ajax는 걍 잣스 이빈다.

클 : 네... 건 글코... 왤케 복잡하게 해 놨어연? 나중에 수정하기가 힘들잖아연!

나 : 로긴 페이지 하나, 로긴체크 페이지 하나, 로긴 프로세스 페이지 하나인데 뭐가 복잡해연?

클 : 우리 후리랜서가 너무 복잡해서 도저히 싸게는 못한다잖아연~ 첨부터 좀 쉽게 만들어 주지...

나 : 걔 옆에 있으삼?

클 : 네... 잠만여~

후 : 넹~ 전화 바갔떠염~

나 : 뭐가 문제임?

후 : 그니깐... 로긴 화면이 화면에 뜨긴 하는뎀 소스보기해도 안나와염~ 이거 어케 한거임?

나 : Ajax로 로긴 화면 불러오는거임. 로긴 버튼에 이벤트 바인딩 되어 있뜸~ 거 보면 로긴 화면 소스 경로 나옴

후 : 뭐 이런 소스가 다 있음? 대체 로긴 화면을 어디서 가져오는 거임?

나 : ... 안드로메다. 됐음?

후 : 장난하지 말고, 나 경력 6년임! 이런 개판 소스는 첨봄!

나 : 파일 하나하나 마다 주석처리 다 되어 있는데 뭔 소리임? 님 잣스 모름?

후 : 잘 보니 잣스도 아니네~ 잣스에 $('foo')쓰는거 본적 없뜸!

나 : 그건 후로토타입 임.

후 : 그니깐 잣스도 아닌걸 왜씀? 이러면 다른데 호스팅에서도 잘 안됨~

나 : 이런~ 알흠다운 호로구람어를 봤나!

후 : 왜 욕을 함!? 나도 소스 보면 대충 다 앎~ 이건 완전 멋대로임.

나 : 그럼 님하가 더 좆케 만드삼~ 싸장님하 바꿔 주센~

클 : 엽떼여? 아나~ 이거 새로 다 만들어야 한다는데 돈 이중으로 나가게 생겼뜸~ 어쩔거임?

나 : 그니깐... 추가할 기능 뭐임?

클 : 자동 로그인.

나 : 그게 다임?

클 : 그게 다임.

나 : 후리랜서가 비용 얼마 든데염?

클 : 로그인 부분 다시 다 만들고 뭐도 수정하고 해서 80만원 달라고 함~

나 : 아나~ 이런 알흠다운 클라이언트 님하를 봤나...

클 : 암튼~ 님하네가 만든거 땜에 돈 와방 깨지겠뜸. 어쩔거임!?

나 : ...일단, 30분 뒤에 사이트 다시 들어가 보센.




대략 30분 후...

띠리리릴~

클 : 헉! 자동 로긴 잘됨! 조낸 ㄳ~

나 : 앞으로 연락 안했음 좆겠뜸. 글고 이거 10만원 주센~

클 : 아나~ 조랭 ㅈㅅ~ 내가 잘 몰라서리~ 30분 하고 10만언은 쩜 사기 아님?

나 : 그럼 후리랜서한테 80주고 하등가...

클 : ... 알았뜸. 10만원 입금 하겠뜸.

나 : 암튼 ㄳ~ 아직도 후리랜서 옆에 있뜸?

클 : 잠만여.

후 : 엽떼여...

나 : 님하 경력 몇년?

후 : 아나... 낵아 소스를 잘못 봤뜸. 다시 보니 금방 할거였뜸.

나 : 그럼 다시 롤빽 해 놓을테니 님하가 하센.

후 : 이미 끝난거 안함. 더이상 할말없뜸.

신발... 날씨도 더운데 별 그지 깽깽이 같은 일이... 귀때기 얇은 클라이언트나 걸 또 후리려고 하는
호로구람어나... 참... 가지가지로 논다. 덕분에 황금같은 오후의 한시간을 날렸군하...
오늘 중으로 10만원 입금 안하면 다시 롤빽 하고 연락두절. 하여간 짜증 지대임.







스쿨에 올라온 글 중 간만에 폭소한 글 ㅋㅋㅋㅋ
Posted by 알찬돌삐

댓글을 달아 주세요

  1. 모던보이 2008/07/10 21:26  댓글주소  수정/삭제  댓글쓰기

    ㅋㅋㅋㅋㅋ 아놔 이거 못봉건데 암튼 알흠다운 스토리

우왕ㅋ.....

PHPSCHOOL 팁텍에 보고서 적용하기 귀찮아서 안하다가.

아파치 동적으로 설치해났으니 모듈만 올리면 되겠다 싶어서.

아파치 설치 디렉토리 가서.

src/module/standard 로 이동한 다음에

/usr/local/apache/bin/apxs -aic mod_expires.c

mod_expires.so 가 생성이 되었다.

httpd.conf 들어가서.

LoadModule expires_module     libexec/mod_expires.so
<IfModule mod_expires.c>
    ExpiresActive On
    <Directory "/usr/local/apache/htdocs/xxxx/xxxx/xxx/img">
     ExpiresDefault "access plus 1 month"
    </Directory>
    <Directory "/usr/local/apache/htdocs/xxxx/xxxx/img">
     ExpiresDefault "access plus 1 month"
    </Directory>
    <Directory "/usr/local/apache/htdocs/xxxxxxx/img">
     ExpiresDefault "access plus 1 month"
    </Directory>
</IfModule>

딱 해주고 아파치 재시작했더니...

이미지만 만료일 줬는데 욜라 빨라짐 >_<

우왕ㅋ굳.

js 랑 css 도 설정해야겠답 >_<

ExpiresByType application/x-javascript "access plus 1 month"
ExpiresByType text/css "access plus 1 month"

관련 URL : http://httpd.apache.org/docs/1.3/mod/mod_expires.html
Posted by 알찬돌삐

댓글을 달아 주세요

예전에 phpschool 에 공동 프로젝트 비스무리한 메뉴가 있었는데 ,

어느순간 사라지더니 이번에. PHP 컨설팅 사업 시작을 알리는 메뉴가 등장했다.

http://www.phpschool.com/biznbaza/human_db_info.php

국내최대의 php 관련 커뮤니티 사이트에서 이런 사업을 시작함에 따라,

앞으로 PHP 개발자들에게 어떤 앞날을 제시할것인지............
Posted by 알찬돌삐

댓글을 달아 주세요

다람쥐메일 + Qmail 을 이용하여 메일 서버 운영중 발생한 문제점들을 올립니다.

* 메일 발송시 제목에 () 괄호가 있으면 제목이 깨짐
원인 : quoted_printed 로 인코딩해서 보내는데 괄호는 1바이트라서 인코딩하지 않아서 보내면서 깨지는거 같음
해결책 : base64_encode 로 인코딩하여 발송

* 웹메일에서 편지쓰기 첨부한 파일 삭제시 삭제 안됨
원인 : compose.php 파일에서
isset($attach) 구문중
attach 변수에 데이터가 존재하지 않음에도 불구하고 변수가 존재하여서 삭제 함수까지 넘어가지 않음
해결책 : isset($attach) 구문을 아래와 같이 변경
$attach > 0 

* 삭제한 편지임에도 불구하고 메일목록에 표시된다.
원인 : 다람쥐메일 캐쉬기능
해결책 : use_mailbox_cache 란 세션변수가 존재함
1일때 캐쉬
0일때 캐쉬안함.
right_main.php 파일에서
use_mailbox_cache 변수를 강제로 0 으로 할당.

* 아웃룩 익스프레스에서 발송한 메일이 winmail.dat 란 첨부파일로 온다.
원인 : 아웃룩 익스프레스에서 HTML 메일 형식으로 메일 작성후 파일첨부시 RTF 인코딩 방식으로 자체 인코딩으로 발송한다.
MS 관련 문서 : [ 링크 ]
해결책 : squirrel mail plugins 를 다운받아서 적용하였음.
첨부파일 타입이 application/ms-tnef 일 경우 파일 이름을 누르면 디코딩하여 보여줌.
플러그인 다운로드 : [ 링크 ]

* 아웃룩 익스프레스에서 발송된 이미지들이 보이지 않음.
원인 : 이미지들이 메일 본문안에 첨부되어 날아옴. 보여질때 cid: 로 보여짐
해결책 : magicHTML 함수 이용 (다람쥐메일 기본함수)

뭔가 더 있었는거 같은데 기억이 안 나네 -_-;;;;;;;;;;;;;
흠~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
생각나면 더 추가할께연~
Posted by 알찬돌삐

댓글을 달아 주세요