Couchbase cbbackup でデータをバックアップ・リストアしてみる
Couchbase server では公式のデータバックアップツールとして、cbbackup というものがある。
cbbackup | Couchbase Docs
Linux 上でインストールした Couchbase server に対して、バックアップを実行してみます。
環境
Version: 4.5.1-2844 Community Edition (build-2844)
Server Node数: 3
Bucket名: default
バックアップ前の状態
このように 100,000 件のレコードが登録されています。
バックアップ用コマンド
/opt/couchbase/bin/cbbackup couchbase://localhost:8091 /cb-backup -m full --single-node -u admin -p password
今回は couchbase server 上でそのままバックアップコマンドを実行するため、取得先サーバーとして、localhost を指定しています。
バックアップ先のディレクトリとして、/cb-backup を予め作成しておく必要があります。
"-m full" はフルバックアップを意味します
"--single-node" はバックアップするサーバを1台に絞ります(クラスター全体からバックアップしない)
実行すると、以下のようなメッセージが表示されます
# /opt/couchbase/bin/cbbackup couchbase://localhost:8091 /cb-backup -m full --single-node -u admin -p password [####################] 100.0% (33332/estimated 33332 msgs) bucket: default, msgs transferred... : total | last | per sec byte : 2629467 | 2629467 | 300695.6 2018-12-16 01:38:07,871: mt could not find index server:0 done
バックアップ先に指定したディレクトリを見てみると、以下のようなファイルが生成されています。
実行時のタイムスタンプから、対象のバケット名やサーバ名など、思っていたよりも深いディレクトリ構造になっています。
# ls -alth cb-backup/2018-12-16T013759Z/2018-12-16T013759Z-full/bucket-default/node-172.26.0.3%3A8091/ total 4.8M -rw-r--r-- 1 root root 3.9K Dec 16 01:38 seqno.json drwxr-xr-x 2 root root 4.0K Dec 16 01:38 . -rw-r--r-- 1 root root 4.8M Dec 16 01:38 data-0000.cbb -rw-r--r-- 1 root root 5.6K Dec 16 01:38 snapshot_markers.json -rw-r--r-- 1 root root 18K Dec 16 01:38 failover.json drwxr-xr-x 3 root root 4.0K Dec 16 01:37 ..
データのクリア
一旦、すべてのデータをクリアします。
リストア用コマンド
/opt/couchbase/bin/cbrestore /cb-backup couchbase://localhost:8091 -b default
実行すると以下のようなメッセージが表示されます。
# /opt/couchbase/bin/cbrestore /cb-backup couchbase://localhost:8091 -b default [####################] 100.0% (33332/estimated 33332 msgs) bucket: default, msgs transferred... : total | last | per sec byte : 2629467 | 2629467 | 964675.8 done
リストア後の状態
なんと、元あったデータの3分の1しか復元されていませんでした。
cbbackup 実行時に --single-node オプションをつけていた為、3台中1台分しかバックアップされていなかったようです。
改めて --single-node なしでバックアップ
# /opt/couchbase/bin/cbbackup couchbase://localhost:8091 /cb-backup -m full -u admin -p password [####################] 100.0% (100000/estimated 100000 msgs) bucket: default, msgs transferred... : total | last | per sec byte : 7888890 | 7888890 | 293045.8 2018-12-16 05:51:27,091: mt could not find index server:0 done
ノード毎に3台分ディレクトリが生成されています。
# ls -alth /cb-backup/2018-12-16T055100Z/2018-12-16T055100Z-full/bucket-default/ total 20K drwxr-xr-x 2 root root 4.0K Dec 16 05:51 node-172.26.0.3%3A8091 drwxr-xr-x 2 root root 4.0K Dec 16 05:51 node-172.26.0.1%3A8091 drwxr-xr-x 2 root root 4.0K Dec 16 05:51 node-172.26.0.2%3A8091 drwxr-xr-x 5 root root 4.0K Dec 16 05:51 . drwxr-xr-x 3 root root 4.0K Dec 16 05:51 ..
改めてリストア
全てのデータがリストアされました。
非常に基本的なバックアップを取得するつもりでしたが、オプションの意味を勘違いしていたことで、意図しない結果が得られました。
自分のドキュメント理解力も残念なところがありますが、試してみることが大切であると改めて感じます。
java.util.stream のソースを読んでみる filter 編
stream については、package java.util.stream; の JavaDoc には以下のように書かれています。
* A sequence of elements supporting sequential and parallel aggregate * operations.
並んでいる要素に対して、直列または並列処理をサポートするみたいなイメージでしょうか。
そもそも、Java8 ではなぜ List から stream() を使って Stream に変換が出来るのだろう?
> Collection インタフェースに stream() が用意されていて、List ではそれを継承しているようです。
https://docs.oracle.com/javase/8/docs/api/java/util/Collection.html
では、filter の JavaDoc を見てみます。
/** * Returns a stream consisting of the elements of this stream that match * the given predicate. * * <p>This is an <a href="package-summary.html#StreamOps">intermediate * operation</a>. * * @param predicate a <a href="package-summary.html#NonInterference">non-interfering</a>, * <a href="package-summary.html#Statelessness">stateless</a> * predicate to apply to each element to determine if it * should be included * @return the new stream */ Stream<T> filter(Predicate<? super T> predicate);
うーん。Predicate ですか。
では、Predicate の JavaDoc を見てみます。
/** * Represents a predicate (boolean-valued function) of one argument. * * <p>This is a <a href="package-summary.html">functional interface</a> * whose functional method is {@link #test(Object)}. * * @param <T> the type of the input to the predicate * * @since 1.8 */
これだけでは、ピンと来なかったですが、用意されているメソッドを見てみると、test(T t) というものがあります。
/** * Evaluates this predicate on the given argument. * * @param t the input argument * @return {@code true} if the input argument matches the predicate, * otherwise {@code false} */ boolean test(T t);
与えられた引数が条件(直訳だと述語?)に一致した場合は、true 一致しない場合は false を返します。
interface Predicate
つまり、Stream
与えたデータ型 T が boolean に変換されるとも捉えられるので、
Stream
こんな感じに変換(おおざっぱに)捉えることにします。
例として、
List<String> before = Arrays.asList("a", "b", "c"); List<String> after = before.stream().filter(e -> e.equals("b")).collect(Collectors.toList()); after.forEach(System.out::println); // b
filter(T -> boolean) と filter(e -> e.equals("b")) のような関係にこじつけられます。
JGiven のレポート機能を試す(Java, JUnit)
JGiven ではいくつかのレポート機能が用意されているようです。
テキストのレポートを無効にする
デフォルトでは、コンソール上にテキストのレポートが表示されます。
Something should happen Given サーバーが止まっている時に When プログラムを実行すると Then エラーになる
しかしながら、これを Java system property にて無効化することが出来ます。
$ mvn test -Djgiven.report.text=false
わざわざ無効化する用途は思い当たらないのですが。
JSON のレポートを無効にする
デフォルトでは、jgiven-reports ディレクトリ以下に JSON ファイルでテスト結果のレポートが出力されます。
私の環境では、eclipse 上からテストを実行した場合、project/jgiven-reports にレポートが出力されました。
mvn test でテストを実行した場合、target/jgiven-reports/json にレポートが出力されました。
後者については、Maven surefire plugin を用いた場合の挙動になるようですが、あまり理解できていません。
しかしながら、これも無効にすることが出来ます。
$ mvn test -Djgiven.report.enabled=false
この property を指定した場合、コンソール上へのテキスト形式のレポートも表示されなくなります。
※レポート機能自体が無効化される
これも、わざわざ無効化する用途は思い当たらないのですが。
HTML のレポートを出力するために
このような感じのレポートが出力できるようです。
まず、JSON のレポート出力は必須とのこと。
そして、Given report generator と呼ばれるツールを html フォーマットを指定して実行すれば出力してくれるようです。
このツールは、jgiven-html5-report と呼ばれるモジュールの一部らしいです。
java com.tngtech.jgiven.report.ReportGenerator \ --format=html \ [--sourceDir=<jsonreports>] \ [--targetDir=<targetDir>] \
maven プラグインを使えば、このようなコマンドを別途実行してあげる必要もなさそうです。
pom ファイルで指定するためには、
<build>
<plugins>
<plugin>
<groupId>com.tngtech.jgiven</groupId>
<artifactId>jgiven-maven-plugin</artifactId>
<version>0.17.0</version>
<executions>
<execution>
<goals>
<goal>report</goal>
</goals>
</execution>
</executions>
<configuration>
<format>html</format>
</configuration>
</plugin>
</plugins>
</build>
注意点は、reporting の配下ではなくて、build の配下に設置すること。
そして、mvn verify コマンドを実行しますと、以下のようにレポートが target/jgiven-reports/html 配下に自動的に生成されます。
[INFO] --- jgiven-maven-plugin:0.17.0:report (default) @ practiceJava --- [INFO] JGiven HTML report source directory: /home/xx/workspace/Java/target/jgiven-reports/json [INFO] JGiven HTML report output directory: /home/xx/workspace/Java/target/jgiven-reports/html [INFO] Generating HTML reports to /home/xx/workspace/Java/target/jgiven-reports/html... [INFO] Generating HTML5 report to /home/xx/workspace/Java/target/jgiven-reports/html/index.html [INFO] Written 1 scenarios to data0.js [INFO] Cannot read /home/xx/workspace/Java/src/test/resources/jgiven/custom.css, skipping [INFO] Cannot read /home/xx/workspace/Java/src/test/resources/jgiven/custom.js, skipping [INFO] ------------------------------------------------------------------- [INFO] Generated JGiven HTML reports to directory /home/xx/workspace/Java/target/jgiven-reports/html [INFO] -------------------------------------------------------------------
ブラウザで開いてみると、こんな感じに見れます。
まとめ
レポート機能は HTML (ブラウザで確認)のためにあるような印象です。
今までテスト結果をコンソール上や、IDE上でのみか確認出来なかった場合、外部向けに説明するには何らかの加工をしてあげる必要がありましたが、ブラウザ上でのレポートであれば見やすく、外部向けにも説明がしやすくなると思います。
ついでに、テストを書くのが楽しくなれば良いと思います。
JGiven をユーザーガイド見ながら試す(Java, JUnit)
JGiven を使おうとしたきっかけ
Java のプロジェクトに関しては、普段 JUnit を使用してテストを書いていますが、以下のような問題に悩まされています。
1. 既存テストの要点を掴むのが難しいことがある
他人が書いたテストコードはもちろんのこと、自分で書いたテストですら数ヶ月も経つと、テストの要点(目的)などが分かり難くなってしまっていることがあります。
テスト内でテストデータを作ったり、条件を作ったりしていると、それをトレースするのが大変になったりします。
2. テストのレポートがわかりにくい
JUnit でテストを実行した結果、例えその結果が全てパス(成功)だったとしても、どういった仕様を満たしているのかわかりにくい場合があります。
特にテストケースが数十、百以上になってきたりすると、どのようなテストケースがあるのか把握するのが難しくなります。
3. 開発者(プログラマー)しか読めない
上記と類似することですが、テストの仕様を理解するのに、プログラム(ソースコード)を読まなければならない場合があるため、開発者以外が仕様を理解することが難しくなります。
場合によっては、開発者以外のために、別途ドキュメントを用意する必要があります。
結局のところ、以下の2点が満足できれば、これらの問題を解消する手助けになりそうです。
1. 読みやすいテストが書けること
2. 分かりやすいレポートが出力されること
これらが、JGiven フレームワークに期待しているポイントになります。
ユーザーガイド
それでは、ユーザーガイドに沿って、インストールから最初の実行までやってみます。
JGiven User Guide
インストール
JUnit と maven を使用しているので、以下を pom に指定してあげます。
<dependency>
<groupId>com.tngtech.jgiven</groupId>
<artifactId>jgiven-junit</artifactId>
<version>0.17.0</version>
<scope>test</scope>
</dependency>
JUnit テストクラスを作る
何はともあれ、テストクラスがなければ話が始まらないので作ります。
JUnit の場合は、ScenarioTest を継承する必要があるようです。
更に ScenarioTest は3種類のパラメータを要求しており、それぞれ Given-When-Then と呼ばれます。
import com.tngtech.jgiven.junit.ScenarioTest; public class MyShinyJGivenTest extends ScenarioTest<GivenSomeState, WhenSomeAction, ThenSomeOutcome> { }
日本語で言うと、以下のようなステージ構成になるようです。
Given : 何かの状態のとき
When : 何かをしたら
Then : 何かの結果になる
これを上手く表現することが出来れば、そのまま仕様のように読み取れるかもしれません。
Given, When, Then クラスを作る
それぞれのステージを構成するためのクラスを生成します。
Given
import com.tngtech.jgiven.Stage; public class GivenSomeState extends Stage<GivenSomeState> { public GivenSomeState some_state() { return self(); } }
When
import com.tngtech.jgiven.Stage; public class WhenSomeAction extends Stage<WhenSomeAction> { public WhenSomeAction some_action() { return self(); } }
Then
public class ThenSomeOutcome extends Stage<ThenSomeOutcome> { public ThenSomeOutcome some_outcome() { return self(); } }
Stage クラスの継承を必須としているわけではないそうですが、and() や self() などの有用なメソッドが提供されているため、推奨されているようです。
※何が有用なのか現時点では不明ですが
テストシナリオを書く
それではいよいよテストシナリオを書いていきます。
最初に作ったテストクラスに追記しています。
public class MyShinyJGivenTest extends ScenarioTest<GivenSomeState, WhenSomeAction, ThenSomeOutcome> { @Test public void something_should_happen() { given().some_state(); when().some_action(); then().some_outcome(); } }
ScenarioTest を継承したことにより、given(), when(), then() がそれぞれ指定したクラスのインスタンスに紐づいているようです。
なので、インスタンス.メソッド名 という形でメソッドの呼び出しが可能となっています。
実行してみる
実行してみると、以下のような結果が出力されました。
Given, When, Then は固定で、some xxx は、メソッド名(some_state など)から来ているようです。
Test Class: test.java.practice.bdd.jgiven.MyShinyJGivenTest Something should happen Given some state When some action Then some outcome
なので、例えば日本語でメソッド名を作成してみた場合、こんな感じの出力が期待できます。
どんなテストをやっているのか分かりやすいですね。
Given サーバーが止まっている時に When プログラムを実行すると Then エラーになる
もし ScenarioTest を継承することができない場合
すでに他のクラスを継承しているなどで、ScenarioTest を継承することができない場合は、JGiven の JUnit rule を使用すれば良いとのこと。
具体的には、JGivenClassRule と JGivenMethodRule を定義すること。
Given-When-Then それぞれのクラスを @ScenarioStage アノテーションを用いて注入してあげること。
public class UsingRulesTest { @ClassRule public static final JGivenClassRule writerRule = new JGivenClassRule(); @Rule public final JGivenMethodRule scenarioRule = new JGivenMethodRule(); @ScenarioStage GivenSomeState someState; @ScenarioStage WhenSomeAction someAction; @ScenarioStage ThenSomeOutcome someOutcome; @Test public void something_should_happen() { someState.given().サーバーが止まっている時に(); someAction.when().プログラムを実行すると(); someOutcome.then().エラーになる(); } }
ScenarioTest のソースを見てみると、似たようなことをしているので、継承する代わりに自分たちで定義してあげるような格好になっていると思われます。
public class ScenarioTest<GIVEN, WHEN, THEN> extends ScenarioTestBase<GIVEN, WHEN, THEN> { @ClassRule public static final JGivenClassRule writerRule = new JGivenClassRule(); @Rule public final JGivenMethodRule scenarioRule = new JGivenMethodRule( createScenario() ); @Override public Scenario<GIVEN, WHEN, THEN> getScenario() { return (Scenario<GIVEN, WHEN, THEN>) scenarioRule.getScenario(); } }
まとめ
以上、ユーザーガイドに沿って試行してみましたが、当初期待していた2点
> 1. 読みやすいテストが書けること
> 2. 分かりやすいレポートが出力されること
について、JGiven が手助けになる可能性を感じることができました。
もちろん現時点では、テストケースも少なくシンプルなので分かりやすいですが、実際にシナリオが増えてきた場合に、上手くシンプルな状態を保ったまま管理が出来るかは分かりません。
とは言え、テスト結果が以下のように表示されるだけでも強力です。
Given サーバーが止まっている時に When プログラムを実行すると Then エラーになる
たとえば、以下のように Given だけ差し替えることも容易に出来ると思われます。
Given ネットワークが止まっている時に When プログラムを実行すると Then エラーになる
レポート出力に関しては、機能が充実しているようなので、機会があれば試行してみたいと思います。
terminator(端末)が起動しなくなった except (KeyError,ValueError)
Ubuntu14.04 上でいつの間にか terminator が起動しなくなりました。
デスクトップ上のランチャーを叩いても、何の応答もありません。
仕方なく、他の端末(XTerm)を起動し、/usr/bin/terminator を直接実行してみたところ、以下のエラーが表示。
File "/usr/bin/terminator", line 103 except (KeyError,ValueError), ex: SyntaxError: invalid syntax
SyntaxError ? なぜ、terminator のソースをさわってもいないのに突然そのようなエラーが出るのか。
答えは、python 3 をデフォルトに設定していたためでした。
※確かに、この問題が発生する直前に変更していました
/usr/bin/terminator の1行目に、#! /usr/bin/python と指定されています。
そのため、python 3 で terminator を起動するようになったと思われますが、それが互換性がなくて SyntaxError になってしまったと思われます。
回避策として、1行目を #! /usr/bin/python2 として、python2を適用することを明示すれば、問題なく起動するようになりました。
この問題は初めて遭遇しましたが、前々からよくありそうですし、報告されています。
bugs.launchpad.net
タイガースとジャイアンツが含まれるツイート件数をカウントしてみた
データ取得期間
2018/11/11 - 11/13 内の数時間
データ取得方法
集計ログ
取得した総ツイート件数
scala> val rawData = sc.textFile("タイガース_ジャイアンツ.txt") rawData: org.apache.spark.rdd.RDD[String] = タイガース_ジャイアンツ.txt MapPartitionsRDD[19] at textFile at <console>:27 scala> rawData.count res24: Long = 1650
ツイートにタイガースが含まれる件数
scala> def isTigers(line: String): Boolean = line.contains("タイガース") isTigers: (line: String)Boolean scala> rawData.filter(isTigers).count res25: Long = 551
ツイートにジャイアンツが含まれる件数
scala> def isGiants(line: String): Boolean = line.contains("ジャイアンツ") isGiants: (line: String)Boolean scala> rawData.filter(isGiants).count res26: Long = 1196
1つのツイートにタイガースとジャイアンツの両方が含まれているものもある。
しかし、ジャイアンツが含まれているツイート数がタイガースよりも、およそ2倍という結果に。
ツイートに矢野監督が含まれる件数
scala> def isYano(line: String) = line.contains("矢野監督") isYano: (line: String)Boolean scala> rawData.filter(isYano).count res30: Long = 5
ツイートに原監督が含まれる件数
scala> def isHara(line: String) = line.contains("原監督") isHara: (line: String)Boolean scala> rawData.filter(isHara).count res31: Long = 11
もちろん、これだけで世間の注目度を示しているわけではないし、集計期間や集計方法にも問題がたくさんあると思いますが、それにしても思っていた以上に数値に差が出る結果となりました。
もう少しサンプルデータを増やした上で、別の角度からも集計をしてみたい。
grep -E が正規表現対応オプションであると勘違いしていた
grep って、global regular expression print の略なので、それ自体が正規表現で一致したものを抽出するコマンドなんですね。
正規表現を使いたい時に、-E オプションを付けるものだと勘違いしていました。
いま、以下のように2行が書かれたファイル(test.txt)があるとします。
$ cat > test.txt 1024 hatena
$ grep ^1 test.txt 1024
はい。特に -E オプションを付けなくても正規表現でマッチしてくれました。
ところが。
$ grep '^(1|h)' test.txt
これは何も返してくれません。
そこで、-E オプションを付けてみると。
$ grep -E '^(1|h)' test.txt 1024 hatena
期待する結果を返してくれました。
E オプションをつけることで、拡張正規表現と呼ばれる式が使用出来るようになります。
egrep でもOKです。
$ egrep '^(1|h)' test.txt 1024 hatena
拡張正規表現を使える = 正規表現を使えると間違って覚えてしまっていたようです。
何気に使用しているオプションについて、意味を正しく理解していないのは宜しくないですね。