티스토리 뷰

[node.js][RethinkDB] RethinkDB를 사용해보았다.(2부)

1. 시작하기 앞서

저번 편에서는 간단하게 RethinkDB에 대해 소개를 했었습니다. 하지만 RethinkDB의 가장 큰 특징 중 하나인 푸시 기반의 정보 갱신을 지면상의 관계로 그냥 넘어갔습니다. 그래서 이번에는 RethinkDB 기반 앱을 만들어보도록 하겠습니다.

2. 실시간 읽어오기

참고 사이트: Ten-minute guide with RethinkDB and JavaScript
저번 편에서 지면상의 관계로 그냥 넘어갔던 Realtime feeds 섹션을 보도록 하겠습니다. 가이드에서도 지금 필요하지 않으면 나중에 봐도 된다고 써놨네요. 이제 필요할 때가 되었으니(…) 써보도록 하겠습니다.

이전편에 사용했던 짤릴 걱정을 해야하는 작가 리스트를 이용하도록 합시다. 불러오는 소스코드는 다음과 같습니다.

r.table('authors').changes().run(connection, function(err, cursor) {
    if (err) throw err;
    cursor.each(function(err, row) {
        if (err) throw err;
        console.log(JSON.stringify(row, null, 2));
    });
});

프로세스를 2개를 이용하도록 합시다. 저 코드가 들어있는 애플리케이션을 실행한 뒤 좀 기다려봅시다. 저희는 짤렸던 두 명을 재고용할(?!) 것입니다. insert data 섹션을 참고하여 넣어보겠습니다.

// insert.js
var r = require('rethinkdb');
var insertData = [
 { name: "Laura Roslin", tv_show: "Battlestar Galactica",
      posts: [
        {title: "The oath of office", content: "I, Laura Roslin, ..."},
        {title: "They look like us", content: "The Cylons have the ability..."}
      ]
    },
    { name: "Jean-Luc Picard", tv_show: "Star Trek TNG",
      posts: [
        {title: "Civil rights", content: "There are some words I've known since..."}
      ]
    }
];

var connection = null;
r.connect( {host: 'localhost', port: 28015}, function(err, conn) {
    if (err) throw err;
    connection = conn;

    r.table('authors').insert(insertData).
    run(connection, function(err, result) {
        if (err) throw err;
        console.log(JSON.stringify(result, null, 2));
    });
});

이 코드를 돌린 다음에 아까 실행했던 프로세스의 로그를 봅시다.

{
  "new_val": {
    "id": "66f2851b-2117-4989-9820-c9e89ad9adb7",
    "name": "Laura Roslin",
    "posts": [
      {
        "content": "I, Laura Roslin, ...",
        "title": "The oath of office"
      },
      {
        "content": "The Cylons have the ability...",
        "title": "They look like us"
      }
    ],
    "tv_show": "Battlestar Galactica"
  },
  "old_val": null
}
{
  "new_val": {
    "id": "df1c9a71-becc-4592-bd54-7371c6c9bd1f",
    "name": "Jean-Luc Picard",
    "posts": [
      {
        "content": "There are some words I've known since...",
        "title": "Civil rights"
      }
    ],
    "tv_show": "Star Trek TNG"
  },
  "old_val": null
}

갑자기 이 로그가 생겼습니다. new_val과 old_val이 보일겁니다. old_val에는 옛날 데이터, new_val에는 최신 데이터임을 직관적으로 보실 수 있을겁니다. 자세한 내용은 Changefeeds in RethinkDB를 보시기 바랍니다. 문서를 쓱 훑어보면 각종 쿼리가 가능함을 알 수 있습니다.

3. 가벼운 앱 만들기

이제 이 것을 이용하여 가벼운 앱을 하나 만들어 볼 계획입니다. socket.io와 rethinkdb를 이용하여 짭트위터를 만들어 볼 예정입니다. 트윗은 휘발성 데이터가 아닌지라 누구나 입장을 했을 때 최신의 트윗을 볼 수 있어야 합니다. 또한, 사용자가 글을 작성했다면, 그 글은 실시간으로 다른 사람들이 볼 수 있어야 합니다. 한 번 만들어봅시다.

3.1. 스키마 보기

진짜 트위터라면 어마어마하게 많은 데이터들이 유기적으로 작동하고 있을 것입니다. 하지만 제가 만드는 것은 짭트위터이기 때문에 몇 개의 필드만 넣겠습니다.

{
  "id" : String, <- 자동으로 생성됩니다.
  "name" : String,
  "text" : String,
  "date" : ISOString <- 날짜를 ISO String 규격으로 저장할 예정입니다.
}

3.2. 뭘로 만들거임?

동시성을 보장하기 위하여 socket.io + express 를 이용하여 서버 코드를 작성할 것입니다. 서버는 rethinkdb를 이용하여 정보의 갱신을 알아차려서 클라이언트에게 뿌리는 방법을 쓸 것입니다.

3.3. 서버 소오오오오스 코드

블로그 용으로 매우 짧게 쓰는 것이므로 프로그램을 견고하게 만드는 코드들은 모두 생략하고 예제 수준으로만 작성하였음을 알려드립니다.

//app.js
'use strict';

const express = require("express");
const http = require("http");
const path = require("path");
const io = require('socket.io');
const r = require('rethinkdb');

var rConn = null;
var app = express();
var httpServer = http.createServer(app);
var ioServer = io(httpServer);
var sockets = {};

app.use('/', express.static(path.join(__dirname, 'public')));

r.connect( {host: 'localhost', port: 28015, db : 'jjab_tweet'}, function(err, conn) {
    if(err) {
        throw err;
    }
    rConn = conn;

    //change 이벤트가 등록이 됩니다. change를 기다리다 발생하면 바로 커서의 내용을 각각 브로드캐스트합니다.
    r.table('tweets').changes().run(rConn, (err, cursor) => {
        if(err) {
            return console.log(err);
        }
        cursor.each((err, data) => {
            if(err) {
                return console.log(err);
            }
            ioServer.emit('updateTweets', data.new_val);    //새로운 값을 뿌리도록 합시다. old_val은 여기서 필요없습니다.
        });
    });
});

ioServer.on('connection', (socket) => {
    console.log(`${socket.id} connected`);
    sockets[socket.id] = socket;

    socket.on('disconnect', () => {
        console.log(`${socket.id} disconnected`);
        delete sockets[socket.id];
    });

    socket.on('tweet', (tweet) => {
        tweet.date = new Date().toJSON();

        //글을 등록하면 저 위의 r.table('tweets').changes() 가 캐치할 것입니다.
        r.table('tweets').insert(tweet)
        .run(rConn, (err) => {
            if(err) {
                console.log(err);
            }
        });
    });

    //페이지가 처음 로드될 때에 필요한 데이터를 10개만 시간 역순으로 불러옵시다.
    r.table('tweets').orderBy(r.desc('date')).limit(10).run(rConn)
    .then((list) => {
        socket.emit('initTweets', list);
    }).catch((e) => {
        console.log(e);
    });
});

httpServer.listen(8888, () => {
    console.log('listen on 8888');
});

한 파일에 작성하느라고 코드가 꽤 길어졌네요. 여기서 주목할 점은 앱을 시작하자마자 tweet 업데이트 이벤트를 등록했다는 것입니다. 이 이벤트로 트윗의 입력을 감지할 수 있으니까요. 사실 저 코드는 실전 상황에서는 문제가 생기는 코드이지만 예제니까 넘어갑시다.

3.4. DB 초기화하기

그런데 저 위의 프로그램을 그대로 실행하면 테이블을 찾을 수 없다는 에러가 발생합니다. rethinkDB는 mysql마냥 DB도 만들고 table도 만들어야 합니다. 제일 편한 방법은 직접 UI에 접속하셔서 추가하시면 됩니다. 저번 글에서 봤듯이 rethinkDB 를 실행하면 포트가 3개가 개방이 되는데 그 중 하나인 웹 인터페이스 포트(8080)으로 접근을 하시면 됩니다.

3.5. 클라이언트 코드

중간에 제가 만들다가 귀차니즘에 쩔어서 css도 없는 눈뜨고 못 볼 처참한 퀄리티의 제품이 나와서 정말 죄송하다는 생각밖에 들지 않네요. 여기선 폼에 자신의 이름과 쓸 내용을 적어 텍스트 입력을 하면 글이 업로드되는 코드입니다. 작성한 코드는 HTML, JS입니다.

<html>
  <head>
    <title>짭트윗</title>
  </head>
  <body>
    <h1>짭트위이이이잇</h1>
    <div id="inputGroup">
      <form id="inputForm">
        <label for="inputAuthor">이름: </label><input type="text" id="inputAuthor" name="author">
        <label for="inputText">내용: </label><input type="text" name="text" id="inputText">
        <button type="submit">쓰기</button>
      </form>
    </div>
    <div class="tweet-lists-container">
      <ul id="tweet-lists">
        <li>ㅋㅋㅋㅋ</li>
      </ul>
    </div>
<script src="https://code.jquery.com/jquery-2.2.3.min.js"></script>
<script src="/socket.io/socket.io.js"></script>
<script src="/main.js" type="text/javascript"></script>
  </body>
</html>

이건 그냥 HTML입니다. 분량 채우려고 실었습니다 ㅋㅋㅋ

//main.js
var socket = io();
$("#inputForm").submit(function() {
    var $name = $("#inputAuthor"),
        $text = $("#inputText");

    var name = $name.val(),
        text = $text.val();

    socket.emit('tweet', {name : name, text : text});
    $text.val('');

    return false;
});

socket.on('updateTweets', function(data) {
    var $tweetList = $('#tweet-lists');

    $(createItem(data)).prependTo($tweetList);
});

socket.on('initTweets', function(list) {
    var $tweetList = $('#tweet-lists');
    $tweetList.empty();

    list.forEach(function(data) {
        $(createItem(data)).appendTo($tweetList);
    });
});

function createItem(data) {
    var str = '';
    str += '<li>';
    str += '<p>작성자: ' + data.name + '</p>';
    str += '<p>작성일자: ' + data.date + '</p>';
    str += '<p>' + data.text + '</p>';

    return str;
}

코드를 짜던 와중에 현자타임이 와서 대충 적어서 코드가 좀 짧습니다. 여기선 socket.io를 이용하여 화면을 동적으로 그리고 있습니다. client가 직접 rethinkDB에 접근을 하면 좀 그러니까 socket.io 서버를 거친다고 봐야겠지요. 이렇게 하면 정말 욕나오는 퀄리티의 앱이 하나 나옵니다.

4. 사용해보고 느낀 점

rethinkDB는 리얼타임 앱을 만들 때 쉽게 만들 수 있는 강점을 가진다고 하는 DB입니다. 지금 상황같은 경우에는 redis의 pub/sub 같은 것을 이용하여 만드는 것이 더 빠르겠지만 단일 규모가 아닌 대형 프로젝트에서 리얼타임을 요구하는 상황에서는 어느 데이터베이스보다 더 간단하게 구현할 수 있을 것으로 보입니다. 특히 요즘 대세가 반응형 프로그래밍으로 가고 있는 점을 보면 적응을 잘 하면 괜찮게 사용할 수 있을 것 같습니다.

5. 다른 예제들은 어디에서 구할 수 있을까?

rethinkDB 홈페이지에서는 Example projects 에 다양한 언어, 종류별 예제를 제공하고 있습니다. 이 글을 보고 ㅂㅅ같은 코드를 작성한 저를 욕하기보단 위 페이지를 보시는 것을 추천합니다.

6. 다루지 않은 점

6.1. 복잡한 데이터 모델링

지금은 간단하게 text만 이용했지만 실제로 날짜 데이터, 바이너리 데이터를 넣는 방법 등은 다루지 않았습니다. 자세한 내용은 홈페이지의 문서를 참고해보시는 것이 좋을 것 같습니다.

6.2. 샤딩, 복제 등의 분산 처리

이 내용은 언제 나올 지 모르는 3부에서 다뤄보려고 합니다. 그냥 넘어가기엔 핵심과도 같은 기능이라 저도 공부를 더 해봐야 할 것 같아서 그렇습니다. Scaling, sharding and replication을 보시면 더 빠르게 볼 수 있을 것입니다.

댓글
댓글쓰기 폼