まだプログラマーですが何か?

プログラマーネタとアスリートネタ中心。たまに作成したウェブサービス関連の話も http://twitter.com/dotnsf

タグ:restore

IBM Bluemix からも提供されている NoSQL DBaaS である Cloudant 。このデータベースサービスにはベータベースの全文書を取得したり、複数データをバルクでインサートするような REST API が提供されており、これらを応用することで(curl を併用するなどして)バックアップやリストアを行うこともできることになっています。


ただこの方法にはいくつかの問題点もありました。個人的には以下の2点がちと無視できない制約でした:
(1) バックアップしたデータをバルクインサートすると、元のデータベース内にあった JSON ドキュメントとは異なる階層構造になってしまう
(2) バックアップデータには attachments(添付ファイル)情報が含まれない。そのためバルクインサートでリストアしても添付ファイルは復元されない


これらの問題を解決して API でバックアップ&リストアするための方法を考えていましたが、結論としては「専用ツールを作った方が早くて便利そう」でした。で、実際に作ってみました:
https://github.com/dotnsf/cdbtool


セットアップ方法や使い方は README.md にも書いておきましたが、動作前提に Node.js が必要です。お使いのシステムに併せて Node.js を導入しておいてくださいませ。

で、上記 URL からツール本体をダウンロード&展開するか、git clone します。

展開後のファイル一覧の中に settings.js というファイルがあります。このファイルをテキストエディタで開き、バックアップ&リストアの対象とする Cloudant サービスのユーザー名およびパスワードに該当部分を書き換えて保存します:
exports.cloudant_username = 'ここを Cloudant のユーザー名に書き換える';
exports.cloudant_password = 'ここを Cloundant のパスワードに書き換える';

なお、Cloudant のユーザー名およびパスワードは別途確認しておいてください。IBM Bluemix 環境の場合であればランタイムやサービスの資格情報から確認することができます:
2017032801


最後にこのツールが必要なライブラリをまとめてインストールします。package.json があるディレクトリで以下のコマンドを実行します:
$ npm install

これで準備完了!

ではまずは Cloudant データベースをダンプ(バックアップ)してみます。使うファイルは dump.js で、コマンドラインから以下のように入力します:
$ node dump (dbname) (dumpfilename)

最初の2つ(node dump)は「Node.js で dump.js を実行する」ことを指定しています。残りの2つはいわゆるコマンドラインパラメータです。

最初のコマンドラインパラメータの (dbname) は Cloudant 上のデータベースの名称です。例えば、現在 Cloudant のダッシュボードでデータベース一覧を見た時に以下のようになっているものとします:
2017032801


データベースが5つありますが、この中の一番下にある "spendb" データベース(文書数 53)のバックアップを取得するのであれば、このパラメータには spendb と指定することになります。

最後のパラメータ (dumpfilename) はダンプ結果を保存するファイル名を指定します。今回はここに spendb.dump と指定して、この名前のファイルをダンプファイルとして新たに作成することにします。つまりコマンドラインからは以下のように実行することになります:
$ node dump spendb spendb.dump

このコマンドが成功すると、実行時のディレクトリに spendb.dump という名前のファイルが作成されているはずです。

ではバックアップで作成したファイルを使って、新しいデータベースにリストアしてみましょう。リストア時は以下のようなコマンドを入力します:
$ node restore (newdbname) (dumpfilename)

ここでも後ろの2つがコマンドラインパラメータで、最初の (newdbname) はリストア先のデータベース名です。指定した名前のデータベースが存在していない場合は新たに作成され、存在している場合は一度削除されて新たに作成されます(文書データだけ上書き、ではありません)。 また (dumpfilename) には上記で作成したダンプファイル名を指定します。仮に上記で作成したダンプファイルを使って、newdb という名前のデータベースにリストアするのであれば、以下のように実行することになります:
$ node restore newdb spendb.dump

このコマンドが成功すると、Cloudant 上に newdb という名前のデータベースが新たに作成され、その中にドキュメントが(元データベースと同じ 53 文書)ロードされているはずです。文書ID も元のデータベースのものがそのまま使われ、元データベース内に添付ファイル(attachments)が含まれていた場合は添付ファイルも含めてリストアされる仕様です(これを実現したくて、このツールを作りました):
2017032802



今後はダンプファイルのサイズ圧縮とかにも対応しようかなあ。気が向いたら機能追加したりバグ修正したりもしますが、MIT ライセンスでオープンソース化しているので、何かあったら適当に(笑)対応していただけるとうれしいです。



先日、Cloudant のデータベースのバックアップ(というかダンプというか、スナップショットというか、、)を撮る方法をこちらで紹介しました:
Cloudant のデータベースダンプとリストア

この中では /_all_docs?include_docs=true に GET アクセスすることで指定データベース内の全ドキュメントを中身ごと取り出す方法でダンプを取得する方法を紹介しました。

併せて、取得したダンプファイルを /_bulk_docs に POST してリストアする、という方法も紹介しました。この方法で確かにデータそのものは指定データベースに入るのですが、JSON の構造が少し変わってしまいます。「リストア」という観点では不十分でした。

というわけで、今回は JSON の構造を変えずにリストアする方法を紹介します。といってもコマンドでどうにかするのではなく、取得したダンプファイルを読み込んで、少しフォーマットを変更した上で同じ /_bulk_docs コマンドでリストアする、という方法です。フォーマットを変更する箇所はプログラミングでツール化しました(赤字はコメント):
public class DbRestore {
  static String c_baseurl = "https://username.cloudant.com"; //. Cloudant サーバー
  static String c_username = "username"; //. ユーザー名
  static String c_password = "password"; //. パスワード
  static String c_db = "newdb"; //. リストア先データベース名(作成しておく必要があります)
  static String c_dumpfile = "olddb.dump"; //. 読み込むダンプファイル名

  public static void main(String[] args) {
    try{
      //. ダンプファイルを読み込む
      String lines = "";
      BufferedReader br = new BufferedReader( new FileReader( new File( c_dumpfile ) ) );
      String line = br.readLine();
      while( line != null ){
        lines += ( line + "\n" );
        line = br.readLine();
      }
      br.close();

      //. ダンプファイル内のドキュメントデータを1つずつ読み込み、リストア用にフォーマットを変更
      String docs2 = "{\"docs\":[\n";
      JSONParser parser = new JSONParser();
      JSONObject obj = ( JSONObject )parser.parse( lines );
      JSONArray docs = ( JSONArray )obj.get( "docs" );
      for( int i = 0; i < docs.size(); i ++ ){
        JSONObject doc0 = ( JSONObject )docs.get( i );
        JSONObject doc = ( JSONObject )doc0.get( "doc" );
        JSONObject d = ( JSONObject )doc.get( "d" );
        if( i > 0 ){ docs2 += ","; }
        docs2 += "{\"d\":" + d.toJSONString() + "}\n";
      }
      docs2 += "]}";

      //. リストア用に変更したダンプデータを /_bulk_docs にポスト
      String url = c_baseurl + "/" + c_db + "/_bulk_docs";
      HttpClient client = new HttpClient();
      byte[] b64data = Base64.encodeBase64( ( c_username + ":" + c_password ).getBytes() );
      PostMethod post = new PostMethod( url );
      post.setRequestHeader( "Authorization", "Basic " + new String( b64data ) );
      post.setRequestHeader( "Content-Type", "application/json" );
      post.setRequestBody( docs2 );
      int sc = client.executeMethod( post );
      String result = post.getResponseBodyAsString();

      //. 結果確認
      System.out.println( "Status = " + sc );
      System.out.println( "Result = " + result );
    }catch( Exception e ){
      e.printStackTrace();
    }
  }
}


このコードは JSON-SimpleApache HTTP Client ライブラリを使って /_bulk_docs にダンプデータをポストする処理を Java で実装しています。ダンプデータをそのままポストすると情報は再現できるのですが、データフォーマットが変わってしまいます。その問題を回避すべく、ポスト前に必要なフォーマット変換を行っていて、ダンプ前のデータフォーマットが変わらないように(_id 値や _rev 値は変わります)ポストしています。

このコードの c_dumpfile に前回の方法でダンプしたファイル名を指定し、Java で動かせばダンプファイルの中身をリストアできるはずです。


Cloudant のような NoSQL データベースの場合、バックアップは一般的には複製機能によって実現するケースが多いのかもしれません。要は生きたデータベースを2系統以上用意して、生きたデータベースにバックアップする、という考え方です。これだとメインDBに障害があってもすぐに切り替えて稼働ができます。

ただ、例えばデータベースの引越しが目的の場合や、あるタイミングでのスナップショットを取るような場合などでは、一旦データベースのダンプを取得して、引越先でリストアしたい、というケースもあります。このようなケースでは生きたデータベースに複製することが必ずしも正しい回答にはなりません。


そういった場合のダンプは以下のコマンドで取得することができます:
$ curl -X GET https://(username):(password)@(hostname)/(dbname)/_all_docs?include_docs=true > file.dump

上記例では file.dump というファイル名を指定してダンプ結果を取得するように指示しています。ダンプというか、データベースの _all_docs(全ドキュメント) に include_docs_true(中身ごと取得)オプションを付けて GET する、というよく考えたらごく普通のコマンドをそのまま実行しています。

取得したダンプファイル(file.dump)を使ってリストアする場合はちょっと準備が必要です。まず上記コマンドで取得したダンプファイルをテキストエディタで開き、1行目の "rows" を "docs" に書き換えて保存します:
{"total_rows":100,"offset":0,"rows":[ 
  :
  :

  ↓ "rows" を "docs" に書き換えて保存
{"total_rows":100,"offset":0,"docs":[ : :

編集後に次のコマンドを実行します:
$ curl -d @file.dump -H "Content-Type: application/json" -X POST https://(username):(password)@(hostname)/(dbname)/_bulk_docs

こちらはデータベースに対して、中身を JSON ファイルで指定して POST しているだけです。

これで手動でバックアップができるので、後はこれを cron などで自動化したり、取得したダンプファイルを外部のオブジェクトストレージとかに転送する仕組みまで作ってしまえば自動バックアップが実現できそうです。

(2016/Jul/30 追記)
上記方法でもリストアできますが、元のデータベース内と比べて JSON ドキュメントのフォーマットが変わってしまいます。フォーマットを変えずにリストアする場合の方法は別のエントリで紹介しています:
Cloudant のバックアップデータをリストアする





 

Couchbase サーバーのデータをバックアップ&リストアする手順を紹介します。なお、以下で紹介する手順はバケットタイプが Couchbase のバケットに対してのみ可能な手順です。

まずバックアップを実行します。バックアップコマンドは /opt/couchbase/bin/cbbackup です:
# /opt/couchbase/bin/cbbackup http://couchbase.test.com:8091 /tmp/default_backup -u Administrator -p password -b default

上記例では couchbase.test.com という Couchbase サーバーのバケット default に対して、管理者名 Administrator 、管理者パスワード password でバックアップを実行し、その結果を /tmp/default_backup/ 以下に出力しています。

実行後、/tmp/default_backup/ 以下にはバックアップ結果のダンプファイルができています。ダンプファイルはクラスタ毎に作成されるので、クラスタ数と同じ数のダンプファイルが出力されているはずです:
/tmp/default_backup/bucket-default/node-couchbase.test.com%3A8091/data-0000.cbb

次にリストアを実行します。リストアコマンドは /opt/couchbase/bin/cbrestore です:
# /opt/couchbase/bin/cbrestore http://Administrator:password@couchbase.test.com:8091 --bucket-source=default --bucket-destination=test

このコマンドでは "default" というバケットから取得したバックアップダンプを(同じく) "test" というバケットに対してリストアする、という処理を指示しています。


このページのトップヘ