月別アーカイブ: 2015年9月

Redisにおけるデータの大量挿入手法

Pocket

こんにちは。Bypass開発チームの川住と申します。
今回はRedisにおけるデータの大量挿入手法について紹介します。

Redisとは?

まず、Redisについて少し紹介します。
Redisとは、メモリ上に Key-Value Store(KVS)を構築できるソフトウェアです(http://redis.io/)。
同系統のソフトウェアにはmemcached (http://memcached.org/) などがあります。
memcachedと比較すると、

  • シングルスレッドでクエリを処理する
  • データを永続化できる
  • ハッシュやリストなど、様々なデータ型を利用できる

といった特徴があります。

これらのソフトウェアを用いると、
メモリ上にデータを保持しており、かつ処理がシンプルであるため、
高速にデータの取得(格納)可能なKVSを構築できます。

データの大量挿入

本題に移ります。
Redisのデータの格納はとても高速ですが、シングルスレッドでクエリを処理するため、
データ数に比例して処理に時間がかかってしまいます。

そこで、大量のデータを挿入する時には、
「1件あたりのデータの挿入にかかる時間」をできるだけ短くする必要があります。

弊社では、Perlスクリプトでデータを加工し、Redisに大量のデータを格納しています。
この時、PerlのRedisモジュールを用いて1件ずつデータを格納してしまうと、

  • Redisとのデータの送受信で発生するRTT (Round-Trip Time)
  • Redisからのレスポンスをパースし、Perlのデータ型に加工する時間

が問題になります。そこで、

  • 複数のコマンドの一括送信
  • Redisからのレスポンスの破棄
    • socketやnc (netcat) でコマンドを送信し、レスポンスを/dev/null等に捨てる

を行い、処理時間を削減します。

処理時間の計測

下記の手法で10万件のデータの格納にかかった時間をそれぞれ計測しました。

  • PerlのRedisモジュールを使う(手法1)

  • socketを用いてデータを流し込む(手法2)

  • ncを用いてデータを流し込む(手法3)

計測結果

同端末で各手法5回ずつ試行し、その平均を処理時間としました。

手法 処理時間 (sec) ロス率
手法1 (module) 59.710145 0%
手法2 (socket) 2.978576 0%
手法3 (netcat) 2.626164 18%

複数のデータの格納コマンドを一括で送信し、
レスポンスのパースを行わないことによって処理時間を大幅に削減できます。
socketを用いる場合は送信(受信)バッファの管理を行わないと、
バッファ溢れによってデータロスが発生してしまいます。

まとめ

今回はRedisにおけるデータの大量insert手法を紹介しました。
目的に合わせて最適な手法を選択することでより効率的にデータを処理できるようになります。
(参考URL: http://redis.io/topics/mass-insert

基本統計量について社内勉強会で発表しました

Pocket

こんにちは。データサイエンティストチームの西岡です。

先日、統計入門ということで基本統計量について社内勉強会で発表しました。

「平均」は恐らく誰もがご存知の統計指標だと思いますが、 平均だけに頼っていてはデータの特徴が見えなくなることもあるよ、ということに伝えるために基本統計量について紹介しました。

基本統計量には大きく分けて

  • 代表値 (representative value)
  • 散布度 (dispersion)

の2種類があります。

代表値は平均値・中央値・最頻値など、文字通りデータを1つの値で示す指標であり、 散布度は分散・標準偏差など、データのバラつき具合を表す指標です。

代表値として平均値ではなく中央値や最頻値を使ったほうが適切な場合もありますし、散布度と合わせるとベターです。

平均値に騙されず、より正確にデータの特徴を掴んでいきましょう。

Pythonでテスト 連載(2) ユニットテストの書き方

Pocket

こんにちは。技術開発部データサイエンティストチームの西岡です。

前回の連載ではなぜユニットテストを書くのかについて述べました。
今回は実際にPythonのunittestモジュールを使用しながら、テストの書き方を説明しようと思います。

Pythonのユニットテストの話ではありますが、ユニットテストの思想自体は他言語でも通用するものでもありますし、Pythonの使用者以外の方も自身のよく使われる言語のユニットテストフレームワークと照らし合わせながら読んでいただければと思います。

なお、ここではPython3を使用しています。

テストケース

calc.pyはテスト対象のスクリプト、test_calc.pyはテストスクリプトです。

calc.py

tests/test_calc.py

ディレクトリ構造は以下のようであるとします。

test_calc.pyでは、calc.pyのadd_three関数にある入力を渡し、その出力が期待通りであるかをテストしています。 これをテストケースと呼びます。

unittestには

  • unittest.TestCaseクラスを継承したクラスにテストメソッドを記述
  • テストメソッドはtest_*で始める

というルールがありますが、これさえ守ればコマンドラインからテストを実行することができます。

この例ではもちろん5+3≠7なので、

上のようにテストが失敗します。 テストが通ったときは下のような表示になります。

テストフィクスチャ

ここで、Rectangleという長方形を表すクラスがあるとします。 Rectangleクラスには、

  • 長方形の幅・高さを取得するgetメソッド
  • 長方形の幅・高さを設定するsetメソッド
  • 長方形の面積を取得するareaメソッド

があり、インスタンスの使用が終わるとfinalizeメソッドで幅と高さをNoneにしなければならないものとします。

階層構造

rect.py

tests/test_rect.py

という感じになるかと思います。

しかし、これではrec = rect.Rectangle(5, 3)rec.finalize()を全テストメソッドで書かなければならず、非常に手間です。

unittestでは、このようなテストに伴って発生する初期化処理・終了処理をまとめるためにsetUp・tearDownメソッドを使用します。

tests/test_rect.py

これにより初期化・終了処理をまとめることができ、すっきりしたテストコードになります。

ただし、setUpとtearDownによってインスタンスの生成が1回にまとまったわけではないことに注意が必要です(以下の実行結果を参照)。

これはテストが以前におこなわれた何らかの実行に依存しないようにするためです。

テストの書き方

テスト対象の関数が複数のことをおこなう場合

テストの書き方は、テスト対象のコードにより大きく書き方が変わります。

以下の関数をどうテストすればよいかを考えてみましょう。

このcalc_average_bmi関数では、全員のBMIの平均を返します(BMIが何かについては、こちらのサイトを参照)。

この関数をテストする際に問題が起こりうる箇所は2つあります。

  • 返される平均の値が正しくないとき、BMI計算が間違っているのか平均計算が間違っているのか特定できない

  • 引数に空の配列が渡された時、平均計算においてZeroDivisionErrorになる

  • person.height == 0のときにBMI計算においてZeroDivisionErrorになる

下2つの「引数に空の配列が渡される」「身長が0である」といった例外はcatchすれば済みますが、そもそもcalc_average_bmi関数は

  • 平均計算
  • BMI計算

の2つのことをやっているため、関数自身はどんどん読みづらく、肥大化していきます。

そこで、BMI計算と平均計算を分離しましょう。

bmi.py

test_bmi.py

と分離すれば、bmi計算と平均計算は各々のメソッドで完結し、calc_average_bmiはほぼユニットテストを書く必要もないほど単純なコードになりました。

ここでは省きましたが、身長の単位がセンチメートルかメートルかもこのメソッドの中でテストすることができます。

メソッドに1つのことだけをさせるように変更すれば、テストもその1つのことだけを確認すればよくなるため、テストが非常に書きやすくなり、またテスト漏れも防ぐことができます。

プログラムをテスタブルにするため、テストが書きやすいようにプロダクションコードの設計から見直すことが重要です。

外部サービスのテスト

ここでいう外部サービスとは、データベースやWeb APIなどです。

git commit時のテストで毎回データベースを丸々スキャンするようなクエリを流すわけにもいきませんし、外部APIは(あまり無いかとは思いますが)落ちている可能性もあるので、外部サービスとの連携テストにはモックオブジェクトを使用するのが一般的です。

今回はモックを使用したテストについては述べませんが、いずれ取り上げてみたいと思います。

まとめ

テスタブルなコードを書くことでバグを減らし、より高速な開発をおこなっていきましょう。