独断と偏見で選ぶ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" } } ] }'
終わり
以上です。冒頭にも書きましたが間違っている点・追加した方が良い点などありましたら教えていただけると助かります!
弊社では現在エンジニアを募集中です!
Serverless Framework V.1 Alphaのかなり丁寧なチュートリアル
サーバーレスアーキテクチャという言葉を聞くようになってしばらく経ちました。 サーバーレスアーキテクチャに関しては以下の記事が非常に綺麗なのでそちらをご覧ください。
サーバーレスアーキテクチャという技術分野についての簡単な調査 - Qiita
サーバーレス・アーキテクチャの話 - yoshidashingo
個人的にも、サーバーを一番簡単に建てる方法はサーバーを建てない事で、サーバーを一番簡単に管理する方法もサーバーを建てない事だと思っているので、サーバーレスアーキテクチャは使えるところでは使っていきたいです。(パフォーマンスが求められる部分ではまだ厳しいですが)
というわけで、Serverless Frameworkが 先日V.1 Alphaをリリースしたようなので試してみます。AWSアカウントの準備から行っているので、既にある程度の知識がある方にとっては冗長な記事になっているかと思います。ご容赦ください。
また、AWSの無料枠(アカウント開設後1年間有効)を持っている方でしたら本記事の内容を全て試してもお金はかからないはずです。
Serverless Frameworkとは
AWS Lambdaを主に活用してサーバレスなアプリケーションサービスを構築するためのフレームワークです。今後はGoogle Cloud Functions, Microsoft Azure,IBM OpenWhiskなどにも対応していくとの事です。Serverless Frameworkってまさにそのままの名前付けられるのも先駆者の特権というやつでしょうか。
環境
- OSX 10.11
- Serverless V.1 Alpha
事前準備
本当に1からやるので、ほとんどの方は飛ばしていただいて大丈夫だと思います。
AWSアカウント作成
以下を参考にして行ってください。面倒くさいかもしれませんが、二段階認証と請求情報のアラームは絶対に設定しておきましょう。
AWSアカウントを取得したら速攻でやっておくべき初期設定まとめ - Qiita
IAMユーザ作成
https://console.aws.amazon.com/iam/home#usersからServerless Frameworkが使用するためのIAMユーザーを作成します。アクセスキー・シークレットキーを安全な場所に保管しておいてください。
※公式のTutorialでは簡易化のため作成したユーザーにAdministratorAccessのポリシーを付けていますが、本番で使う時は適切な権限だけをつけるようにしてください。
AWS CLIインストール
AWS CLIをインストールします。インストール済みの方はスキップでOKですが、公式では[default]にServerless Frameworkが使用するIAMユーザーを設定するよう推奨していますので、そこだけ設定を変更したほうが良いかもしれません。
serverless/provider-account-setup.md at v1.0 · serverless/serverless · GitHub
We'd recommend to create a default AWS profile on your local machine as Serverless uses this as a default which makes developing a lot faster and easier.
$ sudo pip install awscli --ignore-installed six $ aws configure # 先ほど作成したIAMユーザのアクセスキーを入力 AWS Access Key ID [None]: xxxxxxxxxxxx AWS Secret Access Key [None]: xxxxxxxxxxxx Default region name [None]: us-west-2 Default output format [None]: ENTER $ cat ~/.aws/credentials [default] #ここがServerless FrameworkのIAMになっていること推奨 aws_access_key_id = xxxxxxxxxxxx aws_secret_access_key = xxxxxxxxxxxx [user_2] aws_access_key_id = xxxxxxxxxxxx aws_secret_access_key = Fxxxxxxxxxxxx
node環境構築
Serverless Frameworkのインストールにnpmが必要なので、node環境を構築します。 nodeはnodebrewを使って管理しますが、OSX 10.11ではnodebrewはhomebrewでインストールすると正常に動かないので注意してください。公式に従ってcurlを使ってインストールしましょう。
GitHub - hokaccha/nodebrew: Node.js version manager
$ curl -L git.io/nodebrew | perl - setup
~/.bash_profileに以下を追加
export PATH=$HOME/.nodebrew/current/bin:$PATH
設定を反映
$ source ~/.bash_profile
nodebrewを使ってnodeの安定版をインストール(今回はv6.3.0)
$ nodebrew install-binary stable
fetch: http://nodejs.org/dist/v6.3.0/node-v6.3.0-darwin-x64.tar.gz
######################################################################## 100.0%
Install successful
インストールされたバージョンを使うように設定
$ nodebrew ls v6.3.0 current: none $ nodebrew use v6.3.0 use v6.3.0 $ node -v v6.3.0
一応npmもupdateしておく。
$ npm install -g npm $ npm --version 3.10.5
Serverless Frameworkインストール
事前準備が終わったので、実際にServerless FrameworkをインストールしてHello Worldを行ってみます。ここからは公式のセットアップ手順に従って行います。
インストール自体はコマンド1つ打つだけで完了します。
$ npm install -g serverless@alpha $ sls --help _______ __ | _ .-----.----.--.--.-----.----| .-----.-----.-----. | |___| -__| _| | | -__| _| | -__|__ --|__ --| |____ |_____|__| \___/|_____|__| |__|_____|_____|_____| | | | The Serverless Application Framework | | serverless.com, v1.0.0-alpha.1 -------'
プロジェクト作成
$ sls create --name first-service --provider aws Serverless: Creating new Serverless service... Serverless: Successfully created service "first-service" Serverless: |- serverless.yaml Serverless: |- serverless.env.yaml Serverless: |- handler.js
設定ファイル書き換え
公式のドキュメントを参考にして設定ファイルを書き換えます。今回はランタイムをnodejs4.3で、HTTP経由でアクセスできるようにします。
serverless.yaml
service: first-service provider: aws runtime: nodejs4.3 defaults: # overwrite defaults stage: dev region: ap-northeast-1 memory: ${memoryVar} # reference a Serverless variable timeout: 30 functions: hello: handler: handler.hello events: - http: path: greet method: get
serverless.env.yaml
vars: memoryVar: 256 stages: dev: vars: regions: ap-northeast-1: vars:
デプロイ
設定ファイルを変更したらデプロイします。
$ sls deploy Serverless: Creating Stack... Serverless: Checking stack creation progress... Serverless: Stack successfully created. Serverless: Zipping functions... Serverless: Uploading zip files to S3... Serverless: Adding function resources to CF... Serverless: Updating Stack... Serverless: Checking stack update progress... Serverless: Deployment successful!
デプロイが成功したら、AWSのコンソールにアクセスして確認してみます。 LambdaのコンソールとAPI Gatewayのコンソールにアクセスして、実際に登録されていればOKです。
Lambdaコンソール
API Gatewayコンソール
最後に実際にHTTP経由でアクセスできるかどうか確認してみます。きちんと結果が返ってきますね!
$ curl https://xxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/dev/greet {"message":"Go Serverless v1.0! Your function executed successfully!","event":{"body":{},"method":"GET","headers":{...}}
終わり
以上でServerless Frameworkのチュートリアルは終了です。AWS LambdaとAPI Gatewayを手動で連携させようとするとかなり面倒くさいので、個人的には非常に良い感じです。 今後も使えるところでは是非使っていきたいと思います。