某記事のフォロー
会社でかいた記事のはてブのツッコミのフォローをゆるゆる書いてみるテスト
記事中取り消し線多くてゴメンナサイ。
理解が乏しく恐縮なのですがこれは、CSSスプライトみたいなことをアーカイブファイルとインデックスファイルで実現した、という理解でよろしいのでしょうか。 - ngsw のコメント / はてなブックマーク
CSSスプライトみたいに「ページ表示に必要な複数の画像を効率よく配信する方法」ではなく、「それほどアクセス多くない画像ファイルをストレージ上にどう保存するか」という観点で読んで頂けると幸いです。
オンラインで更新があるわけではないので記事上では"log structured"という表現は避けたのですが、最終的にアーカイブファイルと同じフォーマットのファイルを作る"log structuredなHotな分散ストレージ"の設計も実は進めてました。
異動で完全になかったコトになりましたけど。
さすがに会社の公式エンジニアブログで退職エントリを書く勇気はないですw
「1ディレクトリに大量のファイルを作ってしまう」というレベルのハナシではなく、「ファイルシステム全体で数億ファイルとかになったら、IO負荷が上がりやすくなってしまう」という内容でした。
わかりにくくてすみません。
いろんな単語を解説なしに散りばめてしまったのはゴメンナサイ。
記事内では社内限りの用語は排除したつもりです。
大体はググッていただければ何かしら引っかかる用語だと思いますが、 "small_light系"だけそのままだと検索できないかもしれません。"mod_small_light"や"ngx_small_light"で調べて頂けるとよいかと。
それでも出てこない用語があった場合はコメント等でツッコんでください。
golangのよくあるtickerのサンプルがイケてない件
golangでのtickerのサンプルで以下のようにgoroutine内でrangeを使ってforループを回すのをよく見かけます。
package main import "time" import "fmt" func main() { ticker := time.NewTicker(10 * time.Millisecond) go func() { for t := range ticker.C { fmt.Println("Tick at", t) } }() time.Sleep(35 * time.Millisecond) ticker.Stop() }
コレ、以下のようにちょっと変更して実行してみましょう。
package main import "time" import "fmt" func main() { ticker := time.NewTicker(10 * time.Millisecond) go func() { for t := range ticker.C { fmt.Println("Tick at", t) } fmt.Println("Reachable?") }() time.Sleep(35 * time.Millisecond) ticker.Stop() time.Sleep(100 * time.Millisecond) }
"Reachable?"とは表示してくれませんね?
tickerのStop()してもチャンネルをcloseしてはくれないので、残念ながらこのforループは抜けることはないのです。 その結果、goroutineがリークしてしまいます。
んじゃあどうすればいいかっつーと、以下のようにする必要があります。
package main import "time" import "fmt" func main() { ticker := time.NewTicker(10 * time.Millisecond) stop := make(chan bool) go func() { loop: for { select { case t := <-ticker.C: fmt.Println("Tick at", t) case <-stop: break loop } } fmt.Println("Reachable!") }() time.Sleep(35 * time.Millisecond) ticker.Stop() close(stop) time.Sleep(100 * time.Millisecond) }
……別でチャンネル用意したりとか正直クソメンドクサイですね。
ただ単にバックグラウンドでの繰り返し処理をするためだけに毎回こんなコード書くとか耐えられません。
ということで、ラッパーをつくりました。
https://github.com/okzk/ticker
このラッパーを使えば以下のようになります。
package main import ( "fmt" "github/okzk/ticker" "time" ) func main() { ticker := ticker.New(10*time.Millisecond, func(t time.Time) { fmt.Println("Tick at", t) }) time.Sleep(35 * time.Millisecond) ticker.Stop() }
ラクチン!
MySQLのコールドバックアップから別slave作ってハマった件
以下の手順でハマった。対象のバージョンは5.6.27
原因はauto.cnfを消してなかったのでserver_uuidが衝突してしまったから。orz...
# server_uuidは5.6からなので、完全にやらかしてもうた。
ちなみにshow slave statusで出てたエラーメッセージは以下の様なカンジ
A slave with the same server_uuid as this slave has connected to the master
教訓
- コールドバックアップからslaveを作る際はauto.cnfは消そう。
- もしくはバックアップからそもそもauto.cnfを除外しよう。
愚痴
server_uuidが衝突したらレプリ止まるのは仕方ないとしても、後勝ち(先に接続したレプリが止まる)ではなく、先勝ち(後から接続に来たレプリがコケる)にしなかったのはなんでなんだろう?
「start slaveしたら別サーバのレプリが止まる」とか、どう考えても罠やん。。。
MySQLのTransportable Tablespacesのおはなし
今更感あるけど、以下の補足。 kakerukaeru.hatenablog.com
なお、対象のMySQLのバージョンは5.6.26と5.6.27です。
といってもオペレーション自体は特記することもなくて、以下のページをよく読んでそのままやればダイジョウブです。カンタンカンタン。 http://dev.mysql.com/doc/refman/5.6/en/tablespace-copying.html
以下、注意点、というかハマったところ。
複数のテーブルをまとめてエキスポートする場合
FLUSH TABLESは以下のように複数のテーブルをまとめることが可能。
FLUSH TABLES t1, t2, t3, t4 FOR EXPORT;
# (ファイルコピーしてから)
UNLOCK TABLES;
同一セッション内で複数に分けてテーブルをLOCKすることはできないので、以下はダメ。
FLUSH TABLES t1 FOR EXPORT; FLUSH TABLES t2 FOR EXPORT; FLUSH TABLES t3 FOR EXPORT; FLUSH TABLES t4 FOR EXPORT; # (ファイルコピーしてから) UNLOCK TABLES;
移行先がレプリを組んでいる場合
マスタ/スレーブ全台にエキスポートしたファイル(.ibd/.cfg)を配置してから、マスタで以下のSQLを投げればいいだけ...
ALTER TABLE テーブル名 IMPORT TABLESPACE;
なんですけど、このALTER TABLE文、結構IO使うし、テーブルサイズによってはそこそこ時間がかかるので、コレ起因でレプリ遅延が発生orz...
以下のようにレプリさせずに個別に全サーバでIMPORTすれば回避可能なはず。次回があれば試してみます。
SET sql_log_bin = 0; ALTER TABLE テーブル名 IMPORT TABLESPACE; SET sql_log_bin = 1;
インポート用にDB分離しておいて、slave_parallel_workersで複数sql_running動くようにしてもいい気もする。が、そのためだけにslave_parallel_workers設定するのは...
その他
IMPORT後も移行先で.cfgファイルは残ります。
たいしたサイズじゃないので、構わず放置してますけど(ぉ
Rubyから(ry → はやい
2年以上放置していたけど、なんかフィボナッチ数の計算を高速化するのが流行ってると聞いて(違
フィボナッチ数が64bitで表現できる場合に限ると、以下がO(1)でほぼ最速だと思う。
FIB = [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765, 10946, 17711, 28657, 46368, 75025, 121393, 196418, 317811, 514229, 832040, 1346269, 2178309, 3524578, 5702887, 9227465, 14930352, 24157817, 39088169, 63245986, 102334155, 165580141, 267914296, 433494437, 701408733, 1134903170, 1836311903, 2971215073, 4807526976, 7778742049, 12586269025, 20365011074, 32951280099, 53316291173, 86267571272, 139583862445, 225851433717, 365435296162, 591286729879, 956722026041, 1548008755920, 2504730781961, 4052739537881, 6557470319842, 10610209857723, 17167680177565, 27777890035288, 44945570212853, 72723460248141, 117669030460994, 190392490709135, 308061521170129, 498454011879264, 806515533049393, 1304969544928657, 2111485077978050, 3416454622906707, 5527939700884757, 8944394323791464, 14472334024676221, 23416728348467685, 37889062373143906, 61305790721611591, 99194853094755497, 160500643816367088, 259695496911122585, 420196140727489673, 679891637638612258, 1100087778366101931, 1779979416004714189, 2880067194370816120, 4660046610375530309, 7540113804746346429, 12200160415121876738].freeze def fib(n) FIB[n] end puts fib(40)
解説もベンチマークも要らない...よね?
YARN-467/YARN-527でハマッタ件
CDH4.1.2のYarnクラスタで、${yarn.nodemanager.local-dirs}/filecache/以下にディレクトリがいっぱい(3万以上)できてしまってタスクが起動しなくなるという現象に見舞われました。
# 他のnodeでリカバリしてjob自体は成功していましたが。。。
どうやらDistoributed Cacheを利用していると、どんどんディレクトリが作成されていく一方で、自動でcreanupはしてくれない模様。
jiraにも上がっていますが、取りあえず古いディレクトリを削除するようcronを仕込んで対応完了としました。
(参考)
https://issues.apache.org/jira/browse/YARN-467
https://issues.apache.org/jira/browse/YARN-527
CapacitySchedulerでのリソース管理
前のエントリでは各ノードのメモリ管理について書いたので、次にクラスタ全体のリソース管理として
CapacitySchedulerについてのメモです。
なお、CDH4.1.2で試した結果ですので、最新のバージョンとは(ry
CapacitySchedulerについて
クラスタ上では同時に様々なmapreduceを実行するのが普通だと思います。
CapacitySchedulerを利用すると、各jobごとに重要度に応じて、柔軟なリソース割り当てを行うことができます。
詳細は以下を確認してください。
http://archive.cloudera.com/cdh4/cdh/4/hadoop/hadoop-yarn/hadoop-yarn-site/CapacityScheduler.html
以下、微妙に気になったトコをメモしていきます。
rootのcapacity設定は必須
自明なのでなくてもいい気がしますが、無いと怒られます。
<property> <name>yarn.scheduler.capacity.root.capacity</name> <value>100</value> </property>
defaultのcapacity設定は必須
defaultはjob設定でマッチするqueueがない場合に割り当てられるqueueです。
無いとやっぱり怒られます
<property> <name>yarn.scheduler.capacity.root.queues</name> <value>default</value> </property> <property> <name>yarn.scheduler.capacity.root.default.capacity</name> <value>100</value> </property>
各キューのcapacityとmaximum-capacityとuser-limit-factorの関係
分かりにくかったのですが、、、
- クラスタがアイドル状態の場合
- そのjobは"capacity*user-limit-factor +α"分までリソースを利用できる。
- ただしqueue全体でmaximum-capacityを超えることはできない。
- クラスタがビジー状態の場合
- "capacity+α"分までリソースを利用できる
- 上記の"+α"分は弾力性設定で、capacityを少し超えるまではコンテナを起動できる。
- そのためuser-limit-factorが1でも"Over Capacity"状態になりうる。
minimum-user-limit-percentの影響はよくわかりませんでした(汗
例
アドホックなHiveクエリ等の野良MapReduceはdefaultで、定期的なバッチ等はmanagedで実行することを想定しています。
<property> <name>yarn.scheduler.capacity.root.capacity</name> <value>100</value> </property> <property> <name>yarn.scheduler.capacity.root.queues</name> <value>default,managed</value> </property> <property> <name>yarn.scheduler.capacity.root.default.capacity</name> <value>40</value> </property> <property> <name>yarn.scheduler.capacity.root.default.user-limit-factor</name> <value>1.5</value> </property> <property> <name>yarn.scheduler.capacity.root.default.maximum-capacity</name> <value>70</value> </property> <property> <name>yarn.scheduler.capacity.root.managed.capacity</name> <value>60</value> </property> <property> <name>yarn.scheduler.capacity.root.managed.user-limit-factor</name> <value>2</value> </property>
この場合ですが、
- アイドル状態のときは
- defaultに投入されたジョブは60%(= 40% * 1.5)までリソースを使用可
- defaultに複数ジョブが投入された場合、合計で最大70%までリソースを使用可
- managedに投入されたjobは100%のリソースを利用可
- 非アイドル状態のときは
- defaultに投入されたジョブは40%までリソースを使用可
- managedに投入されたジョブは60%までリソースを使用可
- defaultが70%リソースを使用しているときにmanagedにジョブが突っ込まれたら
- managedはとりあえず30%のリソースでジョブが実行できる。
- defaultに投入されたジョブのコンテナが終了すると、managed側が空いたリソースでコンテナを起動できる。
まとめ
以下のような考え方で各queueの設定を決めていけばいいと思います。
- capacityはクラスタがビジーのときにどれくらいの割合でリソースを割り振りたいかで決定する。
- maximum-capacityは、他queueのために最低限どれくらいリソースを残しておきたいかで決定する。
- user-limit-factorは、アイドル状態で効く設定なので気持ち多めでいいんじゃないかと。
なおqueueはツリー的に細分化して設定していくこともできます。
上の例では面倒なので、defaultとそれ以外みたいなカンジにしてしまってますが。