티스토리 뷰

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

1. RethinkDB란?

RethinkDB 사이트에서 이 DB는 리얼타임 웹에 최적화된 오픈소스 데이터베이스라고 소개하고 있네요. 메인 페이지만 쓱 훑어보고 특징을 나름대로 정의해봤습니다.

  1. JSON 기반의 DB이다.
    JSON기반입니다. mongoDB, couchDB 등등이 생각나네요. 이런 JSON 기반 DB의 특징은 NoSQL 을 아시는 분이라면 눈치채시겠지만 Key-Value 에서 Key 여러 개가 하나의 문서(document)로 엮여있다는 것입니다. 그러다보니 단순 Key-Value보다 쿼리가 좀 더 쉬워지는 장점이 있다고 할 수 있겠습니다.
  2. 푸시 기반이다.
    FAQ를 보면 다음과 같이 적혀있습니다.

    instead of polling for changes, the developer can tell RethinkDB to continuously push updated query results to applications in realtime.

    다음을 보면 푸시 기반이라고 적혀있습니다. 업데이트가 발생하였을 때 이를 조회해서 확인하는 방법이 아니라는 거에서 다른 DB와 큰 차별점을 보입니다. 자세한 내용은 직접 실험을 해보면서 확인해보도록 하겠습니다.

어디에 쓰면 좋을까?

홈페이지에서는 이 DB를 이런 곳에 쓰면 좋다고 합니다.

  • Collaborative web and mobile apps
  • Streaming analytics apps
  • Multiplayer games
  • Realtime marketplaces
  • Connected devices
    주로 실시간을 요하는 애플리케이션에 사용할 것을 권장하고 있네요. 실제로 2부에서 이를 이용한 채팅 앱(만만한 게 채팅이지 ㅋㅋㅋ)을 만들어서 확인해보도록 하겠습니다.

이 땐 쓰지 마세요.

  • 완전한 ACID를 요구할 때에는 쓰지 말라고 합니다. 솔직히 NoSQL로 ACID를 다 하려는 발상은 좀 무리수죠 ㅋㅋㅋ
  • Hadoop이나 Vertica같은 CPU 겁나 사용하는 분석에는 사용하지 말라고 합니다. Node.js 설명하는 기분이네요 ㅋㅋ
  • 데이터 동시성을 위해 쓰기 가용성을 포기하기도 한다고 합니다. 쓰기 가용성을 원하거나 충돌을 생각하기 싫다면 Riak같은 Dynamo-style DB를 쓰라고 합니다.

뒤에도 내용이 더 있습니다만 일단 여기까지 보고 자세한 사항은 공식 홈페이지를 참고하시기 바랍니다. 이제 Node.js 에서 직접 사용해보면서 비교해보도록 하겠습니다.

2. RethinkDB 설치 및 QuickStart

참고 URL:
Installing RethinkDB
Thirty-second quickstart with RethinkDB

다른 OS 등의 설치를 위 사이트에 가면 자세하게 쓰여있으니 저는 가볍게 우분투의 apt-get 을 이용하여 설치해보겠습니다.

Ubuntu 14.04 에서 apt-get 으로 설치하기

# apt 레포지터리에 등록
source /etc/lsb-release && echo "deb http://download.rethinkdb.com/apt $DISTRIB_CODENAME main" | sudo tee /etc/apt/sources.list.d/rethinkdb.list

# 키 받아서 저장
wget -qO- https://download.rethinkdb.com/apt/pubkey.gpg | sudo apt-key add -

# 업데이트 및 설치
sudo apt-get update
sudo apt-get install rethinkdb

서버 실행에 관하여

$ rethinkdb

이렇게 서버를 실행하면 뭔가 좌라락 나옵니다.
rethinkDB는 포트를 3개를 사용하고 있습니다. 하나는 intracluster(29015), 다른 하나는 클라이언트 연결(28015), 그리고 다른 하나는 웹 인터페이스 포트(8080) 입니다. 스샷은 귀찮으니 일단 패스! 하겠습니다.
클러스터링은 다음에 시간이 나면 하도록 하겠습니다.

3. Node.js 에서 RethinkDB 사용해보기

참고 URL:
Ten-minute guide with RethinkDB and JavaScript

이 문서는 위의 가이드를 제가 따라해보고 정리한 것입니다. 문제가 생겼을 때 저 말고 위의 가이드를 참고하시기 바랍니다.

모듈 불러오기

var r = require('rethinkdb');

뭐 설명할 게 더 있을까요?

커넥션 생성하기

r.connect({
    host : 'HOST 주소',
    port : 28015 //기본값입니다.
}, function(err, conn) {
    if(err) {
        throw err;
    }
    connection = conn;
});

위에서도 이야기한 것처럼 클라이언트들은 28015 포트로 연결을 해야합니다. 그 밖에는 특이사항이 없네요.

테이블 생성하기

기본적으로 RethinkDB는 test라는 데이터베이스를 생성합니다. 가이드에선 여기에 authors라는 테이블을 생성했습니다. 같이 해보죠.

r.db('test').tableCreate('authors').run(connection, function(err, result) {
    if(err) {
        throw err;
    }
    console.log(JSON.stringify(result, null, 2));
});

자세히 보시면 일단 쿼리를 생성한 다음 r.db('test').tableCreate('authors")
이를 run 할 때 connection 객체를 인자로 주는 것을 알 수 있습니다.
결과를 보도록 합시다.
첫 실행 시

{
  "config_changes": [
    {
      "new_val": {
        "db": "test",
        "durability": "hard",
        "id": "57c9bc67-e3f9-49a0-90e9-31e8b6c27d2a",
        "indexes": [],
        "name": "authors",
        "primary_key": "id",
        "shards": [
          {
            "nonvoting_replicas": [],
            "primary_replica": "cdjhlee_ubuntu_s4j",
            "replicas": [
              "cdjhlee_ubuntu_s4j"
            ]
          }
        ],
        "write_acks": "majority"
      },
      "old_val": null
    }
  ],
  "tables_created": 1
}

또 실행 시에는 이미 테이블이 있다고 예외를 발생시킵니다. 주의하셔야 할 것 같습니다.
테이블 생성하기의 자세한 사항은 ReQL command:tableCreate를 참고하시기 바랍니다.

데이터 넣기

갑자기 메뉴얼이 안드로메다로 갔습니다. 뜬금없이 r.table('authors') 이라뇨. 이게 되는 지 안되는 지는 다음에 보도록 하고 일단 실행해보겠습니다.

r.table('authors').insert([
    { name: "William Adama", tv_show: "Battlestar Galactica",
      posts: [
        {title: "Decommissioning speech", content: "The Cylon War is long over..."},
        {title: "We are at war", content: "Moments ago, this ship received word..."},
        {title: "The new Earth", content: "The discoveries of the past few days..."}
      ]
    },
    { 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..."}
      ]
    }
]).run(connection, function(err, result) {
    if (err) throw err;
    console.log(JSON.stringify(result, null, 2));
})

결과

{
  "deleted": 0,
  "errors": 0,
  "generated_keys": [
    "a98db6f7-d115-4f41-b843-0d3bf60d1200",
    "90fcdae0-49fd-4fe4-8f82-24cf53621cb1",
    "2d74671a-6e49-468f-9a2c-dd1252d45901"
  ],
  "inserted": 3,
  "replaced": 0,
  "skipped": 0,
  "unchanged": 0
}

이유는 알 수 없지만 실행이 되었습니다. connection 생성할 때 db의 기본값이 test 여서 그런 것 같군요. 자세한 건 저도 공부를 해봐야 할 것 같네요.

데이터 불러오기

이제 데이터를 불러오도록 합시다.

모든 documents 불러오기

가이드의 소스코드는 다음과 같습니다.

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

결과값은 배열로 나옵니다. 특이한 점은, 아까 insert 결과로 나온 generated_keys가 id라는 키의 값으로 생겼다는 것입니다. 그리고 generated_keys는 아까 insert 했을 떄의 순서와 일치합니다.

...
{
    "id": "a98db6f7-d115-4f41-b843-0d3bf60d1200",
    "name": "William Adama",
    "posts": [
      {
        "content": "The Cylon War is long over...",
        "title": "Decommissioning speech"
      },
      {
        "content": "Moments ago, this ship received word...",
        "title": "We are at war"
      },
      {
        "content": "The discoveries of the past few days...",
        "title": "The new Earth"
      }
    ],
    "tv_show": "Battlestar Galactica"
  }
...

아까 넣은 값의 0번 인덱스인 William Adama의 id가 a98db6f7-d115-4f41-b843-0d3bf60d1200 로 생성되었음을 알 수 있습니다.
cursor는 전수조회같은 것을 할 때에 iterator 패턴을 위해 쓰이며, toArray는 모든 cursor를 순회하여 배열 형태로 내뱉는 역할을 합니다.

조건에 맞는 결과만 필터해서 불러오기

쿼리가 편하면 DB를 쓰는 데 거부감이 많이 줄어드는 것이 사실입니다. mongoDB의 점유율이 높은 이유 중 하나가 SQL과 근접한 쿼리의 간편함이니까요. 가이드에서는 William Adama라는 이름을 가진 객체를 불러오고자 합니다. 어떻게 하는 지 봅시다.

r.table('authors').filter(r.row('name').eq("William Adama")).
    run(connection, function(err, cursor) {
        if (err) throw err;
        cursor.toArray(function(err, result) {
            if (err) throw err;
            console.log(JSON.stringify(result, null, 2));
        });
    });

filter 안에 row와 eq가 들어가있네요. 자세한 건 가이드를 보면 나올 것 같습니다. 이 역시 cursor를 이용하며, toArray이므로 배열로 출력이 됩니다.

[
  {
    "id": "a98db6f7-d115-4f41-b843-0d3bf60d1200",
    "name": "William Adama",
    "posts": [
      {
        "content": "The Cylon War is long over...",
        "title": "Decommissioning speech"
      },
      {
        "content": "Moments ago, this ship received word...",
        "title": "We are at war"
      },
      {
        "content": "The discoveries of the past few days...",
        "title": "The new Earth"
      }
    ],
    "tv_show": "Battlestar Galactica"
  }
]

지면을 너무 많이 차지하는 관계로 부등호 연산은 패스하겠습니다.

Primary Key 로 찾기

PK로 찾는 방법은 아주 간단합니다. 소스코드로 봅시다.

r.table('authors').get('a98db6f7-d115-4f41-b843-0d3bf60d1200').
    run(connection, function(err, result) {
        if (err) throw err;
        console.log(JSON.stringify(result, null, 2));
    });

이 때에는 배열이 아니네요. 존재한다면 객체를, 없다면 null을 리턴합니다.

여기까지는 단순한 내용이었고 실제 앱을 만들면서 배운 부분은 2부에서 다루도록 하겠습니다. 그런데 여기까지 오면서 RethinkDB만의 특징은 아직 나오지 않았습니다. 바로 Realtime feeds 입니다. 지면을 너무 많이 잡아먹어서 일단 여기서는 작성하지 않겠습니다. 언제 나올 지 모르는~ 2부에서 다루도록 하겠습니다. 1부는 Update 와 Delete를 끝으로 마치도록 하겠습니다.

계속 가도록 합시다.

모든 문서 수정하기

모든 문서를 고쳐봅시다. type 이라는 필드를 모든 문서에 추가할 것이라고 합니다. 코드를 봅시다.

r.table('authors').update({type: "fictional"}).
    run(connection, function(err, result) {
        if (err) throw err;
        console.log(JSON.stringify(result, null, 2));
    });

결과

{
  "deleted": 0,
  "errors": 0,
  "inserted": 0,
  "replaced": 3,
  "skipped": 0,
  "unchanged": 0
}

이번엔 replaced가 3이네요.

어.. 점점 용두사미가 되어가는데..

필터를 이용하여 수정하기

이번엔 name이 William Adama인 사람의 rank를 Admiral로 고칠 예정입니다. 코드 봅시다.

r.table('authors').
    filter(r.row("name").eq("William Adama")).
    update({rank: "Admiral"}).
    run(connection, function(err, result) {
        if (err) throw err;
        console.log(JSON.stringify(result, null, 2));
    });

아까 데이터 불러올 때랑 묘하게 비슷합니다. 결과를 보도록 합시다.

{
  "deleted": 0,
  "errors": 0,
  "inserted": 0,
  "replaced": 1,
  "skipped": 0,
  "unchanged": 0
}

문서 1개만 바뀌었다고 나와있습니다. 배열에 값을 추가할 수도 있지만 select 할 때에 건너뛰었기 떄문에 여기서도 건너뜁니다.
진짜 바뀌었나 내용을 확인해봅시다.

[
  {
    "id": "90fcdae0-49fd-4fe4-8f82-24cf53621cb1",
    "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",
    "type": "fictional"
  },
  {
    "id": "2d74671a-6e49-468f-9a2c-dd1252d45901",
    "name": "Jean-Luc Picard",
    "posts": [
      {
        "content": "There are some words I've known since...",
        "title": "Civil rights"
      }
    ],
    "tv_show": "Star Trek TNG",
    "type": "fictional"
  },
  {
    "id": "a98db6f7-d115-4f41-b843-0d3bf60d1200",
    "name": "William Adama",
    "posts": [
      {
        "content": "The Cylon War is long over...",
        "title": "Decommissioning speech"
      },
      {
        "content": "Moments ago, this ship received word...",
        "title": "We are at war"
      },
      {
        "content": "The discoveries of the past few days...",
        "title": "The new Earth"
      }
    ],
    "rank": "Admiral",
    "tv_show": "Battlestar Galactica",
    "type": "fictional"
  }
]

바뀌었네요.

문서 삭제하기

여기서는 글 3편 이하로 쓴 사람을 다 날려버릴겁니다.(~정리해고 무서벙 ㅠㅠ~) 소스를 봅시다.

r.table('authors').
    filter(r.row('posts').count().lt(3)).
    delete().
    run(connection, function(err, result) {
        if (err) throw err;
        console.log(JSON.stringify(result, null, 2));
    });

결과

{
  "deleted": 2,
  "errors": 0,
  "inserted": 0,
  "replaced": 0,
  "skipped": 0,
  "unchanged": 0
}

이렇게 2명의 작가가 해고되었습니다. 여기까지 RethinkDB의 간단한 CRUD에 대해서 알아보았습니다.

4. 결론

여기까지 RethinkDB의 설치 및 간단한 CRUD를 해봤습니다. 쿼리를 의외로 쉽게 짤 수 있어서 편하게 사용할 수 있을 것 같습니다. 하지만 지면의 한계로 RethinkDB의 중요한 요소 중 하나인 Push 기반 DB임을 확인하지 못한 것은 아쉽습니다. 그래서 다음에는 실제 앱을 하나 만들어보면서 좀 더 볼만한 내용을 찾아보도록 하겠습니다.

5. 레퍼런스

공식 사이트
진짜 이 사이트만 봤습니다.

댓글
댓글쓰기 폼