ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [MongoDB] 6. 관계형 DB와 함께하는 몽고DB 활용
    CSE/MongoDB 2015. 6. 13. 11:46

    이번 포스팅은 관계형 DB와 몽고DB를 조합한 어플리케이션 형을 만들어 보겠습니다!!


    먼저 RDBMS와 MongoDB를 사용하는 잠재적인 사례에 대해 알아보도록 하겠습니다!

     - 집계 연산 질의 결과 저장: Cost가 많이 드는 집계 연산 질의 결과(COUNT, GROUP BY 등)를 몽고DB에 저장할 수 있습니다. 이런 기능은 결과가 유효하지 않을 때까지 애플리케이션에서 동일 질의를 다시 수행할 필요 없이 몽고DB에서 결과를 잽싸게 가져오게 만듭니다. 몽고DB 컬렉션의 스키마는 유연하므로, 결과 데이터의 구조를 미리 걱정할 필요가 없습니다. 집계 연산 질의가 반환하는 Row는 BSON 다큐먼트로 저장이 가능합니다.

     - 데이터 저장: 데이터량이 커질수록, 관계형 테이블에서 질의와 다른 연산 과정에 필요한 시간이 점점 더 늘어납니다. 이런 문제를 풀기위한 한가지 해법으로 데이터를 나눠 테이블 두개에 넣습니다. 여기서 Online 테이블은 작업 중인 데이터 집합을 포함하고, Archival 테이블은 옛날 데이터를 포함합니다. Online 테이블의 크기는 비슷한 수준을 유지하는 반면, Archival 테이블은 점점 커집니다. 이런 접근 방법의 단점은 Online 테이블의 스키마가 변경될 때, Archival 테이블의 스키마 역시 동일하게 변경되어야만 한다는 점이죠. 스키마 변경이 아주 느린 연산인 이유는 데이터량 때문입니다. 또한, Online 테이블에서 컬럼을 하나 이상 삭제하면, Archival 테이블에서도 동일 컬럼을 삭제해야만 합니다. 따라서 가치 있을지도 모르는 옛날 데이터를 잃어버리는 결과를 초래합니다. 이때, 몽고DB의 컬렉션을 사용하여 해법을 풀수 있는 겁니다. 몽고DB의 유연한 스키마 덕문에 예전 테이블과 새로운 테이블의 구조가 달라질 경우에도 뭔가를 반드시 수행할 필요가 없죠.

     - 로깅: 애플리케이션에서 이벤트 로깅 목적으로 몽고DB를 활용할 수 있습니다. RDBMS로도 구현 가능하지만, 몽고DB의 비동기식 삽입 기능과 몽고 질의 언어(Map reduce)로 더 나은 로깅 벡엔드를 만들수 있습니다.


     그럼 실습을 진행하도록 하겠습니다. 순서는 아래와 같습니다. 
      1. MySQL에서 DB 생성
      2. 몽고DB에 일일 제품 판매 이력 저장
      3. 몽고DB에 예전 판매 기록 저장
      4. 몽고DB를 사용해 고객 메타 데이터 저장


    이번 장은 MySQL에 DB를 생성하여 예제 데이터를 입력해서 실습해야합니다. 책에서 아주 친절히(?) 예제 생성하는 방법만 알려주고, 예제 데이터에 대한 예시는 없습니다... 저 또한, 귀차?니즘? 때문에 만들지 않았구요... 그래서 웹페이지 실습시 텅텅 비어있을 겁니다...







    1. MySQL에서 DB 생성
     먼저 mysql shell을 열어 줍니다.(mysql -u root -p) 
     첨부된 sql파일을 받아서 mysql에서 수행합니다. 모든 sql문 들이 성공했는지 확인하시면 끝!




    2. 몽고DB에 일일 제품 판매 이력 저장하기
    mysql.php를 생성하여 아래 코드를 입력합니다.

    * 주의: 여기서 MYSQL_USER와 MYSQL_PASSWD 등은 자신이 설정한 user 명이나 패스워드를 입력하세요!!

    mysql.php

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    <?php
    define('MYSQL_HOST''localhost');
    define('MYSQL_PORT'3306);
    define('MYSQL_USER''root');
    define('MYSQL_PASSWD''**********');
    define('MYQL_DBNAME''acmeproducts');
     
    function getMySQLConnection() {
    $mysqli = new mysqli(MYSQL_HOST, MYSQL_USER, MYSQL_PASSWD, MYQL_DBNAME, MYSQL_PORT);
     
    if (mysqli_connect_error()) {
    die(sprintf('Error connecting to MySQL. Error No: %d, Error: %s',mysqli_connect_errno(), mysqli_connect_error()));
    }
     
    return $mysqli;
    }
    ?>
     
     
    cs



    dbconnection.php를 복사해와서 DBNAME의 값을 acmeproducts_mongo로 변경합니다.

    1
    2
    3
     
            const DBNAME = 'acmeproducts_mongo';
     
    cs

     






    그 다음, aggregate.php를 작성합니다. $query에서 띄어쓰기 유의하세요!! mysql에서 error 날 수도 있습니다!

    aggregate.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
     
    <?php
    require 'mysql.php';
    require 'dbconnection.php';
     
    // MySQL Section
    $query = 'SELECT name, DATE(time_of_sales) as date_of_sales, SUM(units_sold) as total_units_sold '.
     'FROM sales s INNER JOIN products p ON(p.id = s.product_id) '.
     'GROUP BY product_id, DATE(time_of_sales)';
     
    $mysql = getMySQLConnection();
    $result = $mysql->query($query);
     
    if ($result === False) {
    die(sprintf("Error executing query %s, %d"$mysql->error, $mysql->errno));
    }
     
    $salesByDate = array();
     
    while ($row = $result->fetch_assoc()) {
    $date = $row['date_of_sales'];
    $product = $row['name'];
    $totalSold = $row['total_units_sold'];
    $salesPerProduct = (isset($salesByDate[$date])) ? $salesByDate[$date] : array();
    $salesPerProduct[$product] = $totalSold;
    $salesByDate[$date] = $salesPerProduct;
    }
     
    $result->free();
    $mysql->close();
     
     
    // MongoDB Section
    $mongodb = DBConnection::instantiate();
    $collection = $mongodb->getCollection('daily_sales');
    foreach ($salesByDate as $date => $sales) {
    $document = array(
    'sales_date' => new MongoDate(strtotime($date)),
    'items' => array()
    );
     
    foreach ($sales as $product => $unitsSold) {
    $document['items'][$product] = $unitsSold;
    }
    $collection->insert($document);
    }
    ?>
     
    cs




    작성된 aggregate.php를 웹페이지에서 수행합니다. 그리고 daily_sales.php를 작성합니다.

    daily_sales.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
     
    <?php
    require 'dbconnection.php';
     
    $action = (isset($_POST['action'])) ? $_POST['action'] : 'default';
     
    // 입력 날짜 검증 메소드
    function validateInput() {
    if (empty($_POST['year']) || empty($_POST['month']) || empty($_POST['day'])) {
    return False;
    }
    $timestamp = strtotime($_POST['year'].'-'.$_POST['month'].'-'.$_POST['day']);
     
    if (!is_numeric($timestamp)) {
    return False;
    }
     
    return checkdate(date('m',$timestamp), date('d',$timestamp), date('Y',$timestamp));
    }
     
    switch ($action) {
    case 'Show': {
    if (validateInput() == True) {
    $inputValidated = True;
     
    $date = sprintf('%d-%d-%d'$_POST['year'], $_POST['month'], $_POST['day']);
    $mongodate = new MongoDate(strtotime($date));
    $mongodb = DBConnection::instantiate();
    $collection = $mongodb->getCollection('daily_sales');
    $doc = $collection->findOne(array('sales_date' => $mongodate));
    else {
    $inputValidated = False;
    }
    break;
    }
     
    default:
    }
    ?>
     
    <!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> Acme Corp | Daily Sales </title>
        </head>
        <body>
            <div id="contentarea">
                <div id="innercontentarea">
                    <h1> Daily Sales of Acme Products </h1>
                    <form action="<?php echo $_SERVER['PHP_SELF']; ?>" method="POST">
                        Enter Date (YYYY-MM-DD)
                        <input type="text" name="year" size=4/> -
                        <input type="text" name="month" size=2/> -
                        <input type="text" name="day" size=2/
                        <input type="submit" name="action" value="Show"/>
                    </form>
                    <?php if ($action === 'Show'): if ($inputValidated === True):?>
                        <h3<?php echo date('F j, Y'$mongodate->sec);?> </h3>
                    <?php if (!empty($doc)):?>
                        <table class="table_list" cellspacing="0" cellpadding="0">
                            <thead>
                                <tr>
                                    <th width="50%"> Item </th>
                                    <th width="25%"> $nbsp; </th>
                                    <th width="*"> Units Sold </th>
                                </tr>
                            </thead>
                            <tbody>
                                <?php foreach ($doc['items'as $item => $unitsSold):?>
                                    <tr>
                                        <td><?php echo $item;?></td>
                                        <td>&nbsp;</td>
                                        <td><?php echo $unitsSold;?></td>
                                    </tr>
                                <?php endforeach;?>
                            </tbody>
                        </table>
                        <?php elseecho "<p> No sales record found. </p>"
    endif; 
    elseecho "<h3> Invalid input. Try again.</h3>";
    endif;
    endif;?>
                </div>
            </div>
        </body>
    </html>
     
    cs








    브라우저에서 daily_sales.php를 수행하세요! 초기화면 입니다.








    요기다 임의 날짜를 입력해서 데이터가 담긴 페이지를 볼수 있으나, 예제 데이터가 없는 관계상 아래와 같이 출력이 되네요.








     3. 몽고DB에 예전 판매 기록 저장하기

    archive_sales_data.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
     
    <?php
    require 'mysql.php';
    require 'dbconnection.php';
     
    $cutoffDate = date('Y-m-d', strtotime('-30 day'));
    $mysql = getMySQLConnection();
     
    $query = sprintf("SELECT * FROM sales WHERE DATE(time_of_sales) < '%s'"$cutoffDate);
    printf("Fetching old data from MySQL...... \n");
    $result = $mysql->query($query);
     
    if ($result === False) {
    die(sprintf("Error executing query %s"$mysql->error));
    }
     
    printf("Migrating to MongoDB ..... \n");
     
    $mongo = DBConnection::instantiate();
    $collection = $mongo->getCollection('sales_archive');
     
    while ($record = $result->fetch_assoc()) {
    try {
    $collection->insert($record);
    } catch (MongoCursorException $e) {
    die ("Migration Failed ".$e->getMessage());
    }
    }
     
    printf("\tDone. %d records migrated. \n"$result->num_rows);
    $result->free();
    printf("Deleting old data from MySQL...\n");
    $query = sprintf("DELETE FROM sales WHERE DATE(time_of_sales)""< '%s'"$cutoffDate);
     
    $status = $mysql->query($query);
     
    if ($status === False) {
    die(sprintf("Error executing query %s"$mysql->error));
    }
     
    $mysql->close();
    printf("Archiving complete.\n");
     
    ?>
     
    cs
     



    웹에서 위의 스크립트를 실행합니다. 아래와 같이 나오면 성공.




     



    다음으로 mongo shell에서 query를 날립니다. (예제 데이터가 없으므로, 아무것도 안뜬다)



     





    4번까지 진행하려 했으나, 예제데이터가 없는데 보여드릴게 없으므로 이만 pass 하겠습니다ㅜㅜㅜ



    마지막으로 몽고DB와 RDBMS를 함께 쓰는 과정에서 일어나는 문제점만 짚고 넘어가겠습니다!
     - 데이터 일관성
     - 소프트웨어 아키텍처의 복잡도
     - 추가 구성 요소를 지원하는 비용






    댓글

Designed by Tistory.