Udacityのディープラーニングのナノ学位基礎コースを受講します
ネットサーフィンしてたらUdacityがディープラーニングのナノ学位基礎コースを立ち上げ、399ドルで志願者全員入学の記事を見つけたので、受講してみることにしました。
ディープラーニングの基礎の基礎ぐらいは分かっていると思うのですが、どうも具体的なアプリケーションに落とし込むイメージが湧いていないのでそれの足がかりになればなと思います。あと英語勉強も一緒にやりたい。
時間あれば学んだ内容をまとめて記事にできればなと思います。
独断と偏見で選ぶHDFSのファイル形式
HDFSのファイル形式を何にすべきか、というのはRPGの主人公の名前を何にすべきか、と同じぐらい皆さん悩まれるかと思います。
ご多分に漏れず僕も悩みましたので、調べた事をまとめておきます。 なお先に結論だけ言っておきますと、大体のケースではORCをZlib圧縮して使っておけば良いんじゃないかなと考えています。マサカリは歓迎です。
※201701/21追記 EMR5.0以降ではHive + ORCで遅くなるケースがあるとのアドバイスをAWSのサポートの方から伺いました。EMRを使っている方はParquetとの速度比較をしてみたほうが良いかもしれません。
ファイル形式の候補
ファイル形式の候補としては大体以下が挙げられます。
各形式の特徴
それぞれのファイル形式の詳細な説明はここではせず、簡単な特徴だけ記します。なお、以下のサイトを参考にさせていただきました。
How to Choose a Data Format - Silicon Valley Data Science
hadoop - Parquet vs ORC vs ORC with Snappy - Stack Overflow
Mostafa Elzoghbi: Avro vs Parquet vs ORCFile as Hadoop storage files
ORCとParquetは共にカラムナー型でデータを保存でき、クエリの最適化に優れている。 また、圧縮効率も共に高い。
- ORCはrow Indexやbloom filterが使えるので基本的にはORCの方が良い。ただしネストが深いデータ構造の場合はParquetの方が良い。
- ImpalaはORCをサポートしていない
Apache Avroはカラム追加・削除・複数カラムのリネームまでサポートしておりカラム構造の変更に強い。
SequenceFileはMapReduceのジョブに最適化されている。
TextFileは書き込みが早く、人の目で確認できるので管理しやすい。
ユースケースごとの使い分け
上記の特徴を考えると、ユースケースごとの使い分けとしては以下のようになります。
- 単純にクエリを最適化したいならORC
- ネストが深いデータ構造の場合やImpala使うならParquet
- カラム変更に強くしたいならAvro
- MapReduceジョブ最適化ならSequence
- 読み込みのパフォーマンスがどうでも良いならText
ORCのファイル圧縮形式
HDFS上のファイルは圧縮する事で容量が減り、処理も効率的になる事が多いです。
ORCでは圧縮形式としてSnappyかZlibを選べます。昔は圧縮効率ならZlib、書き込み効率ならSnappyという住み分けだったらしいですが、2015年あたりにZlibのアルゴリズムが新しくなってからは書き込み効率にそこまで差が無くなったので無条件でZlibで良いかと思います。
※実際100Gb程度のファイルでSnappyとZlibで書き込み速度を比較した際はほとんど差がありませんでした
参考 Snappy vs. Zlib - Pros and Cons for each compression in Hive/ Orc files - Hortonworks
ORC: 2015 Faster, Better, Smaller
まとめ
Hadoopを使いたいときは大体HiveやPrestoなどのクエリエンジンとセットで使うと思うので、基本的にはZlib+ORCで良いと思います!なおORCフォーマットの性能を活かしきるには設定値やデータ投入時に少し工夫が必要だと思っていますが、それは次回の記事で書こうと思います。
Elasticsearch実運用時の注意点とアンチパターンまとめ
Elasticsearch実運用時に個人的に気をつけている点をまとめました。 自分でも整理できていない部分がありますので、間違っている点・追加した方が良い点などありましたら教えていただけると非常に助かります。
目次
インデックス・スキーマ設定
スキーマを事前に定義しておく
Elasticsearchはスキーマレスじゃないのか!といきなり突っ込まれそうな感じですが、個人的にはデータ投入前にスキーマを厳密に定義しておくのをお勧めします。
理由としては、主に以下の2点です。
- スキーマを定義せずに投入されたデータはElasticsearchが型を判断するためエラーの元になりやすい
- not_analyzed・doc_valueなどの詳細設定(後述)を行ったほうが性能が良くなる
_allの使用は必要な時のみ行う
デフォルトで全フィールドのデータが入る_allというメタフィールドがありますが、ディスク容量・性能の観点から全フィールドを跨いだ検索を行う予定が無い場合は無効にしたほうが良いです。
{ "mappings" : { "sample_type" : { "_all" : {"enabled" : false} } } }
部分一致検索をしないstringフィールドにはnot_analyzedをつける
stringフィールドはデフォルトではインデックス時に形態素解析などの処理が行われてしまうため、完全一致検索時に想定しない結果が返ってきたりインデックス処理が重くなったりしてしまいます。
完全一致検索のみを行いたいstringフィールドに対しては、マッピング時にnot_analyzedをつけることを推奨します。
"string_field" : { "type" : "string", "index" : "not_analyzed" }
doc_value:trueをつける
マッピングの際にdoc_values:trueを設定すると、cacheをメモリではなくディスクに乗せることができます。メモリに乗せる場合よりも20%程低速になりますが、ヒープメモリに影響されなくなり、挙動が安定するのでオススメです。
なお、not_analyzedではないstringフィールドには適用できないので注意してください。
Disk-Based Field Data a.k.a. Doc Values | Elastic
"string_field" : { "type" : "string", "index" : "not_analyzed", "doc_values":true }
できるだけエイリアスを使う
アプリからインデックスを参照する際に、インデックス名を直接参照するのではなくエイリアスを設定してそちらを参照することで、変更に強くなります。
一般的に検索仕様の変更などを行う際はインデックス再作成を行う必要があるのですが、エイリアスを利用すればダウンタイム無しで柔軟に対応することができます。
Changing Mapping with Zero Downtime | Elastic
Elasticsearch インデックス・エイリアス – Hello! Elasticsearch. – Medium
Elasticsearch のインデックスを無停止で再構築する - クックパッド開発者ブログ
以下は、test_indexに紐付いていたエイリアスをtest_index_2に切り替える際のコマンドです。アプリからは常にtest_index_aliasを参照するようにしておけば、ダウンタイム無しでアプリから新しいindexを参照できるようになります。
curl -XPOST 'http://localhost:9200/_aliases' -d ' { "actions" : [ {"remove": {"index": "test_index", "alias": "test_index_alias" }}, {"add": {"index": "test_index2", "alias": "test_index_alias" }} ] }'
必要に応じてルーティングを行う
デフォルトではデータがどのシャードに配置されるかはidのハッシュ値をシャード総数で割った値で決定されますが、マッピング時に任意のフィールドを_routingに指定する事で、そのフィールドの値によってシャードの配置を決めることができます。
適切に設定すると検索時の負荷を減らすことができますが、不適切なフィールドを指定するとホットシャードが生まれてしまうので注意して設定してください。自信が無いならデフォルト設定のままで良いです。
対象のフィールドは、store:trueかつindex:not_analyzedである必要があります。また、required:trueでなくてはなりません。
_routing field | Elasticsearch Reference [6.1] | Elastic
{ "mappings" : { "sample_type" : { "_routing" : {"required" : true, "path" : "route_param"}, "properties" : { "route_param" : { "type" : "string", "index" : "not_analyzed", "required" : "true", "store" : true } } } } }
本体の設定
ヒープメモリを適切に設定する
公式に書いてある通り、ヒープメモリは物理メモリの半分までで32GBを超えないようにしましょう。
Heap: Sizing and Swapping | Elasticsearch: The Definitive Guide [2.x] | Elastic
/etc/sysconfig/elasticsearch
ES_HEAP_SIZE:4g
スワップしないように設定
公式で「起動時にメモリを確保し、スワップしないようにする」という設定が推奨されています。
http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/setup-configuration.html
起動時のメモリ確保は上に書いたES_HEAP_SIZEの設定でOKなので、後はスワップしないようにする設定を行います。
/etc/elasticsearch/elasticsearch.yml
bootsrap.mlockall:true
環境によって上手くいかない時があるので、設定が効いているかどうかhttp://localhost:9200/_nodes/process?prettyを見て確認しましょう。
上手くいかない時は以下の記事も見てみてください。
ElasticSearchでmlockall:trueが効かない時にチェックすること - サナギわさわさ.json
スプリットブレイン対策を行う
スプリットブレイン対策のために、マスターノードを選ぶ際に必要となる接続ノードの数をノード数/2+1
に設定しておきましょう。例えば6台構成の場合は4になります。
/etc/elasticsearch/elasticsearch.yml
discovery.zen.minimum_master_nodes:4
curlから設定する場合はこっち
curl -XPUT http://localhost:9200/_cluster/settings -d '{ "persistent": {"discovery.zen.minimum_master_nodes": 4} }'
field data cacheに上限を与える
Elasticsearchでは各fieldのデータをメモリに載せることで検索を高速化しているのですが、デフォルトでは無制限にメモリに載るため、OutOfMemoryErrorが発生することがあります。 以下は、使用可能なメモリをHEAP_SIZEの75%に変更する例です。
/etc/elasticsearch/elasticsearch.yml
indices.fielddata.cache.size: 75%
検索高速化
Warmerを登録する
各fieldのデータがメモリに載ることで検索が高速化されるのは上述した通りですが、fieldのデータをメモリに載せる処理はインデックス再構築後1回目のクエリが投げられた時に行われるため、1回目のクエリがかなり重くなることが多いです。
Warmerクエリを設定しておくと、インデックス再構築後に自動でクエリを発行してfield cacheを作成してくれるので便利です。
Warmers | Elasticsearch Reference [6.1] | Elastic
curl -XPUT localhost:9200/testindex/_warmer/warmer_1 -d '{ "query" : { "match_all" : {} }, "aggs" : { "aggs_1" : { "terms" : { "field" : "field" } } } }'
queryとfilterを使い分ける
条件検索はqueryでもfilterでも行えますが、スコア計算を用いない検索条件(完全一致やrangeなど)ならqueryではなくfilterを用いた方が早いです。
Optimizing Elasticsearch Searches | Elastic
filterのキャッシュ設定を使い分ける
filterを使うとフィルタで引っかかったリクエストをデフォルトでキャッシュしてくれます。
ただ全てキャッシュしているとメモリが足りなくなる事が多いので、明らかにキャッシュしない方が良い絞り込み条件に関しては検索の際にキャッシュしないように明示的に設定しましょう。
必要無いフィールドは検索の際に取得しない
言うまでも無いですね。。。
インデクシング高速化
困ったら公式を見ましょう。
Performance Considerations for Elasticsearch Indexing | Elastic
あと以下の記事にほとんどまとまっているので、詳しくはそちらを見た方が良いかもしれません。
Elasticsearchインデクシングパフォーマンスのための考慮事項 - Qiita
bulk APIを使う
大規模データの投入には必ずbulk APIを使いましょう。なお、公式では1つのbulk Importのサイズは5~15MBにすることを推奨しています。
curl -XPOST http://localhost:9200/_bulk --data-binary @databatch.json >/dev/null
コミット頻度の変更
ElasticSearchはNRT(Near Real-Time Search)機能を搭載しており、デフォルトで1秒ごとにソフトコミットが実行され、更新結果が検索に反映されます。
インデクシング時にはこれが結構重いため、更新結果をそこまでリアルタイムに反映する必要がない場合は、ソフトコミットの頻度は30sぐらいに設定した方が早くなります。
curl -XPUT http://localhost:9200/testindex/_settings -d ' { "index" : { "refresh_interval" : "30s" } }'
レプリカシャードを一時的に0に設定
インデクシング中のみレプリカシャードを作らないようにすることで、 データ投入にかかる時間が短くなります。リスクとのトレードオフです。
curl -XPUT http://localhost:9200/testindex/_settings -d ' { "index" : { "number_of_replicas" : 0 } }'
ノード追加・削除
ノード追加・削除時はスプリットブレインが起こりやすいので注意してオペレーションしましょう。また、オペレーション後にdiscovery.zen.minimum_master_nodes
を変更するのを忘れないようにしてください。
シャードの自動配置を停止
ノードの追加・削除処理を行う際に、デフォルトだとシャードの再配置が走って負荷が一気に上がります。処理の前にシャードの自動配置を停止しておきましょう。
Elasticsearchのクラスタにノードを追加するときにやっていること - すずけんメモ
curl -XPUT http://localhost:9200/_cluster/settings -d '{ "persistent": {"cluster.routing.allocation.enable": "none"} }'
ちなみにオペレーション終了時に自動配置を有効に戻す場合は以下です。
curl -XPUT http://localhost:9200/_cluster/settings -d '{ "persistent": {"cluster.routing.allocation.enable": "all"} }'
シャードを手動で再配置
ノード追加の際は以下のオペレーションで問題ありませんが、
- シャードの自動配置を停止
- ノードを追加
- シャードの自動配置を有効に戻す
ノード削除の際は一手順増えます。
- シャードの自動配置を停止
- 削除対象ノードからシャードを移動
- ノードを削除
- シャードの自動配置を有効に戻す
シャードの手動再配置は以下のようにして行えます。面倒ですが1つずつ移動させるのをお勧めします。
curl -XPOST 'localhost:9200/_cluster/reroute' -d '{ "commands" : [ { "move" : { "index" : "test", "shard" : 0, "from_node" : "node1", "to_node" : "node2" } } ] }'
終わり
以上です。冒頭にも書きましたが間違っている点・追加した方が良い点などありましたら教えていただけると助かります!
弊社では現在エンジニアを募集中です!