ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [MongoDB] 5. 몽고DB를 사용한 웹 분석
    CSE/MongoDB 2015. 6. 13. 11:45
    이번 포스팅은 몽고DB를 사용한 웹 분석 ​을 진행해보도록 하겠습니다!

    내용을 설명하기 전, 준비해야할 것이 있습니다! 
     예전에 작성한 blog.php, blogs.php, dbconnection.php가 이번 실습하는 곳에 같은 곳에 존재해야 합니다!
     
    이번 포스팅의 순서는 다음과 같습니다. 
     1. 페이지 방문 로깅
     2. 블로그 포스트당 전체 방문 수와 평균 응답 시간 찾기
     3. 실시간 페이지 방문 카운트 구현







    먼저, 중요 개념을 설명하도록 하겠습니다.
     
    몽고DB가 웹 분석 백엔드로 좋은 이유!!!!
     - 몽고DB는 대량의 데이터를 다루는데 아주 적합
      : 몽고DB의 확장 가능한 기능(복제, 샤딩, 레플리카 집합 등)과 데이터 크기와 연산 횟수에 따라 최적으로 수행하는 기능은 점점 확장되고 있다. 트래픽이 무척 큰 웹사이트는 사이트에서 일어나는 사용자 활동 전체를 저장하기 위해 몽고DB를 사용할 수 있으며, 배경 작업으로 데이터를 처리하고 분석 할 수 있게 된다.

     - 몽고DB는 비동기식 삽입을 지원
     ​ : 비동기식 기능은 PHP, C, 몽고 셸의 자바스크립트 인터페이스 중 무엇을 사용하든 애플리케이션 코드가 몽고 DB에 다큐먼트를 삽입한 다음에 서버의 응답을 기다리지 않고 다음 명령으로 이동하도록 요청한다. 이 특징이 훌륭한 로깅 도구로 만들어 준다. 

     - 맵리듀스!

    자, 그럼 실습을 진행해보도록 하겠습니다!






     1. 페이지 방문 로깅

    먼저 log.php를 작성합니다.

    log.php

     

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    <?php
    require_once('dbconnection.php');
    define('LOGNAME''access_log');
     
     
    class Logger {
    private $_dbconnection;
    private $_db;
     
    public function __construct() {
    $this->_dbconnection = DBConnection::instantiate();
    $this->_collection = $this->_dbconnection->getCollection(LOGNAME);
    }
     
    public function logRequest($data = array()) {
    $request = array();
    // $_SERVER에 접근하여 HTTP 요청정보 얻음
    $request['page'] = $_SERVER['SCRIPT_NAME'];
    $request['viewed_at'] = new MongoDate($_SERVER['REQUEST_TIME']);
    $request['ip_address'] = $_SERVER['REMOTE_ADDR'];
    $request['user_agent'] = $_SERVER['HTTP_USER_AGENT'];
     
    if (!empty($_SERVER['QUERY_STRING'])) {
    $params = array();
    foreach (explode('&'$_SERVER['QUERY_STRING']) as $parameter) {
    list($key$value) = explode('='$parameter);
    $params[$key] = $value;
    }
    $request['query_params'] = $params;
    }
     
    if (!empty($data)) {
    $request = array_merge($request$data);
    }
    $this->_collection->insert($request);
    }
    }
     
    ?>
     
    cs



    다음, blog.php 파일을 열어, 첫 부분에 다음 코드를 추가해주세요. (당연히 <?php ?> 안에 넣어 주시면 됩니다)
     


    1
    2
    3
    4
     
        require('log.php');
        $start = microtime();
     
    cs




    그리고 마지막 부분에다 이 코드를 추가하세요.(</html> 밑에다가 넣어주세요)


     

    1
    2
    3
    4
    5
    6
    7
    8
     
     
    <?php
    $end = microtime();
    $data = array('response_time_ms' => ($end - $start) * 1000);
    $logger = new Logger();
    $logger->logRequest($data);
    ?>
    cs

     






    mongo 셸로 가서 

     use myblogsite
     db.createCollection('access_log', {capped: true, size: 100000}) 

    이렇게 작성해주세요!




     







    위 작업을 마치고 나서, blogs.php 페이지를 열어, 각 포스트를 Read More를 여러 번 클릭해주세요.
    그러고 나서, 셸에다가



     db.access_log.find()


    입력 합니다. 그럼 아래와 같이 출력 결과를 뿌려줍니다.












    위 로그를 볼때, 가장 오래된 다큐먼트를 먼저 반환하는데, 가장 새로운 다큐먼트 순으로 반환을 원할경우
    위 명령어 뒤에 .sort({$natural : -1})을 옵션을 주면 된다.  


     


     
    2. 블로그 포스트당 전체 방문 수와 평균 응답 시간 찾기

    지난 일주일 동안 블로그 포스트마다 방문한 숫자와 함께 화면 출력에 있어 평균 응답 시간을 파악하도록 맵과 리듀스 함수를 정의하는 프로그램을 작성해 보겠습니다! 

    먼저 page_views.php를 작성합니다

    page_views.php
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    <?php
    require('dbconnection.php');
    $dbConnection = DBConnection::instantiate();
    $db = $dbConnection->database;
     
    /* map 함수: 문서별로 count와 resp_time을 emit()으로 보냄 */
    $map = "function() { emit(this.query_params.id, {count: 1, resp_time: this.response_time_ms}) }";
     
    /* reduce 함수: 카운터와 응답 시간 값을 종합 */
    $reduce = "function(key, values) { ".
                    "var total_count = 0;".
                    "var total_resp_time = 0;".               
                    "values.foreach(function(doc) {".
                        "total_count += doc.count;".
                        "total_resp_time += doc.resp_time;".               
                    "});".               
                    "return {count: total_count, resp_time: total_resp_time};".
              "}";
     
    /* finalize 함수: 카운터 총합으로 응답 시간 총합을 나누는 방법을 사용해 평균 응답 시간을 찾음 */
    $finalize = "function(key, doc) {".
                     "doc.avg_resp_time = doc.resp_time / doc.count;".
                     "return doc;".
                "}";
     
    $db->command(array(
                        'mapreduce' => 'access_log'
                        'map' => new MongoCode($map),
                        'reduce' => new MongoCode($reduce),
        //                'query' => array('page' => '/blog.php', 
        //                                'viewed_at' => array('$gt' => new MongoDate(strtotime('-7 days')))),
                        'finalize' => new MongoCode($finalize),
                        'out'   => 'page_views_last_week'
                    )
                );
     
     
    $results = $dbConnection->getCollection('page_views_last_week')
                            ->find();
     
    function getArticleTitle($id) {
    global $dbConnection;
    $article = $dbConnection->getCollection('articles')->findOne(array('_id' => new MongoId($id)));
     
    return $article['title'];
    }
     
    //echo json_encode($results);
    ?>
     
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml" lang="en">
        <head>
            <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
            <title> Most viewd articles </title>
            <link rel="stylesheet" href="style.css"/>
            <style type="text/css" media="screen">
    body { font-size: 13px; }
    div#contentarea { width: 680px; }
    </style>
        </head>
        <body>
            <div id="contentarea">
                <div id="innercontentarea">
                    <h1> Most viewed articles (Last 7 days) </h1>
                    <table class="articles" cellpadding="0" cellspacing="0">
                        <thead>
                            <tr>
                                <th width="50%">Article</th>
                                <th width="25%">Page views</th>
                                <th width="*">Avg response time</th>
                            </tr>
                        </thead>
                        <tbody>
                            <?php foreach ($results->sort(array('value.count' => -1)) as $result):?>
                                <tr>
                                    <td><?php echo getArticleTitle($result['_id']);?></td>
                                    <td><?php echo $result['value']['count'];?></td>
                                    <td><?php echo sprintf('%f ms'$result['value']['avg_resp_time']);?></td>
                                </tr>
                            <?php endforeach;?>
                        </tbody>
                    </table>
                </div>
            </div>
        </body>
    </html>
     
     
     
    cs


     



    원래 7일간의 데이터를 통하여 얻어온다는 취지의 스크립트입니다만. 실행해보니 $db->command 안의 query 옵션이 이상해서 결과가 올바르게 나오지 않더군요... 그래서 저는 주석처리하고 결과를 출력했습니다.
    아 그리고 블로그 포스트의 내용이 짧으면 $start와 $end 타임이 같아서 응답시간이 0이 되버리는 문제도 있습니다.

    충분히 길게 포스트 내용을 긁어오시던지 작성해주세요
     




    결과 화면은 이렇습니다.
    음... 최신 포스팅에 뭔가 문제가 있는 것 같군요... 저것을 제외하고는 이상이 없습니다.










    3. 실시간 페이지 방문 카운트 구현하기
    여기서는 제가 도메인을 써서 만든 페이지가 아니라 기껏해야 제가 클릭한 것만 페이지 카운팅이 되므로 blogreader_bot을 둬서 페이지 리딩을 시키겠습니다.

    먼저 log.php 파일을 열어 메소드를 추가시켜 줍니다.

    log.php


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public function updateVisitCounter($articleId) {
        // 방문 카운트를 담을 Collection 선택(생성)
        $articleVisitCounterDaily = $this->_dbconnection->getCollection('article_visit_counter_daily');
        $criteria = array(
            'article_id' => new MongoId($articleId),
            'request_date' => new MongoDate(strtotime('today'))
        );
            
        $newObj = array('$inc' => array('count' => 1));
        
        $articleVisitCounterDaily->update($criteria, $newObj, array('upsert' => True));
    }
    cs

     





    다음으로 blog.php 파일을 열어 맨 끝에 아래 코드를 추가해 줍니다. ($logger->logRequest($data); 밑에 넣어주면 되겠죠?)


    1
    $logger->updateVisitCounter($id);
    cs




    blogreader_bot.php를 작성합니다.

     * 주의사항: 이 코드의 22번 라인을 보시면 localhost 뭐시껭 되있습니다. 눈치 빠르시다면 아무래도 자신의 포트설정, 디렉토리 경로 설정해주셔야 겠죠?? 저는 chap5/ 라는 디렉토리에 들어있으므로 아래와 같이 작성된겁니다.

    blogreader_bot.php

     

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    <?php
    require('dbconnection.php');
    $mongo = DBConnection::instantiate();
    $articles = $mongo->getCollection('articles');
    $articleIds = array();
     
    foreach ($articles->find(array(), array('_id' => TRUE)) as $article) {
    array_push($articleIds, (string) $article['_id']);
    }
     
    function getRandomArrayItem($array) {
    $length = count($array);
    $randomIndex = mt_rand(0$length - 1);
    return $array[$randomIndex];
    }
     
    echo 'Simulating blog post reading... ';
     
    while(1) {
    $id = getRandomArrayItem($articleIds);
     
    $url = sprintf('http://localhost:88/chap5/blog.php?id=%s'$id);
    $curlHandle = curl_init();
    curl_setopt($curlHandle, CURLOPT_URL, $url);
    curl_setopt($curlHandle, CURLOPT_HEADER, false);
    curl_setopt($curlHandle, CURLOPT_RETURNTRANSFER, true);
    curl_exec($curlHandle);
    curl_close($curlHandle);
    }
    ?>
    cs


    마지막으로 작성할 코드는 realtime_pageviews.php입니다.
     
    realtime_pageviews.php

     

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    <?php
    require 'dbconnection.php';
    $dbConnection = DBConnection::instantiate();
    $collection = $dbConnection->getCollection('article_visit_counter_daily');
     
    function getArticleTitle($id) {
    global $dbConnection;
    $article = $dbConnection->getCollection('articles')->findOne(array('_id' => new MongoId($id)));
     
    return $article['title'];
    }
     
    $obj = $collection->find(array('request_date' => new MongoDate(strtotime('today'))));
     
    ?>
     
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml" lang="en">
        <head>
            <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
            <link rel="stylesheet" href="style.css"/>
            <title> Daily Page views (in realtime) </title>
            <style type="text/css" media="screen">
    body { font-size: 13px; }
    div#contentarea { width: 680px; }
    </style>
        </head>
        <body>
            <div id="contentarea">
                <div id="innercontentarea">
                    <h1> Daily page views (in realtime) </h1>
                    <table class="articles" cellspacing="0" cellpadding="0">
                        <thead>
                            <tr>
                                <th>Articles</th>
                                <th>Viewed</th>
                            </tr>
                        </thead>
                        <tbody>
                        <?php foreach($obj->sort(array('count' => -1)) as $ob):?>
                            <tr>
                                <td><?php echo getArticleTitle((string) $ob['article_id']);?></td>
                                <td><?php echo $ob['count'];?></td>
                            </tr>
                        <?php endforeach;?>
                        </tbody>
                    </table>
                </div>
            </div>
        </body>
        <script type="text/javascript">
    var REFRESH_PERIOD = 5000;
    var t = setInterval("location.reload(true);", REFRESH_PERIOD);
    </script>
    </html>
     
    cs





      
    여기까지 했다면!  먼저 blogreader_bot.php 파일을 수행합니다!!!! 
    이와 동시에 realtime_pageviews.php를 열 준비를 하셔야 합니다.
    아예 2개로 분할해서 확인하세요. 브라우저를.

    왜냐하면!! 오른쪽 놈이 루프를 돌면서 랜덤으로 방문을 합니다.
    그러면서 실시간으로 왼쪽 페이지에서 5초간 refresh하면서 페이지 뷰한 횟수가 바뀝니다.
    요래 놓고 오른쪽 실행하면 5초마다 변합니다!!!








    자 이것으로 5장 몽고DB를 사용한 웹 분석을 마치겠습니다!! 






    댓글

Designed by Tistory.