2013年12月11日水曜日

AutoScalingってなんじゃ?(AutoScalingがAWSマネージメントコンソールで管理可能に!)

ついにAWSの管理コンソールからAutoScalingが管理可能になりました。

早速触ってみます。

EC2のコンソールの左ペインの最下部にAutoScaling(以下AS)の項目が追加されています。
選択してみると、左側に概要が表示されるので、「Create Auto Scaling Group」をクリックします。




Launch Configuration の作成



既存のLaunchConfiguration(以下LC)を使うか、新規にLCを作るかを聞かれるので、今回は新規LCを選びます。


すると、LC作成ウィザードに切り替わり、インスタンスの起動にまつわる設定が始まります。


AMI


インスタンスタイプ




詳細設定


ここで、LC名を入力します。
それ以外はインスタンス起動の詳細設定と同じです。




ストレージ





セキュリティグループ





確認画面






キーの選択




ここでLCで設定する鍵を選択して「Create launch configuration」をクリックすると、LCの作成は終了で、自動的にASの作成に移ります。



Auto Scaling Groupの作成



基本設定


AS作成画面では、先に新規作成したLCまたは既存から選択したLCがセットされ、AS名やサイズ、起動場所(VPCやゾーン)、バランサのヘルスチェックなどを入力します。




スケーリングポリシー


次にスケーリングポリシーを設定します。
アラームや増減の限界値や単位を設定します。



「Add new alarm」でその場でアラーム設定が可能です。




通知


通知の設定をします。ASのイベントとそれが発生した時の通知先を設定します。




確認画面


最後に確認画面で内容をチェックし、「Create auto scaling group」をクリックして設定を終えます。





Auto Scaling Groupの確認



設定の変更


一覧画面に作成したばかりのASが表示されます。
また、ASを選択すると、下部ペインの「Details」タブなどで詳細が確認でき、
「Edit」で設定の変更が可能です。

ここで、DesiredCapacityの変更もできるので、min=1, max=5, desired=1にして、1つインスタンスを起動してみます。




すると、「ScalingHistory」タブでスケーリングイベントの履歴が確認できます。
失敗イベントはその理由も記述されているので簡単なデバッグも行えます。




インスタンス一覧を見てみます。



正常に起動しているようです。


いままでASはコンソールからはまったく見えませんでしたが、これで視認性がよくなりました。
これからもコマンドライン等で設定を行うという人も、状態の確認などはコンソールを使うと便利かもしれません。


以上です。



2013年12月9日月曜日

CDPってなんじゃ?(Kinesisでstream chain)

この記事は「CDP Advent Calendar 2013」12/9用の記事です。

 前回前々回でKinesisを触りました。

資料などみていく中で、ストリームの終端になりえるものに、Redshift, S3, EMR, DynamoDBなどの他に、別のKinesisストリームもありえるということを知りました。

処理の内容が単純、高速であったり、データ数が少ない場合には1つのConsumerで事足りると思いますが、あまりにも大量であったり、処理の幾つかに非常に時間の掛かるものがあったり、複合的な処理が必要な場合には、複数のストリームを使うと全体の効率が上がる場合があるかと思います。

これは、CDPQueuing Chainパターンの派生的なものになるかと思います。
そこで、別のKinesisストリームに流すにはどういう場合が有効かを考えてみました。

Stream Chianパターン


・統計的な処理を連続して行う場合、Producerが投入したデータ単位と、1つ目の処理が終わったデータ単位が異なる場合があります。その場合も1つ目が終わったときに別のストリームを使います。



・処理全体の中で、負荷の高い部分と低い部分を分けて別に処理を行うことで、コストの最適化を行うことも考えられます。




・共通処理もあるが、データの種類によって個別の処理が必要という時も別のストリームを使える場合があります。




・おまけ:JettyをProducerにして、3つストリームのチェーンを使うと、ジェットストリームアタックになります。





最後のが言いたかっただけでした。
以上です。



2013年12月4日水曜日

Kinesisってなんじゃ?(Java実装編)

前回からの続きです。

このストリームをつかってイベント処理を行うアプリケーションを実装します。
Kinesisを扱うには、現時点でJavaのSDKとKinesisのクライアントライブラリ(KCL)が利用可能です。

今回のサンプルとなるシステムですが、以下のように定義してみました。
  • Producerはユーザーの出発地点(0, 0)からの到達位置(x, y)を日時ごとにJSONとしてストリームに入力していきます。
  • ConsumerAではストリームからデータを受け取り、ログとしてS3のバケットに保存していきます。
  • ConsumerBでは同じくストリームからデータを受け取り、ユーザーごとの総移動距離をDynamoDBにインクリメント保存していきます。

これを、限定公開用にKinesis対応されたJavaのSDKとKinesisのクライアントライブラリ(KCL)を使って実装してみます。SDKにはサンプルコードなどが付属しているため、それを参考に実装してみます。
また、KCLはシャードの管理のために裏側でConsumerごとにDynamoDBテーブルを使用します。

JDKの1.7とantがインストールされていることが前提です。


Producer


以下のツリー図の通り、SDKのサンプルコードの一部として他のサンプルと同じようにMemoKinesisというサンプルをつくりました。

/usr/local/src/aws-java-sdk-1.6.4/
├── lib
│   ├── aws-java-sdk-1.6.4.jar
│   ├── aws-java-sdk-1.6.4-javadoc.jar
│   ├── aws-java-sdk-1.6.4-sources.jar
│   ├── aws-java-sdk-flow-build-tools-1.6.4.jar
│   ├── commons-lang-2.6.jar
│   ├── joda-time-2.3-dist.tar.gz
│   ├── joda-time-2.3.jar
│   └── jsonic-1.3.0.jar
├── samples
│   └── MemoKinesis
│       ├── AwsCredentials.properties
│       ├── build.xml
│       ├── Hoge.java
│       └── MemorycraftKinesisProducer.java
└── third-party

配下のbuild.xmlは他サンプルと同様に、../../lib, ../../third-partyにクラスパスを通して、コンパイル→実行するようなターゲットになっているため、クラス名だけ変更してそのまま利用しています。
AwsCredentials.propertiesには自分のアカウントのキー情報を入力してあります。
また、このコードで必要なライブラリは../../libディレクトリに追加してあります(青字)。

また、コードは以下の通りです。

MemorycraftKinesisProducer.java

Hoge.java

Producerでは、testuser_[A-Z]の26人のユーザーIDと、日時、ランダムなx,y をJSON化してストリームに投入しつづけます。
AmazonKinesisClientを初期化し、PutRecordRequestを使ってputRecordします。
実行すると以下のように投入され続けていくことがわかります。
[root@ip-10-154-154-57 MemoKinesis]# ant
Buildfile: /usr/local/src/aws-java-sdk-1.6.4/samples/MemoKinesis/build.xml

run:
    [javac] /usr/local/src/aws-java-sdk-1.6.4/samples/MemoKinesis/build.xml:12: warning: 'includeantruntime' was not set, defaulting to build.sysclasspath=last; set to false for repeatable builds
    [javac] Compiling 1 source file to /usr/local/src/aws-java-sdk-1.6.4/samples/MemoKinesis
    [javac] warning: Supported source version 'RELEASE_6' from annotation processor 'com.amazonaws.eclipse.simpleworkflow.asynchrony.annotationprocessor.AsynchronyDeciderAnnotationProcessor' less than -source '1.7'
    [javac] 1 warning
     [java] Dec 03, 2013 10:08:23 AM MemorycraftKinesisProducer main
     [java] INFO: {"datetime":"2013-12-03 10:08:22","user_id":"testuser_F","x":"66.41701160878385","y":"85.85361518485006"}
     [java] Dec 03, 2013 10:08:23 AM MemorycraftKinesisProducer main
     [java] INFO: {"datetime":"2013-12-03 10:08:23","user_id":"testuser_D","x":"64.19880893804586","y":"26.65513499028829"}
     [java] Dec 03, 2013 10:08:24 AM MemorycraftKinesisProducer main
     [java] INFO: {"datetime":"2013-12-03 10:08:23","user_id":"testuser_T","x":"59.34696505060783","y":"85.54404394674596"}
     [java] Dec 03, 2013 10:08:24 AM MemorycraftKinesisProducer main
     [java] INFO: {"datetime":"2013-12-03 10:08:24","user_id":"testuser_N","x":"76.26154448594934","y":"67.74843698818461"}
     [java] Dec 03, 2013 10:08:24 AM MemorycraftKinesisProducer main
     [java] INFO: {"datetime":"2013-12-03 10:08:24","user_id":"testuser_Z","x":"29.457789127237753","y":"18.48254643961956"}
     [java] Dec 03, 2013 10:08:24 AM MemorycraftKinesisProducer main
     [java] INFO: {"datetime":"2013-12-03 10:08:24","user_id":"testuser_Z","x":"9.91729666668798","y":"18.193714091252332"}
     [java] Dec 03, 2013 10:08:24 AM MemorycraftKinesisProducer main
     [java] INFO: {"datetime":"2013-12-03 10:08:24","user_id":"testuser_M","x":"98.54843645906183","y":"83.25335740427954"}
     [java] Dec 03, 2013 10:08:24 AM MemorycraftKinesisProducer main
     [java] INFO: {"datetime":"2013-12-03 10:08:24","user_id":"testuser_F","x":"22.2596852199308","y":"39.49631977033009"}
     [java] Dec 03, 2013 10:08:25 AM MemorycraftKinesisProducer main
     [java] INFO: {"datetime":"2013-12-03 10:08:24","user_id":"testuser_W","x":"35.34360225385589","y":"7.982969346289337"}
.......



Consumer A


次はConsumerです。これも同じように他のサンプルコードとほぼ同じ構成で実装しました。

/usr/local/src/aws-java-sdk-1.6.4/
├── lib
│   ├── aws-java-sdk-1.6.4.jar
│   ├── aws-java-sdk-1.6.4-javadoc.jar
│   ├── aws-java-sdk-1.6.4-sources.jar
│   ├── aws-java-sdk-flow-build-tools-1.6.4.jar
│   ├── commons-lang-2.6.jar
│   ├── jsonic-1.3.0.jar
│   └── KinesisClientLibrary.jar
├── samples
│   └── MemoKinesis
│       ├── AwsCredentials.properties
│       ├── build.xml
│       ├── ConfigKeys.java
│       ├── MemorycraftKinesisLoggingConsumer.java
│       ├── MemorycraftKinesisLoggingProcessorFactory.java
│       └── MemorycraftKinesisLoggingProcessor.java
└── third-party

Consumer Aのインスタンス群では、ストリームに流れているデータを抽出して、順次S3にログとして保存していきます。
ConsumerではKCLを使用します。

構成としては、MemorycraftKinesisLoggingConsumerからIRecordProcessorFactoryを実装したMemorycraftKinesisLoggingProcessorFactoryを初期化してワーカーとして実行します。
実際の処理はIRecordProcessorを実装したMemorycraftKinesisLoggingProcessorのprocessRecordsメソッド内部に記述します。


MemorycraftKinesisLoggingConsumer.java

MemorycraftKinesisLoggingProcessorFactory.java

MemorycraftKinesisLoggingProcessor.java

実行してみます。
[root@ip-10-118-97-158 MemoKinesis]# ant
Buildfile: /usr/local/src/aws-java-sdk-1.6.4/samples/MemoKinesis/build.xml

run:
    [javac] /usr/local/src/aws-java-sdk-1.6.4/samples/MemoKinesis/build.xml:12: warning: 'includeantruntime' was not set, defaulting to build.sysclasspath=last; set to false for repeatable builds
    [javac] Compiling 4 source files to /usr/local/src/aws-java-sdk-1.6.4/samples/MemoKinesis
    [javac] warning: Supported source version 'RELEASE_6' from annotation processor 'com.amazonaws.eclipse.simpleworkflow.asynchrony.annotationprocessor.AsynchronyDeciderAnnotationProcessor' less than -source '1.7'
    [javac] 1 warning
..........
     [java] INFO: Successfully published 3 datums.
     [java] Dec 03, 2013 11:58:06 AM com.amazonaws.services.kinesis.metrics.impl.DefaultCWMetricsPublisher publishMetrics
     [java] INFO: Successfully published 18 datums.
     [java] Dec 03, 2013 11:58:14 AM com.amazonaws.services.kinesis.clientlibrary.lib.worker.Worker$WorkerLog info
     [java] INFO: Current stream shard assignments: shardId-000000000001, shardId-000000000000
     [java] Dec 03, 2013 11:58:14 AM com.amazonaws.services.kinesis.clientlibrary.lib.worker.Worker$WorkerLog info
     [java] INFO: Sleeping ...
     [java] Dec 03, 2013 11:58:16 AM com.amazonaws.services.kinesis.metrics.impl.DefaultCWMetricsPublisher publishMetrics
     [java] INFO: Successfully published 20 datums.
     [java] Dec 03, 2013 11:58:16 AM com.amazonaws.services.kinesis.metrics.impl.DefaultCWMetricsPublisher publishMetrics
     [java] INFO: Successfully published 3 datums.
     [java] Dec 03, 2013 11:58:18 AM MemorycraftKinesisLoggingProcessor process
     [java] INFO: process record [21269323576357670914947437401207537664]
     [java] Dec 03, 2013 11:58:18 AM MemorycraftKinesisLoggingProcessor process
     [java] INFO:  saved [json]={"datetime":"2013-12-03 08:41:05","user_id":"testuser_G","x":"54.504733903095726","y":"27.966324048617963"}
     [java] Dec 03, 2013 11:58:18 AM MemorycraftKinesisLoggingProcessor process
     [java] INFO: process record [21269323576357720993054772957520920576]
     [java] Dec 03, 2013 11:58:19 AM MemorycraftKinesisLoggingProcessor process
     [java] INFO:  saved [json]={"datetime":"2013-12-03 08:41:05","user_id":"testuser_W","x":"12.256118907591883","y":"63.004577908019634"}
     [java] Dec 03, 2013 11:58:19 AM MemorycraftKinesisLoggingProcessor process
     [java] INFO: process record [21269323576357794417499720660453949440]
     [java] Dec 03, 2013 11:58:19 AM MemorycraftKinesisLoggingProcessor process
     [java] INFO:  saved [json]={"datetime":"2013-12-03 08:41:05","user_id":"testuser_O","x":"69.81831906353719","y":"31.571006624423724"}
     [java] Dec 03, 2013 11:58:19 AM MemorycraftKinesisLoggingProcessor process
     [java] INFO: process record [21269323576357840331291814692712415232]
     [java] Dec 03, 2013 11:58:19 AM MemorycraftKinesisLoggingProcessor process
     [java] INFO:  saved [json]={"datetime":"2013-12-03 08:41:04","user_id":"testuser_F","x":"19.924814432111905","y":"41.65256488223227"}
..........


うまく拾えているようです。
S3のバケットを見てみます。

おお、次から次へとファイルが保存されていくのがわかります。
そのうちの一つをダウンロードして、中身を見てみます。
{"datetime":"2013-12-03 08:44:17","user_id":"testuser_H","x":"79.49199108301805","y":"2.4829017202529724"}
中身もうまく入っているようです。


Consumer B



次はConsumerBです。

/usr/local/src/aws-java-sdk-1.6.4/
├── lib
│   ├── aws-java-sdk-1.6.4.jar
│   ├── aws-java-sdk-1.6.4-javadoc.jar
│   ├── aws-java-sdk-1.6.4-sources.jar
│   ├── aws-java-sdk-flow-build-tools-1.6.4.jar
│   ├── commons-lang-2.6.jar
│   ├── jsonic-1.3.0.jar
│   └── KinesisClientLibrary.jar
├── samples
│   └── MemoKinesis
│       ├── AwsCredentials.properties
│       ├── build.xml
│       ├── ConfigKeys.java
│       ├── Hoge.java
│       ├── MemorycraftKinesisDistanceConsumer.java
│       ├── MemorycraftKinesisDistanceProcessorFactory.java
│       └── MemorycraftKinesisDistanceProcessor.java
└── third-party


Consumer Bのインスタンス群では、ユーザーIDをハッシュキーにしたDynamoDBのレコードにXとYから距離を出してインクリメントしていき総距離を加算更新します。

クラスとしてはMemorycraftKinesisDistanceProcessorを中心に実装します。

MemorycraftKinesisDistanceConsumer.java

MemorycraftKinesisDistanceProcessorFactory.java

MemorycraftKinesisDistanceProcessor.java

実行してみます。

[root@ip-10-60-155-21 MemoKinesis]# ant
Buildfile: /usr/local/src/aws-java-sdk-1.6.4/samples/MemoKinesis/build.xml

run:
    [javac] /usr/local/src/aws-java-sdk-1.6.4/samples/MemoKinesis/build.xml:12: warning: 'includeantruntime' was not set, defaulting to build.sysclasspath=last; set to false for repeatable builds
     [java] Dec 03, 2013 3:22:17 PM MemorycraftKinesisDistanceConsumer configure
     [java] INFO: Using workerId: ip-10-60-155-21.ec2.internal:584216f1-8d4d-429c-ba99-8af365345e17
     [java] Dec 03, 2013 3:22:17 PM MemorycraftKinesisDistanceConsumer configure
.......
     [java] INFO: Successfully published 18 datums.
     [java] Dec 03, 2013 3:23:18 PM com.amazonaws.services.kinesis.clientlibrary.lib.worker.Worker$WorkerLog info
     [java] INFO: Current stream shard assignments: shardId-000000000001, shardId-000000000000
     [java] Dec 03, 2013 3:23:18 PM com.amazonaws.services.kinesis.clientlibrary.lib.worker.Worker$WorkerLog info
     [java] INFO: Sleeping ...
     [java] Dec 03, 2013 3:23:18 PM MemorycraftKinesisDistanceProcessor process
     [java] INFO: process record [21269323576357670914947437401207537664]
     [java] Dec 03, 2013 3:23:19 PM MemorycraftKinesisDistanceProcessor process
     [java] INFO:  saved [user_id:testuser_G, distance:61.260764757221075]
     [java] Dec 03, 2013 3:23:19 PM MemorycraftKinesisDistanceProcessor process
     [java] INFO: process record [21269323576357720993054772957520920576]
     [java] Dec 03, 2013 3:23:19 PM MemorycraftKinesisDistanceProcessor process
     [java] INFO:  saved [user_id:testuser_W, distance:64.18558473711015]
     [java] Dec 03, 2013 3:23:19 PM MemorycraftKinesisDistanceProcessor process
     [java] INFO: process record [21269323576357794417499720660453949440]
     [java] Dec 03, 2013 3:23:19 PM MemorycraftKinesisDistanceProcessor process
     [java] INFO:  saved [user_id:testuser_O, distance:76.62457919060493]
.....


それではDynamoDBを見てみます。
ちゃんとユーザーごとに集計されているようです。


続々加算されていきます。



チェックポイント



KinesisのコンシューマーでKCLを使うとコンシューマーごとにチェックポイントというマーカーを打つことができます。
IRecordProcessor.checkpointメソッド内で、checkpointer.checkpoint()を呼び出すと、ストリームのシーケンスのなかでどこまで読み込んだという記録が保存され、読み取りを中断したり障害が起こった後に、プロセスを再稼働すると、そのチェックポイントから読み取りが再開されます。
チェックポイントを呼び出すタイミングはユーザーに委ねられていて、今回のサンプルでは1分に設定しています。

   private static final long CHECKPOINT_INTERVAL_MILLIS = 60000L;
    public void processRecords(List records, IRecordProcessorCheckpointer checkpointer) {

        process(records);

        if (System.currentTimeMillis() > nextCheckpointTimeInMillis) {
            checkpoint(checkpointer);
            nextCheckpointTimeInMillis = System.currentTimeMillis() + CHECKPOINT_INTERVAL_MILLIS;
        }

    }


わかったこと


  • 定期的ではなく常にデータ処理を処理し続けることができる仕組みと、そのためのスケーラビリティを持っている。
  • ユーザーが書くコードは、最低限であればそんなに多くない。
  • バッチではタイムラグがありすぎる!という場合にはうってつけのサービス。
  • Consumer同士の処理順を気にしないでいい。処理順をつけたい場合は、そのConsumer内で行うか、出力先に別のストリームを指定しそちらで次の処理をするとか
  • DynamoDBと同様、ストリームのキャパシティの見積もりは必要です。でもシャード数を指定するだけなので変更自体は簡単
  • 周辺のインスタンス、特にConsumerはバッチと同様インスタンスの負荷をみてロールごとにAutoScalingGroupを作る必要あり
  • Consumerからデータの移動先となるDynamoDBやRedshiftその他のエンドポイントのキャパシティ管理も同様に注意が必要


以上です。

Kinesisってなんじゃ?(ストリーム作成編)

AWSの新サービスKinesisが限定プレビュー公開されています。
私のところでも見れるようになったので、触ってみました。

Kinesisとは


Kinesisは大量なデータのリアルタイムイベント処理をサポートするサービスです。
ユーザーは「ストリーム」を作り、そこにデータを流し、受け取り、処理を行います。

データを流す、受け取る、処理を行う、という部分はユーザーがプログラムで実装します。
Kinesisに接続するプログラムには大きく分けてProducerとConsumerという2つの立場があります。
  • Producerはストリームにデータを入力します。Producerは場合によってWEBまたはAPPサーバーのようなエンドポイントであったり、既存データのフェッチプログラムとして実装されます。
  • Consumerはストリームからデータを受け取り処理します。Consumerは処理内容ごとにAutoScalingグループなどによってクラスタ化されることが多くなります。

ストリームを流れるデータは順序付けされたシーケンスとして扱われ、「シャード」に分散されます。
また、Kinesisではシャードの単位でスループットが決定され、ユーザーは入出力の量に合わせてシャード数を設定できます。


ストリームの作成


それでは、まずストリームを作ってみます。
限定公開なので、コンソールのメニューには現れませんが、申請が通っている場合は、以下のURLでアクセスできます。

https://console.aws.amazon.com/kinesis/



「Create Stream」をクリックして、ストリームを作成します。
ストリーム名とストリーム数を入力します。
ストリーム数の目安がわからない場合は、DynamoDBと同じように、データサイズや書込回数、Consumer数を設定することで自動的に最適なシャード数を適用できます。




「Create」をクリックすると、作成中(CREATING)のストリームが一覧に表示されます。
Statusが「ACTIVE」になると利用可能です。



ストリーム名をクリックすると、ストリームのメトリクスが表示されます。
これをもとに、SDKなどでシャード数の変更などを行うことができます。



料金



  • シャード:1シャードあたり1時間0.015ドル
  • リクエスト:100万PUTで0.028ドル
  • データ転送(入力):無料 
  • データ転送(出力):EC2で処理する分には無料

これとは別に、実際の処理を行うEC2のインスタンス料金が掛かります。
ストリームの料金ではなく、接続するEC2の料金で回収するモデルのようです。


次回は、ストリームを使った実装をやってみます。
以上です。




2013年11月16日土曜日

re:Inventってなんじゃ?(CloudTrail)

re:Invent2013に来ています。
そこでCloudTrailの発表があったので触ってみました。

CloudTrailは、AWSのAPIコールの記録をS3にログとして保存するサービスです。
これによってセキュリティ分析や運用チェックなどを行うことが可能です。

AWSコンソールをみるとCoudTrailが追加されているので、開いてみます。


ログの保存先のS3バケットを指定して、必要であればその他のオプションも設定します。


Subscribeボタンを押すと、設定完了です。



しばらく、AWSの操作をしたあと、出力先に設定したS3バケットを確認します。
すると、以下のように、json形式のログファイルが圧縮された状態で日ごとに保存されるようになります。



このうちの一つをダウンロードして中身を見てみます。

{
    "Records": [
        {
            "awsRegion": "us-east-1", 
            "eventName": "DescribeRouteTables", 
            "eventSource": "ec2.amazonaws.com", 
            "eventTime": "2013-11-15T18:08:00Z", 
            "eventVersion": "1.0", 
            "requestParameters": {
                "filterSet": {}, 
                "routeTableIdSet": {}
            }, 
            "responseElements": "<responseOmitted>", 
            "sourceIPAddress": "54.249.240.229", 
            "userAgent": "aws-sdk-php/1.6.2 PHP/5.4.20 curl/7.19.7 openssl/1.0.0-fips", 
            "userIdentity": {
                "accessKeyId": "xxxxxxxxxxxxxxxxxx", 
                "accountId": "821635308497", 
                "arn": "arn:aws:iam::821635308497:root", 
                "principalId": "821635308497", 
                "type": "Root"
            }
        }, 
        {
            "awsRegion": "us-east-1", 
            "eventName": "DescribeSubnets", 
            "eventSource": "ec2.amazonaws.com", 
            "eventTime": "2013-11-15T18:07:59Z", 
            "eventVersion": "1.0", 
            "requestParameters": {
                "filterSet": {}, 
                "subnetSet": {}
            }, 
            "responseElements": "<responseOmitted>", 
            "sourceIPAddress": "54.249.240.229", 
            "userAgent": "aws-sdk-php/1.6.2 PHP/5.4.20 curl/7.19.7 openssl/1.0.0-fips", 
            "userIdentity": {
                "accessKeyId": "xxxxxxxxxxxxxxxxxx", 
                "accountId": "821635308497", 
                "arn": "arn:aws:iam::821635308497:root", 
                "principalId": "821635308497", 
                "type": "Root"
            }
        }, 
        {
            "awsRegion": "us-east-1", 
            "eventName": "DescribeVpcs", 
            "eventSource": "ec2.amazonaws.com", 
            "eventTime": "2013-11-15T18:07:58Z", 
            "eventVersion": "1.0", 
            "requestParameters": {
                "filterSet": {}, 
                "vpcSet": {}
            }, 
            "responseElements": "<responseOmitted>", 
            "sourceIPAddress": "54.249.240.229", 
            "userAgent": "aws-sdk-php/1.6.2 PHP/5.4.20 curl/7.19.7 openssl/1.0.0-fips", 
            "userIdentity": {
                "accessKeyId": "xxxxxxxxxxxxxxxxxx", 
                "accountId": "821635308497", 
                "arn": "arn:aws:iam::821635308497:root", 
                "principalId": "821635308497", 
                "type": "Root"
            }
        }, 
        {
            "awsRegion": "us-east-1", 
            "eventName": "DescribeSecurityGroups", 
            "eventSource": "ec2.amazonaws.com", 
            "eventTime": "2013-11-15T18:08:03Z", 
            "eventVersion": "1.0", 
            "requestParameters": {
                "filterSet": {}, 
                "securityGroupIdSet": {}, 
                "securityGroupSet": {}
            }, 
            "responseElements": "<responseOmitted>", 
            "sourceIPAddress": "54.249.240.229", 
            "userAgent": "aws-sdk-php/1.6.2 PHP/5.4.20 curl/7.19.7 openssl/1.0.0-fips", 
            "userIdentity": {
                "accessKeyId": "xxxxxxxxxxxxxxxxxx", 
                "accountId": "821635308497", 
                "arn": "arn:aws:iam::821635308497:root", 
                "principalId": "821635308497", 
                "type": "Root"
            }
        }, 
        {
            "awsRegion": "us-east-1", 
            "eventName": "DescribeNetworkAcls", 
            "eventSource": "ec2.amazonaws.com", 
            "eventTime": "2013-11-15T18:08:01Z", 
            "eventVersion": "1.0", 
            "requestParameters": {
                "filterSet": {}, 
                "networkAclIdSet": {}
            }, 
            "responseElements": "<responseOmitted>", 
            "sourceIPAddress": "54.249.240.229", 
            "userAgent": "aws-sdk-php/1.6.2 PHP/5.4.20 curl/7.19.7 openssl/1.0.0-fips", 
            "userIdentity": {
                "accessKeyId": "xxxxxxxxxxxxxxxxxx", 
                "accountId": "821635308497", 
                "arn": "arn:aws:iam::821635308497:root", 
                "principalId": "821635308497", 
                "type": "Root"
            }
        }, 
        {
            "awsRegion": "us-east-1", 
            "eventName": "DescribeDBInstances", 
            "eventSource": "rds.amazonaws.com", 
            "eventTime": "2013-11-15T18:08:06Z", 
            "eventVersion": "1.0", 
            "requestParameters": null, 
            "responseElements": null, 
            "sourceIPAddress": "54.249.240.229", 
            "userAgent": "aws-sdk-php/1.6.2 PHP/5.4.20 curl/7.19.7 openssl/1.0.0-fips", 
            "userIdentity": {
                "accessKeyId": "xxxxxxxxxxxxxxxxxx", 
                "accountId": "821635308497", 
                "arn": "arn:aws:iam::821635308497:root", 
                "principalId": "821635308497", 
                "type": "Root"
            }
        }, 
        {
            "awsRegion": "us-east-1", 
            "eventName": "DescribeVolumes", 
            "eventSource": "ec2.amazonaws.com", 
            "eventTime": "2013-11-15T18:08:04Z", 
            "eventVersion": "1.0", 
            "requestParameters": {
                "filterSet": {}, 
                "volumeSet": {}
            }, 
            "responseElements": "<responseOmitted>", 
            "sourceIPAddress": "54.249.240.229", 
            "userAgent": "aws-sdk-php/1.6.2 PHP/5.4.20 curl/7.19.7 openssl/1.0.0-fips", 
            "userIdentity": {
                "accessKeyId": "xxxxxxxxxxxxxxxxxx", 
                "accountId": "821635308497", 
                "arn": "arn:aws:iam::821635308497:root", 
                "principalId": "821635308497", 
                "type": "Root"
            }
        }, 
        {
            "awsRegion": "us-east-1", 
            "eventName": "DescribeAddresses", 
            "eventSource": "ec2.amazonaws.com", 
            "eventTime": "2013-11-15T18:08:03Z", 
            "eventVersion": "1.0", 
            "requestParameters": {
                "allocationIdsSet": {}, 
                "filterSet": {}, 
                "publicIpsSet": {}
            }, 
            "responseElements": "<responseOmitted>", 
            "sourceIPAddress": "54.249.240.229", 
            "userAgent": "aws-sdk-php/1.6.2 PHP/5.4.20 curl/7.19.7 openssl/1.0.0-fips", 
            "userIdentity": {
                "accessKeyId": "xxxxxxxxxxxxxxxxxx", 
                "accountId": "821635308497", 
                "arn": "arn:aws:iam::821635308497:root", 
                "principalId": "821635308497", 
                "type": "Root"
            }
        }, 
        {
            "awsRegion": "us-east-1", 
            "eventName": "DescribeInstances", 
            "eventSource": "ec2.amazonaws.com", 
            "eventTime": "2013-11-15T18:08:02Z", 
            "eventVersion": "1.0", 
            "requestParameters": {
                "filterSet": {}, 
                "instancesSet": {}
            }, 
            "responseElements": "<responseOmitted>", 
            "sourceIPAddress": "54.249.240.229", 
            "userAgent": "aws-sdk-php/1.6.2 PHP/5.4.20 curl/7.19.7 openssl/1.0.0-fips", 
            "userIdentity": {
                "accessKeyId": "xxxxxxxxxxxxxxxxxx", 
                "accountId": "821635308497", 
                "arn": "arn:aws:iam::821635308497:root", 
                "principalId": "821635308497", 
                "type": "Root"
            }
        }
    ]
}


このように呼び出したAPIの内容が表示されるため、jqやログ解析ツールなどと併用してAWSアカウントに対してどういった操作があったかを簡潔に知ることが可能になります。

以上です。

2013年9月26日木曜日

EBSってなんじゃ?(cryptsetup + S3 + IAM Roleでディスク暗号化鍵をS3で管理)

EBSの暗号化の方法のひとつとしてcryptsetupという技術があります。cryptsetupはパスフレーズで暗号化されたディスクにアクセス(マウント)しますが、今回は鍵ファイルを使用してアクセスする手順をまとめました。
また、鍵ファイルを同じインスタンス内に置かないためにS3から鍵ファイルを取得し、マウントした後に削除するようにします。

それでは手順を追ってみます。

まずcryptsetupで/dev/xvdfにアタッチされたEBSボリュームの暗号化を行い、マウントします。
# yum install -y cryptsetup
# yum install xfsprogs -y
# cryptsetup luksFormat -c aes -h sha256 /dev/xvdf
# cryptsetup luksOpen /dev/xvdf encrypted
# ls -l /dev/mapper/
# mkfs.xfs /dev/mapper/luks
# mkdir /mnt/vol
# mount -t xfs /dev/mapper/luks /mnt/vol
# mkdir /mnt/vol/data


次に鍵ファイルを作成し、cryptsetupに登録します
# dd if=/dev/urandom of=/root/encrypted_key bs=1 count=1024
# cryptsetup luksAddKey /dev/xvdf /root/encrypted_key


S3バケットを作成し、そこへ鍵を保管します




自動で暗号化+マウントするために起動スクリプトをつくり、S3から鍵ファイルを取得しマウントするようにします。終わったらAMIを作成します。
# cat /etc/init.d/cryptmount
-----------
#!/bin/sh
#
#
# chkconfig: 345 60 16

aws s3 get-object --region ap-northeast-1 --bucket luks-key --key xvdf_luks_key /boot/xvdf_luks_key
cryptsetup luksOpen /dev/xvdf luks --key-file /boot/xvdf_luks_key
mount -t xfs /dev/mapper/luks /mnt/vol
rm -rf /boot/xvdf_luks_key
-----------

# rm -rf /root/xvdf_luks_key



IAM Roleを作成し、S3へのアクセスを許可します



作成したAMIの起動時にIAM Roleを指定します




これで、鍵がない状態ではEBSをマウントすることができなくなりました。
以上です。

2013年6月15日土曜日

Spiderってなんじゃ?(SUBPARTITIONで負荷分散しながらストレージ容量を無限追加)

Spiderシリーズです。Spiderではパーティションを使ってシャーディングしますが、パーティションの定義によって分散の特性が変わります。

1. サロゲートキーなどのHASHパーティション
  • データは均等に分散
  • 更新負荷は均等に分散
  • 増設はデータ全体の再ハッシュが必要

2. 日付の年月などのHASHパーティション
  • データは順次格納
  • 更新負荷は常に1データノードにのみかかる
  • 増設は新しい年月を追加

3. サロゲートキーなどのRANGEパーティション
  • データは順次格納
  • 更新負荷は常に1データノードにのみかかる
  • 増設は新しいキー範囲を追加

理想は均等に負荷分散しながら、データ容量を後から増やせるのがベストですが、1,2,3とも負荷分散と容量追加のどちらかにしか親和性がありません。
そこで、作者の斯波さん(@kentokushiba)に伺ったところ、 SpiderでもSUBPARTITIONが使えるとのことでRANGEとHASHを組み合わせることで実現できると教えて頂きました。



早速やってみます。
CREATE TABLE `spdoc` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `created_at` datetime DEFAULT NULL,
  `content` text,
  `docname` varchar(255),
  INDEX(created_at) ,
  PRIMARY KEY (`id`)
) ENGINE=SPIDER DEFAULT CHARSET=utf8 CONNECTION=' table "spdoc", user "xxxxxxxx", password "yyyyyyyyy" auto_increment_mode "1" '
PARTITION BY RANGE (id)
SUBPARTITION BY HASH(id) (
  PARTITION p1 VALUES LESS THAN (1000000) (
    SUBPARTITION db1 COMMENT = 'host "10.0.1.141", port "3306"' ENGINE = SPIDER,
    SUBPARTITION db2 COMMENT = 'host "10.0.1.142", port "3306"' ENGINE = SPIDER
  ),
  PARTITION p2 VALUES LESS THAN (2000000) (
    SUBPARTITION db3 COMMENT = 'host "10.0.1.143", port "3306"' ENGINE = SPIDER,
    SUBPARTITION db4 COMMENT = 'host "10.0.1.144", port "3306"' ENGINE = SPIDER
  )
);


まずRANGEパーティションをidの値の範囲で定義します。試しに1000000,2000000に設定します。
次に、SUBPARTITIONでidでHASHパーティションを定義し、ここにデーターノードを指定します。

すると、idが1000000以下のデータについてdb1, db2のデータノードに均等に分散され、
1000000〜2000000の範囲では、db3, db4に均等に分散されます。

idが2000000を超えそうになった場合は、後から3つめのパーティションを追加することができます。



ALTER TABLE spdoc ADD PARTITION
(
  PARTITION p3 VALUES LESS THAN (3000000) (
    SUBPARTITION db5 COMMENT = 'host "10.0.1.145", port "3306"' ENGINE = SPIDER,
    SUBPARTITION db6 COMMENT = 'host "10.0.1.146", port "3306"' ENGINE = SPIDER
  )
);

これでデータ容量はほぼ無限に増やすことができます。

パーティションごとにSUBPARTITIONの数を変えることはできないので、負荷の分散数は同じデータノード数で賄わなければいけませんが、その場合はパーティションごとのデータノードのインスタンスタイプやPIOPSを増やすことで処理能力をアップさせることで対応できるかと思います。

斯波さんありがとうございました!

以上です。

2013年5月28日火曜日

Fluentdってなんじゃ?(設定ファイルをリモートから読み込む)

fluentdのドキュメントに以下のような記載がありました。

Configuration File - (3) Include Directive
# absolute path
include /path/to/config.conf

# if using a relative path, the directive will use 
# the dirname of this config file to expand the path
include extra.conf

# glob match pattern
include config.d/*.conf

# http
include http://example.com/fluent.conf

最後の行、、、HTTPから設定ファイルを取得してインクルードできるみたいです。

ということで試してみました。

common.conf
<source>
  type tail
  format apache
  pos_file /var/log/td-agent/httpd-access.log.pos
  path /var/log/httpd/access_log
  tag apache.access
</source>

<match apache.access>
  type s3

  aws_key_id xxxxxxxxxxxxxxxxxxx 
  aws_sec_key yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
  s3_bucket hoge-bucket
  path logs/
  buffer_path /var/log/fluent/s3
  flush_interval 10s 
</match>

これをS3にアップロードしてWEBホスティングしてみます。


そして、EC2では以下のように設定します。

/etc/td-agent/td-agent.conf
include http://s3-ap-northeast-1.amazonaws.com/hoge-bucket/conf/fluentd/common.conf
# /etc/init.d/td-agent start


httpdのコンテンツにアクセスしてしばらくたつと、、、


無事出力されていました!

これの何がいいかというと、

  • 各サーバーに共通の設定ファイルなどを外部化しておき、一括変更をかけられる (再起動は必要ですが、定期的に再起動をかけるようにしておくという手もあります)
  • いっそ全設定を外出しして完全に外部で管理する

などが可能になります。

以上です。

2013年5月25日土曜日

spiderってなんじゃ?(spider自身の冗長化)

いままではSpiderがひとつに対して、データノードが複数ある構成をとってきましたが、実戦ではSpiderが単一障害点になるため好ましくありません。ここではSpider自身の冗長化を行なってみます。

いままでのSpider x 1 + mroonga x 2 の構成に対してデータを入れ込むためのappノードを用意します。

SpoderのSPOF





mroonga

 CREATE TABLE doc (
  id INT PRIMARY KEY AUTO_INCREMENT,
  created_at datetime,
  content text,
  FULLTEXT INDEX (content)
 ) ENGINE = mroonga; 


spider

 CREATE TABLE doc (
  id BIGINT PRIMARY KEY AUTO_INCREMENT,
  created_at datetime,
  content text,
  FULLTEXT INDEX (content)
 ) ENGINE = spider CHARSET utf8
 CONNECTION ' table "doc", user "memorycraft_user", password "memorycraft_pass" '
 PARTITION BY KEY() (
  PARTITION db1 comment 'host "10.0.1.136", port "3306"',
  PARTITION db2 comment 'host "10.0.1.137", port "3306"'
 ); 

app

# yum install -y php php-mbstring php-pdo php-mysql
$ vim links.php
<?php

//ランダム文字列
function getRandomString($size = 8){
    $chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
    mt_srand();
    $char = "";
    for($i = 0; $i < $size; $i++) {
        $char .= $chars{mt_rand(0, strlen($chars) - 1)};
    }
    return $char;
}

//XMLテンプレート
$tmpl =<<< END
<?xml version="1.0" encoding="UTF-8" ?>
<data>
  <info>
    <datetime>%s</datetime>
    <address>%s</address>
    <content>%s</content>
  </info>
</data>
END;

//XMLテンプレートに流しこむcontent要素の内容を読み込む
$contents = array();
for($i=0;$i<10;$i++){
  $contents[] = file_get_contents(dirname(__FILE__)."/dummy".$i.".txt");
}

//spiderホストは引数から取得する
$host = $argv[1];
//タイムゾーン
date_default_timezone_set('Asia/Tokyo');

//無限ループ
while(true){
  //XMLテンプレートに各要素を割り当てる
  $xml = sprintf($tmpl, date('Y/m/d H:i:s'), getRandomString(), $contents[rand(0,9)]);
  echo ".";
  //DB接続
  $pdo = new PDO(
    "mysql:dbname=memorycraft;host=$host;",
    "memorycraft_user",
    "memorycraft_pass",
    array(
     PDO::MYSQL_ATTR_INIT_COMMAND => "SET CHARACTER SET `utf8`"
   ));
  $pdo->query("SET NAMES utf8;");
  //投入
  $st = $pdo->prepare("INSERT INTO doc VALUES(NULL,NOW(),?);");
  $rslt = $st->execute(array($xml));
}
?>
実行します
$ php links.php 10.0.1.20
.......................................


Spiderノードで投入されていることを確認します。

mysql> select id, created_at from doc limit 10;
+------+---------------------+
| id   | created_at          |
+------+---------------------+
|    1 | 2013-05-25 11:27:47 | |    2 | 2013-05-25 11:27:47 | |    3 | 2013-05-25 11:27:47 | |    4 | 2013-05-25 11:27:47 | |    5 | 2013-05-25 11:27:47 | |    6 | 2013-05-25 11:27:48 | |    7 | 2013-05-25 11:27:48 | |    8 | 2013-05-25 11:27:48 | |    9 | 2013-05-25 11:27:48 | |   10 | 2013-05-25 11:27:48 | +------+---------------------+ 10 rows in set (0.06 sec)


これで、spider x 1 + mroonga x 2 + app x 1になりました。


Spiderの冗長化


次は、Spider x 2 + mroonga x 2 + internal ELB + app x 4 にしてみます。




Spiderノードをもう1台(spider2)追加します。

テーブルはspider1と同じ内容にします。
/etc/my.cnfでauto_increment_increment = 100でoffsetを1,2でずらします。
これについては、別の記事で触れています。

mysqlってなんじゃ?(マルチマスタでauto_increment)
http://memocra.blogspot.jp/2013/05/mysqlautoincrement.html

また、spiderを複数にした場合、appからの接続は1つにまとめます。
Proxy系のソフトウェアを入れるのも1つの方法ですが、ここでは内部ELBを利用してみます。

ヘルスチェックは3306ではなく別のポート経由でMySQL監視プロセスに接続スべきですが、ここではとりいそぎhttpdを立ちあげ80番に対してヘルスチェックします。


それではappで投入プログラムを実行します。
対象のホストは内部ELBのDNSNameになります。

$ php links.php internal-spider-2040122974.ap-northeast-1.elb.amazonaws.com
........................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................


spider1でデータを見てみます。

mysql> select id, created_at from doc limit 20;
+------+---------------------+
| id   | created_at          |
+------+---------------------+
|    1 | 2013-05-25 11:27:47 |
|  101 | 2013-05-25 11:27:47 |
|  201 | 2013-05-25 11:27:47 |
|  202 | 2013-05-25 11:27:47 |
|  601 | 2013-05-25 11:27:47 |
|  701 | 2013-05-25 11:27:48 |
| 1101 | 2013-05-25 11:27:48 |
| 1201 | 2013-05-25 11:27:48 |
| 1601 | 2013-05-25 11:27:48 |
| 1701 | 2013-05-25 11:27:48 |
| 2101 | 2013-05-25 11:27:49 |
| 2201 | 2013-05-25 11:27:49 |
| 2301 | 2013-05-25 11:27:49 |
| 2601 | 2013-05-25 11:27:49 |
| 2701 | 2013-05-25 11:27:49 |
| 2802 | 2013-05-25 11:27:49 |
| 3101 | 2013-05-25 11:27:49 |
| 3201 | 2013-05-25 11:27:49 |
| 3301 | 2013-05-25 11:27:49 |
| 3601 | 2013-05-25 11:27:50 |
+------+---------------------+

mysql> select count(*) from doc;
+----------+
| count(*) |
+----------+
|   111433 |
+----------+


順調に入っていってるみたいです。
あとはSpiderノードを必要に応じて追加していき、my.cnfでoffsetをずらして起動するだけです。
offsetずらしの処理はcloud-initなどで行うと、AutoScalingすることもできそうです。

今回は以上です。




mysqlってなんじゃ?(マルチマスタでauto_increment)

MySQLでマルチマスタやシャーディングなどを行った場合、auto_incrementを使いたい場合があります。

たとえばspiderだとすると、複数台のSpiderノードを経由してデータが投入されると、データノードでauto_increment値が競合してしまうことがあるため、auto_incrementのオフセットをspiderノードごとにずらします。

spider1
/etc/my.cnf
------
[mysqld]
auto_increment_increment = 100
auto_increment_offset = 1


spider2
/etc/my.cnf
------
[mysqld]
auto_increment_increment = 100
auto_increment_offset = 2


auto_increment_incrementはカウントアップされる単位です。上の例では100ずつ増えていきます。
auto_increment_offsetはauto_incrementのスタート時点の値です。

これによって、各spiderノードのauto_increment値は以下のように増えていきます。

spider1
1
101
201
301
401
.....
123401

spider2
2
102
202
302
402
....
123402

つまり3桁目以上は全体で同じ値をカウントアップしていきますが、下2桁は重複しなくなります。

これを元に投入されたデータはspider上で抽出すると以下のようになります。

1
2
101
102
201
202
301
302
401
402
.....
123401
123402

随分値を飛ばしているように感じますが、ビットずらしのような感覚でとらえれば違和感はなくなると思います。そして、この場合は、Spiderノードを99台まで増やしても値が重複しません。
将来的に何台まで増えるかわからない場合はこのようにするとよさそうです。

以上です。

2013年5月22日水曜日

jqってなんじゃ?(containsで簡単APIテスト)

jqはsedのjson版のようなコマンドラインツールで、jsonデータをフィルタしたり切り取ったり整形したりが簡単にできます。

aws界隈ではaws cliがjsonを返すことで話題になったようで、以下の記事などで紹介されています。
suz-lab : EC2の価格のJSONを"jq"でいじってみた

jqの1.2では、containsという関数が追加されたようで、jsonに特定の要素が含まれているかどうかの真偽値を返すようです。

これをつかって簡単なAPIテストが出来るんじゃないかと思い、試してみました。

こんな感じのJSONがあったとします。
$ curl -s http://aws.amazon.com/jp/ec2/pricing/pricing-on-demand-instances.json | jq '.config.regions[].region'
"us-east"
"us-west-2"
"us-west"
"eu-ireland"
"apac-sin"
"apac-tokyo"
"apac-syd"
"sa-east-1"

containsで指定(config.regions[].region)の要素にap-northeast-1があるかどうかを検査します。
$ curl -s http://aws.amazon.com/jp/ec2/pricing/pricing-on-demand-instances.json | jq 'contains({config: {regions: [{region: "ap-northeast-1"}]}})'

false

無いようです。

ではapac-tokyoがあるかどうかを検査します。
$ curl -s http://aws.amazon.com/jp/ec2/pricing/pricing-on-demand-instances.json | jq 'contains({config: {regions: [{region: "apac-tokyo"}]}})'

true

ありました!


わざわざ導入に手間がかかるツールを使うまでもない簡単な検査であれば、ワンライナーで終わるコマンドラインをシェルにいくつか書いてまとめて実行するというのも手段としてありかもしれません。


以上です。

2013年5月20日月曜日

mroongaってなんじゃ?(Spiderで分散全文検索)

前回の記事でmroongaを使用しましたが、全文検索のデータは大きくなりがちなので、Spiderを利用して書き込み負荷やストレージ容量を分散してみます。



設定


mroongaデータノード

前回と同じようにmroongaテーブルを作りますが、Spiderノードからアクセスするためにユーザー権限を登録します。今回はホストは特に絞りません。
mysql> GRANT ALL PRIVILEGES ON *.* TO 'memorycraft_user'@localhost IDENTIFIED BY 'memorycraft_pass';
mysql> GRANT ALL PRIVILEGES ON *.* TO 'memorycraft_user'@'%' IDENTIFIED BY 'memorycraft_pass';

新しくデータベースをつくり、blogテーブルを作ります。
mysql> CREATE DATABASE memorycraft;
mysql> use memorycraft;
mysql> 
mysql> CREATE TABLE blog (
mysql>  id INT PRIMARY KEY AUTO_INCREMENT,
mysql>  content text,
mysql>  FULLTEXT INDEX (content)
mysql> ) ENGINE = mroonga DEFAULT CHARSET utf8;



Spiderノード

Spiderのインストールは以前の記事のとおりです。
同じように権限を登録し、データベースを作成します。

mysql> GRANT ALL PRIVILEGES ON *.* TO 'memorycraft_user'@localhost IDENTIFIED BY 'memorycraft_pass';
mysql> GRANT ALL PRIVILEGES ON *.* TO 'memorycraft_user'@'%' IDENTIFIED BY 'memorycraft_pass';
mysql> 
mysql> CREATE DATABASE memorycraft;
mysql> use memorycraft;


Spiderテーブルを作成します。
ここでは、2つのノードにKEYパーティションで分散してみます。
mysql> CREATE TABLE blog (
mysql>  id INT PRIMARY KEY AUTO_INCREMENT,
mysql>  content text,
mysql>  FULLTEXT INDEX (content)
mysql> ) ENGINE = Spider DEFAULT CHARSET utf8
mysql> CONNECTION ' table "blog", user "memorycraft_user", password "memorycraft_pass" '
mysql> PARTITION BY KEY() (
mysql>  PARTITION db1 comment 'host "10.0.1.169", port "3306"',
mysql>  PARTITION db2 comment 'host "10.0.1.170", port "3306"'
mysql> );


これで設定は完了です。




確認



次にデータを登録してみます。
登録の仕方は前回と同様です。


Spiderノード


mysql> INSERT INTO blog (content) VALUES ("前回はpsqlでRedshiftを利用してみましたが、通常データウェアハウス(DWH)というのはBI(Buisiness Intelligence)ツールを利用することが多いようです。

エンジニアの観点からすると複雑なSQLを書くだけでいいかもしれませんが、経営者などの立場からするとBIツールなどを使って画面上でポチポチやって分析できることが重要なようです。
今回はそのBIツールの中で、Redshiftにいち早く対応しているJaspersoftという製品を使ってRedshiftに接続してみたいと思います。");

mysql> INSERT INTO blog (content) VALUES ("久しぶりにnagiosの話題です。
アプリログ内容の監視の仕方には様々な要件がありますが、特定の間隔でログを監視し「error」などの文言があったらアラートする。などがよくあるケースで、以前の記事にも書きました。
その逆に、例えば、多量のアクセスがあるにも関わらず頻繁に出力されるはずの重要なキーワードがでていない場合は、不測の事態がおこっているかも知れません。
今回は特定の間隔でログを監視し、その中にキーワードが含まれていなかったらアラートする
というものです。
ではやってみます。");

mysql> INSERT INTO blog (content) VALUES ("S3のwebホスティングで、ログ出力の設定をしていた場合、ログファイルが大量に出力されます。
ログの記録時間は標準時で出力されていてわかりづらいです。
今回はEMRのHiveを利用して、日本時間の0時〜翌日の0時までのログを1ファイルにまとめてみたいと思います。");

mysql > INSERT INTO blog (content) VALUES ("久しぶりのSpiderの話題です。
今回はtpcc-mysqlというベンチマークツールを使ってSpiderのベンチマークをとってみました。
mysqlにかぎらずDBのベンチマークツールの多くは、TPCという団体の定めたベンチマーク仕様に基いて実装されていて、トランザクションやアクセスなどのDB用途によっていくつかのベンチマークタイプに分かれていて、OLTP向けのTPC-Eや意思決定システム向けのTPC-HやTPC-DSなど色々あるようです。");


mysql> select id,MATCH(content) AGAINST("ログ アクセス" IN BOOLEAN MODE) as score from blog WHERE MATCH(content) AGAINST("ログ アクセス" IN BOOLEAN MODE)
    -> ;
+----+-------+
| id | score |
+----+-------+
|  3 |     4 |
|  2 |     4 |
|  4 |     1 |
+----+-------+
3 rows in set (0.01 sec)

mysql> select id, MATCH(content) AGAINST("ログ" IN BOOLEAN MODE) from blog WHERE MATCH(content) AGAINST("ログ" IN BOOLEAN MODE) ORDER BY  MATCH(content) AGAINST("ログ" IN BOOLEAN MODE) DESC;
+----+--------------------------------------------------+
| id | MATCH(content) AGAINST("ログ" IN BOOLEAN MODE)   |
+----+--------------------------------------------------+
|  3 |                                                4 |
|  2 |                                                3 |
+----+--------------------------------------------------+
2 rows in set (0.01 sec)




データノード


mysql> select id from blog;
+----+
| id |
+----+
|  1 |
|  3 |
+----+
2 rows in set (0.00 sec)


mysql> select id from blog;
+----+
| id |
+----+
|  2 |
|  4 |
+----+
2 rows in set (0.00 sec)


うまく分散されているようです。
これで沢山データがあっても分散されます。

以上です。

2013年5月17日金曜日

mroongaってなんじゃ? (mroongaでmysql全文検索)

mysqlで全文検索といえばTritonnが有名でした。現在ではmysqlで全文検索をするときにmroongaというプロダクトが有望なようです。



Tritonnとの違いは以下のサイトが詳しいです。
「全文検索エンジンgroongaを囲む夕べ #1」参加メモ

簡単にいうと、

  • Tritonn:Sennaという全文検索エンジンをMySQL用に組み込んだものです。MyISAMを前提に組み込まれていることもあり、更新が遅く、また更新時の検索が重くなるなど、ベストなパフォーマンスが得られにくかったようです。
  • mroongagroongaという全文検索エンジンをMySQL用にストレージエンジンにしたものです。MyISAMに依存しないため更新時の参照に影響を与えず、更新も速いようです。



では、さっそく導入してみます。
環境はEC2上のCentOS-6.4.1(suz-lab AMI)です。


groongaとmecabのインストール


まずgroongaとmecabをインストールします。
yumリポジトリを登録し、必要なものをインストールします。
# rpm -ivh http://packages.groonga.org/centos/groonga-release-1.1.0-1.noarch.rpm
# yum makecache
# yum install -y mecab
# yum install -y mecab-ipadic
# yum install -y groonga
# yum install -y groonga-tokenizer-mecab
# yum install -y wget


mysqlのインストール


yumを使った場合mroongaはmysql5.1を前提とするようです。ここではmysql5.5を使用したいのでソースからインストールします。
# cd /usr/local/src/
# wget http://dev.mysql.com/get/Downloads/MySQL-5.5/mysql-5.5.31.tar.gz/from/http://cdn.mysql.com/
# tar xzvf mysql-5.5.31.tar.gz
# cd mysql-5.5.31/
# yum install cmake
# yum install -y ncurses
# yum install -y ncurses-devel
# yum install -y gcc-c++
# yum install -y bison
# cmake . -DCMAKE_INSTALL_PREFIX=/usr/local/mysql -DDEFAULT_CHARSET=utf8 -DDEFAULT_COLLATION=utf8_general_ci -DENABLED_LOCAL_INFILE=true -DWITH_INNOBASE_STORAGE_ENGINE=1 -DWITH_EXTRA_CHARSETS=all -DWITH_READLINE=OFF
# make
# make install


mysql の設定


適当に設定ファイルを用意します。
# cd /usr/local/src/
# mv /etc/my.cnf /etc/my.cnf.org
# cp /usr/local/mysql/support-files/my-large.cnf /etc/my.cnf
# vim /etc/my.cnf
# The following options will be passed to all MySQL clients
[client]
#password = your_password
port  = 3306
socket  = /tmp/mysql.sock
default-character-set = utf8
# Here follows entries for some specific programs

# The MySQL server
[mysqld]
port  = 3306
socket  = /tmp/mysql.sock
skip-external-locking
key_buffer_size = 256M
max_allowed_packet = 1M
table_open_cache = 256
sort_buffer_size = 1M
read_buffer_size = 1M
read_rnd_buffer_size = 4M
myisam_sort_buffer_size = 64M
thread_cache_size = 8
query_cache_size= 16M
# Try number of CPU's*2 for thread_concurrency
thread_concurrency = 8

# Don't listen on a TCP/IP port at all. This can be a security enhancement,
# if all processes that need to connect to mysqld run on the same host.
# All interaction with mysqld must be made via Unix sockets or named pipes.
# Note that using this option without enabling named pipes on Windows
# (via the "enable-named-pipe" option) will render mysqld useless!
#
#skip-networking

# Replication Master Server (default)
# binary logging is required for replication
log-bin=mysql-bin

# binary logging format - mixed recommended
binlog_format=mixed


# required unique id between 1 and 2^32 - 1
# defaults to 1 if master-host is not set
# but will not function as a master if omitted
server-id = 1

# Uncomment the following if you are using InnoDB tables
innodb_data_home_dir = /usr/local/mysql/data
innodb_data_file_path = ibdata1:10M:autoextend
innodb_log_group_home_dir = /usr/local/mysql/data
# You can set .._buffer_pool_size up to 50 - 80 %
# of RAM but beware of setting memory usage too high
innodb_buffer_pool_size = 256M
innodb_additional_mem_pool_size = 20M
# Set .._log_file_size to 25 % of buffer pool size
innodb_log_file_size = 64M
innodb_log_buffer_size = 8M
innodb_flush_log_at_trx_commit = 1
innodb_lock_wait_timeout = 50

innodb_file_per_table
innodb_fast_shutdown = 0

[mysqldump]
quick
max_allowed_packet = 16M

[mysql]
no-auto-rehash
# Remove the next comment character if you are not familiar with SQL
#safe-updates

[myisamchk]
key_buffer_size = 128M
sort_buffer_size = 128M
read_buffer = 2M
write_buffer = 2M

[mysqlhotcopy]
interactive-timeout



mysqlの起動


mysqlユーザーの作成と、起動スクリプトのコピー、DBの初期化を行い、mysqlにパスを通して起動します。
# groupadd mysql
# useradd mysql -g mysql -s /sbin/nologin
# cd /usr/local/mysql/
# /usr/local/mysql/scripts/mysql_install_db --user=mysql --basedir=/usr/local/mysql --datadir=/usr/local/mysql/data
# vim /root/.bash_profile
# .bash_profile

# Get the aliases and functions
if [ -f ~/.bashrc ]; then
 . ~/.bashrc
fi

# User specific environment and startup programs

PATH=$PATH:$HOME/bin:/usr/local/mysql/bin

export PATH
# source ~/.bash_profile
# cp /usr/local/mysql/support-files/mysql.server /etc/init.d/mysql
# /etc/init.d/mysql start



mroongaのインストール


ここでmroongaをソースからビルドします。
configureオプションで、mysqlのソースを指定します。
# yum install -y automake
# cd /usr/local/src
# curl -OL http://packages.groonga.org/source/mroonga/mroonga-3.03.tar.gz
# tar xzvf mroonga-3.03.tar.gz
# cd mroonga-3.03/
# yum install groonga-devel
# ./configure --with-mysql-source=/usr/local/src/mysql-5.5.31 --with-mysql-config=/usr/local/mysql/bin/mysql_config
# make
# make install



mysqlへのmroongaエンジンのインストール


プラグインのインストールを行い、いくつかの関数を登録します。
mysql -u root
mysql> INSTALL PLUGIN mroonga SONAME 'ha_mroonga.so';
mysql> show engines;
+--------------------+---------+------------------------------------------------------------+--------------+------+------------+
| Engine             | Support | Comment                                                    | Transactions | XA   | Savepoints |
+--------------------+---------+------------------------------------------------------------+--------------+------+------------+
| MyISAM             | YES     | MyISAM storage engine                                      | NO           | NO   | NO         |
| PERFORMANCE_SCHEMA | YES     | Performance Schema                                         | NO           | NO   | NO         |
| CSV                | YES     | CSV storage engine                                         | NO           | NO   | NO         |
| InnoDB             | DEFAULT | Supports transactions, row-level locking, and foreign keys | YES          | YES  | YES        |
| MRG_MYISAM         | YES     | Collection of identical MyISAM tables                      | NO           | NO   | NO         |
| MEMORY             | YES     | Hash based, stored in memory, useful for temporary tables  | NO           | NO   | NO         |
| mroonga            | YES     | CJK-ready fulltext search, column store                    | NO           | NO   | NO         |
+--------------------+---------+------------------------------------------------------------+--------------+------+------------+
7 rows in set (0.00 sec)

mysql> CREATE FUNCTION last_insert_grn_id RETURNS INTEGER SONAME 'ha_mroonga.so';
mysql> CREATE FUNCTION mroonga_snippet RETURNS STRING SONAME 'ha_mroonga.so';
mysql> CREATE FUNCTION mroonga_command RETURNS STRING SONAME 'ha_mroonga.so';


これでひと通りのインストールが完了しました。
show enginesにmroongaが含まれていれば成功です。



確認


それでは簡単に触ってみます。
testデータベースにblogテーブルをつくってみます。
ここでは自分のグログ記事の抜粋をtextカラムにいれておきます。

mysql> use test;
mysql> CREATE TABLE blog (
mysql>   id INT PRIMARY KEY AUTO_INCREMENT,
mysql>   content text,
mysql>   FULLTEXT INDEX (content)
mysql> ) ENGINE = mroonga DEFAULT CHARSET utf8;

mysql> INSERT INTO blog (content) VALUES ("前回はpsqlでRedshiftを利用してみましたが、通常データウェアハウス(DWH)というのはBI(Buisiness Intelligence)ツールを利用することが多いようです。
エンジニアの観点からすると複雑なSQLを書くだけでいいかもしれませんが、経営者などの立場からするとBIツールなどを使って画面上でポチポチやって分析できることが重要なようです。
今回はそのBIツールの中で、Redshiftにいち早く対応しているJaspersoftという製品を使ってRedshiftに接続してみたいと思います。");

mysql> INSERT INTO blog (content) VALUES ("久しぶりにnagiosの話題です。
アプリログ内容の監視の仕方には様々な要件がありますが、特定の間隔でログを監視し「error」などの文言があったらアラートする。などがよくあるケースで、以前の記事にも書きました。
その逆に、例えば、多量のアクセスがあるにも関わらず頻繁に出力されるはずの重要なキーワードがでていない場合は、不測の事態がおこっているかも知れません。
今回は特定の間隔でログを監視し、その中にキーワードが含まれていなかったらアラートする
というものです。
ではやってみます。");

mysql> INSERT INTO blog (content) VALUES ("S3のwebホスティングで、ログ出力の設定をしていた場合、ログファイルが大量に出力されます。
ログの記録時間は標準時で出力されていてわかりづらいです。
今回はEMRのHiveを利用して、日本時間の0時〜翌日の0時までのログを1ファイルにまとめてみたいと思います。");

mysql > INSERT INTO blog (content) VALUES ("久しぶりのSpiderの話題です。
今回はtpcc-mysqlというベンチマークツールを使ってSpiderのベンチマークをとってみました。
mysqlにかぎらずDBのベンチマークツールの多くは、TPCという団体の定めたベンチマーク仕様に基いて実装されていて、トランザクションやアクセスなどのDB用途によっていくつかのベンチマークタイプに分かれていて、OLTP向けのTPC-Eや意思決定システム向けのTPC-HやTPC-DSなど色々あるようです。");



そして、「ログ」を含むレコードを探してみます。
mysql> SELECT * FROM blog WHERE MATCH(content) AGAINST("ログ");
mysql> SELECT * FROM blog WHERE MATCH(content) AGAINST("ログ") \G;
*************************** 1. row ***************************
     id: 2
content: 久しぶりにnagiosの話題です。アプリログ内容の監視の仕方には様々な要件がありますが、特定の間隔でログを監視し「error」などの文言があったらアラートする。などがよくあるケースで、以前の記事にも書きました。その逆に、例えば、多量のアクセスがあるにも関わらず頻繁に出力されるはずの重要なキーワードがでていない場合は、不測の事態がおこっているかも知れません。今回は特定の間隔でログを監視し、その中にキーワードが含まれていなかったらアラートする
というものです。

ではやってみます。
*************************** 2. row ***************************
     id: 3
content: S3のwebホスティングで、ログ出力の設定をしていた場合、ログファイルが大量に出力されます。

ログの記録時間は標準時で出力されていてわかりづらいです。

今回はEMRのHiveを利用して、日本時間の0時〜翌日の0時までのログを1ファイルにまとめてみたいと思います。
2 rows in set (0.00 sec)

ERROR:
No query specified

mysql>


検索が成功したようです。
次に検索スコアを出してみます。デフォルトでは自然言語検索によるスコアのようです。

mysql> select id,MATCH(content) AGAINST("ログ") as score from blog;
+----+---------+
| id | score   |
+----+---------+
|  1 |       0 |
|  2 |  786435 |
|  3 | 1048580 |
|  4 |       0 |
+----+---------+
4 rows in set (0.00 sec)



また、「ログ」と「アクセス」が含まれるレコードの検索をしてみます。

両方含まれるもの
mysql> select id,MATCH(content) AGAINST("+ログ +アクセス" IN BOOLEAN MODE) as score from blog WHERE MATCH(content) AGAINST("+ログ +アクセス" IN BOOLEAN MODE);
+----+-------+
| id | score |
+----+-------+
|  2 |     4 |
+----+-------+
1 row in set (0.00 sec)

どちらかが含まれるもの
mysql> select id,MATCH(content) AGAINST("ログ アクセス" IN BOOLEAN MODE) as score from blog WHERE MATCH(content) AGAINST("ログ アクセス" IN BOOLEAN MODE)
+----+-------+
| id | score |
+----+-------+
|  2 |     4 |
|  3 |     4 |
|  4 |     1 |
+----+-------+
3 rows in set (0.00 sec)



マッチ度がスコアで取得されるので、ソートをすることもできます。
mysql> select id, MATCH(content) AGAINST("ログ" IN BOOLEAN MODE) from blog WHERE MATCH(content) AGAINST("ログ" IN BOOLEAN MODE) ORDER BY  MATCH(content) AGAINST("ログ" IN BOOLEAN MODE) DESC;
+----+--------------------------------------------------+
| id | MATCH(content) AGAINST("ログ" IN BOOLEAN MODE)   |
+----+--------------------------------------------------+
|  3 |                                                4 |
|  2 |                                                3 |
+----+--------------------------------------------------+
2 rows in set (0.00 sec)



ざっとさわりでしたが、mroongaを試してみました。
パフォーマンスやクエリの用法などはまたの機会に調べてみます。

またMySQLでは5.6からInnoDB FTSというInnoDBでの全文検索が可能なようですが、こちらは参照は速いものの更新はmroongaの方が速いという結果も出ているようなので、こちらも機会があれば調べてみたいと思います。
http://www.slideshare.net/y-ken/my-sql-56innodb-fts

以上です。


2013年5月15日水曜日

logrotateってなんじゃ?(pigzで高速ローテート)

大量アクセスがあってログファイルが巨大であったりすると、日々のlogrotateが重くなります。

対象ディレクトリ以下に大量のファイルがある場合も時間がかかることがありますが、その場合はローテションの世代数を減らしたり、対象ディレクトリから定期的に退避すれば済むため、ネックになるのは主に圧縮によるものだと思います。

デフォルトではgzipが利用されますが、別の圧縮プログラムを利用することもできます。
圧縮プログラムの選定ですが、基本的に圧縮率の高いプログラムは時間がかかりますし、高速なものは圧縮率が低いものが一般的ですが、例外があります。

gzipなどはシングルコアしか使用しませんが、マルチコアで分散処理する圧縮プログラムだとおなじ圧縮フォーマットでも格段に速くなります。

今回は、その例としてpigzを利用してみます。
pigzは、gzipフォーマットの圧縮をマルチコアで行う高速gzipとして動作します。
pigzで圧縮したファイルはgunzipで解凍できるので、pigzの無い環境にもファイルを持っていくことができます。

それでは早速触ってみます。


複数のCPUがあるマシンでないと意味が無いので、コア数を確認します。
# cat /proc/cpuinfo | grep processor
processor : 0
processor : 1
processor : 2
processor : 3
processor : 4
processor : 5
processor : 6
processor : 7

今回はc1.xlargeですので、8コアです。


pigzをダウンロードして、ビルドします。
cd /usr/local/src/
curl -OL http://www.zlib.net/pigz/pigz-2.3.tar.gz
tar xzvf pigz-2.3.tar.gz
cd pigz-2.3/
make

cc -o pigz pigz.o yarn.o zopfli/deflate.o zopfli/blocksplitter.o zopfli/tree.o zopfli/lz77.o zopfli/cache.o zopfli/hash.o zopfli/util.o zopfli/squeeze.o zopfli/katajainen.o -lpthread -lz
zopfli/tree.o: In function `CalculateEntropy':
tree.c:(.text+0x75): undefined reference to `log'
tree.c:(.text+0x11a): undefined reference to `log'
tree.c:(.text+0x16b): undefined reference to `log'
collect2: ld はステータス 1 で終了しました
make: *** [pigz] エラー 1


なんかエラーが出ました。リンクが張られていないようなので、Makefileで-lmオプションをつけます。
6c6
<  $(CC) -o pigz $^ -lpthread -lz
---
>  $(CC) -o pigz $^ -lpthread -lz -lm




もう一度ビルドします。
# make
cc -o pigz pigz.o yarn.o zopfli/deflate.o zopfli/blocksplitter.o zopfli/tree.o zopfli/lz77.o zopfli/cache.o zopfli/hash.o zopfli/util.o zopfli/squeeze.o zopfli/katajainen.o -lpthread -lz -lm
ln -f pigz unpigz

# ls -l
合計 584
-rw-r--r-- 1  501 games   2630  5月 16 03:46 2013 Makefile
-rw-r--r-- 1 root root    2626  5月 16 03:46 2013 Makefile.org
-rw-r--r-- 1  501 games   2188  7月 29 05:37 2012 README
-rwxr-xr-x 2 root root  128382  5月 16 03:46 2013 pigz
-rw-r--r-- 1  501 games   4836  3月  4 15:24 2013 pigz.1
-rw-r--r-- 1  501 games 140762  3月  4 16:42 2013 pigz.c
-rw-r--r-- 1 root root  115496  5月 15 04:17 2013 pigz.o
-rw-r--r-- 1  501 games   8758  3月  4 15:24 2013 pigz.pdf
-rw-r--r-- 1  501 games    947  7月 29 05:37 2012 pigz.spec
-rwxr-xr-x 2 root root  128382  5月 16 03:46 2013 unpigz
-rw-r--r-- 1  501 games  11079  1月 14 07:56 2012 yarn.c
-rw-r--r-- 1  501 games   6358  1月 14 07:56 2012 yarn.h
-rw-r--r-- 1 root root   11624  5月 15 04:17 2013 yarn.o
drwxr-xr-x 2  501 games   4096  5月 15 04:17 2013 zopfli



pigzとunpigzが出力されていたら成功です。
この2つのプログラムをパスの通っているところに置きます。
cp pigz /usr/local/bin/
cp unpigz /usr/local/bin/



それでは実際に、動かしてみます。
まず1GBのファイルを作ります。
dd if=/dev/zero of=test.log bs=1024 count=1024000

# ls -lh
合計 1001M
-rw-r--r-- 1 root root 1000M  5月 15 05:02 2013 test.log




そして、gzipでの圧縮/非圧縮と、pigzでの圧縮/非圧縮で時間とCPU使用率を見てみます。
# time gzip test.log

real 0m11.084s
user 0m10.407s
sys 0m0.673s

# top
Cpu0  :  0.0%us,  0.0%sy,  0.0%ni,100.0%id,  0.0%wa,  0.0%hi,  0.0%si,  0.0%st
Cpu1  : 97.3%us,  2.7%sy,  0.0%ni,  0.0%id,  0.0%wa,  0.0%hi,  0.0%si,  0.0%st
Cpu2  :  0.0%us,  0.0%sy,  0.0%ni,100.0%id,  0.0%wa,  0.0%hi,  0.0%si,  0.0%st
Cpu3  :  0.0%us,  0.0%sy,  0.0%ni,100.0%id,  0.0%wa,  0.0%hi,  0.0%si,  0.0%st
Cpu4  :  0.0%us,  0.0%sy,  0.0%ni,100.0%id,  0.0%wa,  0.0%hi,  0.0%si,  0.0%st
Cpu5  :  0.0%us,  0.0%sy,  0.0%ni,100.0%id,  0.0%wa,  0.0%hi,  0.0%si,  0.0%st
Cpu6  :  0.0%us,  0.0%sy,  0.0%ni,100.0%id,  0.0%wa,  0.0%hi,  0.0%si,  0.0%st
Cpu7  :  0.0%us,  0.0%sy,  0.0%ni,100.0%id,  0.0%wa,  0.0%hi,  0.0%si,  0.0%st

------------------------------------------------------------------------------------------
# time gunzip test.log.gz

real 0m23.668s
user 0m7.032s
sys 0m1.174s

# top
Cpu0  : 83.6%us, 15.8%sy,  0.0%ni,  0.0%id,  0.3%wa,  0.0%hi,  0.0%si,  0.3%st
Cpu1  :  0.0%us,  0.0%sy,  0.0%ni,100.0%id,  0.0%wa,  0.0%hi,  0.0%si,  0.0%st
Cpu2  :  0.0%us,  0.0%sy,  0.0%ni,100.0%id,  0.0%wa,  0.0%hi,  0.0%si,  0.0%st
Cpu3  :  0.0%us,  0.0%sy,  0.0%ni,100.0%id,  0.0%wa,  0.0%hi,  0.0%si,  0.0%st
Cpu4  :  0.0%us,  0.3%sy,  0.0%ni, 99.7%id,  0.0%wa,  0.0%hi,  0.0%si,  0.0%st
Cpu5  :  0.0%us,  0.0%sy,  0.0%ni,100.0%id,  0.0%wa,  0.0%hi,  0.0%si,  0.0%st
Cpu6  :  0.0%us,  0.0%sy,  0.0%ni,100.0%id,  0.0%wa,  0.0%hi,  0.0%si,  0.0%st
Cpu7  :  0.0%us,  0.0%sy,  0.0%ni,100.0%id,  0.0%wa,  0.0%hi,  0.0%si,  0.0%st

------------------------------------------------------------------------------------------
# time pigz test.log

real 0m3.025s
user 0m18.781s
sys 0m1.082s

# top
Cpu0  : 51.0%us,  0.7%sy,  0.0%ni, 45.7%id,  0.0%wa,  0.0%hi,  0.0%si,  2.6%st
Cpu1  : 47.8%us,  4.0%sy,  0.0%ni, 48.2%id,  0.0%wa,  0.0%hi,  0.0%si,  0.0%st
Cpu2  : 48.2%us, 11.0%sy,  0.0%ni, 40.5%id,  0.0%wa,  0.0%hi,  0.0%si,  0.3%st
Cpu3  : 50.8%us,  0.3%sy,  0.0%ni, 47.8%id,  0.0%wa,  0.0%hi,  0.0%si,  1.0%st
Cpu4  : 49.0%us,  3.3%sy,  0.0%ni, 47.0%id,  0.0%wa,  0.0%hi,  0.0%si,  0.7%st
Cpu5  : 50.0%us,  2.0%sy,  0.0%ni, 45.7%id,  0.0%wa,  0.0%hi,  0.0%si,  2.3%st
Cpu6  : 50.3%us,  0.7%sy,  0.0%ni, 48.7%id,  0.0%wa,  0.0%hi,  0.0%si,  0.3%st
Cpu7  : 48.3%us,  3.3%sy,  0.0%ni, 48.0%id,  0.0%wa,  0.0%hi,  0.0%si,  0.3%st

------------------------------------------------------------------------------------------
# time unpigz test.log.gz

real 0m5.298s
user 0m6.808s
sys 0m1.128s

# top
Cpu0  : 73.8%us,  3.6%sy,  0.0%ni, 21.5%id,  0.0%wa,  0.0%hi,  0.0%si,  1.1%st
Cpu1  : 48.0%us, 18.5%sy,  0.0%ni, 18.9%id, 13.0%wa,  0.0%hi,  0.0%si,  1.6%st
Cpu2  :  0.0%us,  0.0%sy,  0.0%ni,100.0%id,  0.0%wa,  0.0%hi,  0.0%si,  0.0%st
Cpu3  :  0.0%us,  0.0%sy,  0.0%ni,100.0%id,  0.0%wa,  0.0%hi,  0.0%si,  0.0%st
Cpu4  :  0.0%us,  0.3%sy,  0.0%ni, 99.7%id,  0.0%wa,  0.0%hi,  0.0%si,  0.0%st
Cpu5  :  0.0%us,  0.0%sy,  0.0%ni,100.0%id,  0.0%wa,  0.0%hi,  0.0%si,  0.0%st
Cpu6  :  0.0%us,  0.0%sy,  0.0%ni,100.0%id,  0.0%wa,  0.0%hi,  0.0%si,  0.0%st
Cpu7  :  0.0%us,  0.0%sy,  0.0%ni,100.0%id,  0.0%wa,  0.0%hi,  0.0%si,  0.0%st


おお、だいぶ速くなったようです。複数のCPUが使われていることも分かります。

次はこれをlogrotateで利用してみます。
コメント付きの部分が圧縮プログラムの設定になります。
全体的に適用したい場合は、/etc/logrotate.confに記述します。
# cat /etc/logrotate.d/httpd
/var/log/httpd/*log {
    daily
    missingok
    ifempty
    compresscmd /usr/local/bin/pigz               # 圧縮プログラムを指定
    uncompresscmd /usr/local/bin/unpigz       # 解凍プログラムを指定
    compressext .gz                                           # 圧縮ファイル拡張子を指定
    sharedscripts
    postrotate
        /sbin/service httpd reload > /dev/null 2>/dev/null || true
    endscript
}


httpdのログファイルを作って、時刻をローテーションの時間に合わせます。
dd if=/dev/zero of=/var/log/httpd/access_log bs=1024 count=1024000
date -s "2013/05/14 03:00:00"


しばらくすると、きちんとローテーションされていることがわかります。
# ls -l /var/log/httpd/
合計 1160
-rw-r--r-- 1 root root       0  5月 16 03:11 2013 access_log
-rw-r--r-- 1 root root 1176015  5月 16 03:11 2013 access_log-20130516.gz
-rw-r--r-- 1 root root     245  5月 16 03:11 2013 error_log
-rw-r--r-- 1 root root     294  5月 16 03:11 2013 error_log-20130516.gz


マルチコア環境に限りますが、これでローテーションが軽くなります。
以上です。


2013年5月10日金曜日

nagiosってなんじゃ?(nrpe + check_log3 + negateでログに特定のキーワードが含まれていなかったらアラート)

久しぶりにnagiosの話題です。

アプリログ内容の監視の仕方には様々な要件がありますが、特定の間隔でログを監視し「error」などの文言があったらアラートする。などがよくあるケースで、以前の記事にも書きました。

その逆に、例えば、多量のアクセスがあるにも関わらず頻繁に出力されるはずの重要なキーワードがでていない場合は、不測の事態がおこっているかも知れません。

今回は特定の間隔でログを監視し、その中にキーワードが含まれていなかったらアラートする
というものです。

ではやってみます。


nrpe check_log3の利用


まずは普通にcheck_log3の設定をします。

Nagiosサーバー側に、以下の設定をします。
3分間隔でチェックし、キーワード「hoge」が一つでも含まれていたらCRITICALを出します。
今回はWARNINGは出さないので、WARNINGの閾値をとても大きな数値にしておきます。

commands.cfg
define command{
    command_name    check_nrpe
    command_line    $USER1$/check_nrpe -H $HOSTADDRESS$ -c $ARG1$ -a $ARG2$
}


client.cfg
define host{
        use                    linux-server
        host_name        client_a
        alias                   client_a
        address             ec2-54-250-36-70.ap-northeast-1.compute.amazonaws.com
 }

define service {
        use                                  generic-service
        host_name                      client_a
        service_description         CHECKLOG3
        normal_check_interval   3
        check_command           check_nrpe!check_log3!/var/log/httpd/access_log /etc/nagios/seeks/check_log3.httpd.seek hoge 99999999999999 1

}


すると、キーワードが含まれている場合は、


キーワードが含まれていない場合は、


のようになります。



nrpe check_log3 + negateの利用


次に、これの逆をやってみます。
nagiosにはnegateというプラグインがあり、CRITICALとOKの評価を反転します。
このプラグインを使用して、評価反転用のcheck_nrpeコマンドを定義します。


commands.cfg
define command{
    command_name    check_nrpe
    command_line    $USER1$/check_nrpe -H $HOSTADDRESS$ -c $ARG1$ -a $ARG2$
}

define command{
    command_name    check_nrpe_negate
    command_line    $USER1$/negate $USER1$/check_nrpe -H '$HOSTADDRESS$' -c $ARG1$ -a $ARG2$
}


client.cfg
define host{
        use                    linux-server
        host_name        client_a
        alias                   client_a
        address             ec2-54-250-36-70.ap-northeast-1.compute.amazonaws.com
 }

#define service {
#        use                     generic-service
#        host_name               client_a
#        service_description     CHECKLOG3
#        normal_check_interval   3
#        check_command           check_nrpe!check_log3!/var/log/httpd/access_log /etc/nagios/seeks/check_log3.httpd.seek hoge 99999999999999 1
#}

define service {
       use                     generic-service
       host_name               client_a
       service_description     NEGATE_CHECKLOG3
       normal_check_interval   3
       check_command           check_nrpe_negate!check_log3!/var/log/httpd/access_log /etc/nagios/seeks/check_log3.httpd.seek hoge 99999999999999 1

}


すると、キーワードが含まれている場合は、


キーワードが含まれていない場合は、



のようになり、結果が反転します。
nrpeでなくても、どのプラグインでもnegateと組み合わせることができるので、ちょっとした工夫が必要なときにはとても便利です。

以上です。


2013年5月7日火曜日

Redshiftってなんじゃ?(BI編:JaspersoftでRedshift)

前回はpsqlでRedshiftを利用してみましたが、通常データウェアハウス(DWH)というのはBI(Buisiness Intelligence)ツールを利用することが多いようです。

エンジニアの観点からすると複雑なSQLを書くだけでいいかもしれませんが、経営者などの立場からするとBIツールなどを使って画面上でポチポチやって分析できることが重要なようです。

今回はそのBIツールの中で、Redshiftにいち早く対応しているJaspersoftという製品を使ってRedshiftに接続してみたいと思います。




起動とログイン



RedshiftとRDSに対応してあるバージョンのJaspersoftのAMIがマーケットプレイスにあるので、それを購入します。

Jaspersoft Reporting and Analytics for AWS
https://aws.amazon.com/marketplace/pp/B00B527JQ0





このAMIはインスタンスのランニングコスト以外にもソフトウェアの使用料金が時間単位で掛かります。
ここでは、RedshiftのあるUSのリージョンで起動します。




AWSコンソールのページが開き、インスタンス起動のウィザードが実行されます。




Redshiftと同じゾーンを選択します。
今回はm1.mediumのサイズで立ちあげます。




ここではルートボリュームを100GBにします。



また、セキュリティグループではSSHとHTTPをあけておきます。




起動させたあと、このインスタンスのPublicDNSまたはEIPに対してローカルのブラウザからアクセスしてみます。
すると、jasperサーバーの初期画面が表示されるので、「Login」をクリックします。



すると、ログイン画面が表示されます。



「ログインのお手伝いが必要ですか」というリンクをクリックすると、下図のように初期のID、パスワードが表示されます。ここではsuperuserでログインしてみます。



すると、新しいパスワードを要求されるので、好きなパスワードを設定して改めてログインします。



これでログインすることが出来ました。
これがJasperサーバーのホーム画面のようです。





データソースの登録



次に、データソースを作成します。
ここでいうデータソースとは、データがある場所をしめします。
データベースの場合やファイルの場合などがあり、ここではRedshiftをデータソースとして登録します。
グローバルヘッダの「作成」から「Data Source」を選択します。


 データソース作成画面では、タイプ欄からいくつかのデータソースタイプが選択でき、今回は「JDBC Data Source」を選択します。




ちなみに「AWS Data Source」でRedshiftを選択しても接続できます。






ドメインの作成



次にドメインを作成します。ドメインとはビューに渡すデータセットに当たります。
データソースからデータを選別してビューに渡すために整理した状態にします。

グローバルヘッダから「作成 > ドメイン」を選択します。

 ドメイン作成画面では、ドメイン名と作成したドメインの保存場所を適当に入力します。
また、データソースには先ほど作成したRedshiftのデータソースを選択します。
そして、ドメインデザイナのリンクをクリックします。


すると、ドメインデザイナが開き、データソースの「テーブル」タブが表示されます。
ここで、「>」ボタンなどで使用するテーブルを右側の枠に持っていきます。


次の「派生テーブル」タブで選択されたテーブルを対象に直接クエリ(SubQuery)などをかけられたり、



複数のテーブルを結合(Join)できたり、



フィルタ(Where)をかけられたり


などといったことができます。
つまりドメインというのはGUIでつくるSQL、つまりクエリビルダのようなものです。

最後の「表示」タブでは、このようにして作ったデータセットに対して、レポート表示時のラベルをつけれたりできます。




 そして「OK」を押すと、ドメインにこの設定が保存されます。
 ドメイン作成画面に戻り、「送信」を押すとこのドメインが作成されます。





アドホックビューの作成



次にアドホックビューを作成します。
アドホックビューは、レポート用の様々なタイプのビューで、グラフなどを作成できます。




アドホックビューでは最初に、入力ソースを選択します。
ここでは、さきほど作成したドメインを選択します。





 次に、ドメインに含まれるフィールドを選定します。
使用するフィールドを右側に移動し、下部のボタンから、テーブルやグラフなど表示タイプのボタンを押します。 今回は「グラフ」のボタンを押します。





すると、UIデザインの画面が開きます。
 ここで、左側の「フィールド」と「メジャー」から中央上部の「列」や「行」に適用する項目をドラッグします。この部分はGoogle Analyticsでカスタムレポートに指標やディメンションを追加するのに似ています。




 ここでは、列にprefecture_name, 行にcntをセットして、右側のデータラベルを右側にスライドします。
すると以下のようにグラフが自動で作成されます。
グラフのタイプを変更するには、グラフ左上の歯車アイコンをクリックして、変更します。





保存する場合は、中央ツールバーのディスクアイコンで保存します。








レポートの作成



 次に、レポートを作成します。
レポートは基本的に、アドホックビューを選択するだけです。





レポート作成画面でもグラフのタイプを変更することができます。



 ツールバーのディスクアイコンでレポートを保存します。






ダッシュボードの作成



最後はダッシュボードです。



ダッシュボードの作成画面では、複数のレポートを貼り付けたり、コントロールを貼り付けたりして帳票としての全体的な画面をつくります。




作成されたダッシュボードは以下のようになります。
このように、DBの知識がほとんどなくてもいろいろなデータの見た目をプリセットで整えて、手軽に更新、確認できるのがBIツールの役目のようです。





まとめ



Jaspersoftを使ってみた感覚としては、独特のコンポーネントにつけられた用語や意味を把握するのに少し戸惑いますが、慣れてしまうとそれなりに直感的に操作ができます。

ただ、異なるデータソースのデータ同士をJoinできるかと思ったのですが、やりかたがわかりませんでした。
その場合は、RedshiftにインポートしてRedshiftデータソースとして、ドメイン内でJoinしてしまえばよいかなと思います。


以上です。