patorashのブログ

方向性はまだない

くるくるワイドについてマインドマップでまとめた

FXくるくるワイド投資術

FXくるくるワイド投資術

昔から少しやっている投資法である、くるくるワイドだけれど、理解しては忘れる、を繰り返していたので、マインドマップにまとめながら本を再読した。

f:id:patorash:20180812223745p:plain
くるくるワイドのマインドマップ

くるくるワイドは放置系ではない

基本は、本体と、その同量のトラップを仕掛けておくのであるけれど、これだけで放置しておけばいいというものではない。デイトレのヘッジトレードで得た利益で、固定ポジションを作っていく必要がある。

本体をロングとした場合、100円で7万ドル持っていたとして、出口が107円の場合、(107-100)x7=49万円が本体の利益。 トラップはショートになって、10銭刻みでのトラリピとなり、出口の107円が損切りラインとなり、出口に着いたら24.5万円の損。 100円から107円にたどり着くまでにトラップは500円ずつヒットするので、利益は107円までにヒットした回数x500円。

裁量トレードを行わない場合ですんなり上がった場合は、

49万-24.5万+トラップ益=24.5万+トラップ益

となって、儲かりそうではあるが、すんなりと順行でいくことは滅多にない。

そこで、24.5万円の予定利益分を使って、ヘッジトレードを行う。

ヘッジトレードが肝

ヘッジトレードは、出口を損切りラインにした10銭幅のデイトレで、玉数を多く建てることができる。 話を簡単にするために、100円のときに3.5万ドルでヘッジトレードをしたとする。

ヘッジトレードが成功した場合

3.5万x0.1=3,500円の儲けとなる。

ヘッジトレードが失敗した場合

(107-100)x3.5=24.5万の損切りとなるが、本体ロングとトラップの利益は24.5万+トラップ益なので、

24.5万+トラップ益-24.5万=トラップ益

となり、もしヘッジトレードが塩漬けになっても出口でマイナスにはならない。

なので、出口でも損はしないという心理的安全を元に、積極的にヘッジトレードを仕掛けていくべき。成功すれば、建て玉数が多いだけに、利益も大きい。何度でもやってよい。個人的には抵抗線の手前で仕掛けたら成功しやすいんじゃないかなと思った。

複利で利益倍増する

くるくるワイドが放置系でないところは、ヘッジトレードだけではなく、複利もである。

複利は、トラップで得た利益を3分割して本体と同じ向きで再投資するものだ。

  1. 攻撃的複利・・・レンジの外辺りにストップロスを置くポジション
  2. 現実的複利・・・支持線の下辺りにストップロスを置くポジション
  3. 非現実的複利・・次の支持線や、史上最安値辺りにストップロスを置くポジション

1日1,500円のトラップ益が発生したとすると、それぞれに500円ずつ割り振っていく。

ドル円が100円で、攻撃的複利のストップロスが95円、現実的複利のストップロスが90円、非現実的複利のストップロスが75円として、トラップ益がそれぞれ5万円ずつ貯まっているとすると

攻撃的複利 現実的複利 非現実的複利
ストップロス 95 90 75
1枚あたりの必要トラップ益 5,000円 10,000円 25,000円
複利ポジション数 10 5 2

となり、複利は合計1.7万ドルとなり、もしこれが出口までたどり着いたら(107-100)x1.7=11.9万円の上乗せ利益となる。

実際は値動きは荒いし、攻撃的複利がストップロスにひっかかることもあるのだけれど、

  • 値が下がる=トラップトレードが儲けを生み出しているはず
  • 値が下がる=現実的複利、非現実的複利を仕込みやすくなっている

という点がある。例えば、ドル円が94円になってしまって攻撃的複利が切られたとする。5万円の損ではあるけれど、この損はあくまでトラップ益であり、元本ではない。そして、94円になるまでにトラップ益が8万円貯まっていたとすると現実的複利、非現実的複利に4万ずつ割り振ったとして…

攻撃的複利 現実的複利 非現実的複利
ストップロス 95 90 75
1枚あたりの必要トラップ益 ---- 4,000円 19,000円
複利ポジション数 ---- 15 4

となり、複利ポジションは19になっている。また、現実的複利にたどり着く前に反転する可能性は高い(あくまで、そういう支持線の下辺りにストップを置くから)。値下がりは、複利ポジションを増やすチャンスでもある。ナンピンっぽいけれど、あくまでトラップ益の範囲でやる。

私が思う本命、「固定ポジション」

くるくるワイドの本命は、この「固定ポジション」だと思う。これがリスクを抑えるための要になる。固定ポジションとは、ヘッジトレードで得た利益で建てる本体との逆のポジションのことで、つまり両建てである。個人的には、複利も大事だけれど、トラップ益で固定ポジション建ててもいいと思う。

本体に対しての両建て=固定ポジションなのだが、なぜこれを建てる必要があるのかというと「逆行対策」のためである。 くるくるワイドの弱点は、逆行。くるくるワイドをよく理解せずに放置系だと思って始めた頃は、本体ロングした途端に逆行して即マイナスになって半年くらい苦しんだのだけれど、固定ポジション(ショート)をしておいたら、下がってもダメージが少ない、または、なくなる(完全にヘッジし終わっていたら)。

そして、下がって支持線の辺りで固定ポジションを決済したら、ショートの利益が得られる。

固定ポジションの位置

固定ポジションは、抵抗線の上と出口の中間くらいをストップロスとして建てていく。あくまでヘッジトレードの利益も、出口までの道具として使う。 ヘッジトレードで1回につき3,500円の利益、103.5円に固定ポジションをつけるとして、35,000円のヘッジトレード益があったとする。

固定ポジション
固定ポジションのストップロス 103.5
1枚あたりの必要ヘッジ利益 3,500円
固定ポジション数 10

この状態で100円から99円に下がったとしたら、本体ロングが70なので、-7万円になるのだけれど、固定ポジションは+1万円になり、トータルで-6万円となり、傷が浅く済む。

こんな感じで固定ポジション数を70になるまで積み増すと、100円が99円になってもプラスマイナス0となる。やったね! しかし、103.5円を迎えると、固定ポジションはロスカットされる…。24.5万円マイナスになるが、これはヘッジトレードで得た利益のため、元本には影響しない。そして、本体ロングがプラス24.5万円になっているので、運用上はプラスになっていて喜ばしい、ということだ。

固定ポジションをヘッジしきったら?

固定ポジションを70までヘッジしきったら、もう下落はノーリスクとなる。むしろ、下落=トラップトレードの成功なので利益が出る。しかし、ヘッジトレードがストップロスにひっかかるとやはり勿体無い。そこで今度はヘッジトレード益で固定ポジションの損切りラインを107円に変更していく。これの意味は一体なんなのか?

これは、本体ロングで予定していた利益(49万)を100%確保したという状態になる。これで完全にノーリスク。利益を確保した上でノーリスク。あとは出口にくるまでトラップトレードで得た利益を元に複利を建てていき、複利に対して固定ポジションを追加していく等をすればよい。

くるくるワイドのリスクコントロールの上で重要なのが、固定ポジションである。両建て強い!

先消し、ピンポンなどの裁量トレード

この辺りは運用が面倒な人はやらなくてもいいだろうけれど、利益率が大きく変わるのでちゃんと理解しておいたほうがいい。 正直、ヘッジトレードをやる意味と、固定ポジションと複利を理解できたら、リスクを抑えて出口でプラスにすることは可能であると思う。 しかし、それだけだと利益が乗りにくい。

為替はトレンドが順行であったとしても、必ずどこかに抵抗線がある。抵抗線を迎えると、越えられずに反落することが多い。反落する可能性が高いんだったら、複利抵抗線付近で決済して、反落後に複利を建て直すというのが先消しである。

先消しして、反落しなかったら?

もし反落せずに抵抗線を越えたとしても、利益確定はできている。

先消しして、反落したら?

105円に抵抗線があるとして、その付近で先消し(決済)して、予想通り104円まで反落したとしたら、104円で買い直したら1円分利益を得られた上に、複利のチャンスが得られる。

本にも書いてあったけれど、先消しで損をするわけではないから、積極的に先消ししていくほうがよさそう。先消しを躊躇していたら下がってきて攻撃的複利ロスカットされてしまう…とかになると勿体無い。

ピンポン

ピンポンも、先消しで得られた利益を使ってレンジ相場でハイレバで勝負するというやつで、うまくいけば大きな利益が得られる。ただし、やや難易度が高めに感じる。失敗したとしても、複利の利益が飛んだだけだから問題ないという考えということだ。

固定ポジションも決済対象

固定ポジションに利益が乗っている状態で、トラップのレンジが外れそうだったら、固定ポジションを決済して仮想建値を下げる。仮想建値を下げて、出口も下げる。なぜ固定ポジションを決済してまで仮想建値を下げるのか?というと、トラップ値幅を拡げないため。トラップ値幅が拡がると、攻撃力が落ちるから。

具体例でいうと、100円スタートで107円出口だったけれど、逆行してしまい、96.5〜103.5円のトラップにしていたのに(トラップ値幅10銭刻み)、さらに96円まで落ちてトラップのレンジから外れたとする。こうなると、大幅逆行のときのルールに則って、100円を中心に線対称にレンジとゴールを広げることになる。たとえば、95〜105円とか。今までは103.5-96.5=7円だったので、7÷70=0.1で10銭刻みでよかったのだが、10÷70=0.14となり、14銭刻みになる。こうなると攻撃力が下がる…。

そこで、固定ポジションを決済して、その利益で仮想建値を95円始まり扱いにして、95〜102円としてしまえば、7円なので、トラップトレードの値幅は10銭刻みとなり、攻撃力は落ちない!ということだと私は理解した。

固定ポジションを持ってヘッジしきることも大事だけれど、攻撃力を保つことも大事で、要はバランスだと魚屋さんは書いていた。

まとめ

出口では必ずプラスになるように調整して、まずリスクヘッジしていこうというのが、魚屋さんの理論なのだと理解した。効率の良し悪しよりも生き残りやすい方針だと思う。最後に、のところで、退場してしまうということは、リスク管理できてないということとあった。本体建てた直後に天変地異があったら仕方ないけれど、そうでなければ、くるくるワイドはいい戦略だなぁと個人的に思う。今までの運用で勘違いしていたところがあったので、もっと積極的に固定ポジションを取っていこうと思った。

Herokuでpumaを使う。jemallocも使う。

Herokuで稼働しているアプリのApplicationServerをunicornからpumaに変更することにした。

理由は、Capybaraを3系にアップグレードしようとしたところ、デフォルトのサーバがpumaらしく、このまま放っておくとテスト系のライブラリもレガシー化していきそうだなと思ったため。

やったこと

unicorn系の削除

まず、Gemfileからunicorn系のgemの削除を行った。

unicornの設定ファイル、unicorn-worker-killerの設定の削除も忘れずに。

pumaのインストール

  • Gemfileにpumaを追加
  • config/puma.rbを作成
  • Procfileの修正

config/puma.rbは、Herokuのサイトを参考に行った。

devcenter.heroku.com

デプロイしてみる

これでデプロイしてみた。Herokuで利用しているDynoはStandard-2Xのため、以下のサイトの情報を読んで、WEB_CONCURRENCYを3、RAILS_MAX_THREADSを5に設定しておいた。

techracho.bpsinc.jp

これで1日様子見してみた。

修正を試みる

1日経って、R14のWarningが結構発生していた。R14はメモリの使いすぎでスワップが発生している状態なので、よろしくない。暫定的にRAILS_MAX_THREADSを4に落とした後、Herokuのドキュメントを読んでみる。

devcenter.heroku.com

puma_worker_killerを入れる

pumaの使用メモリが多すぎたことが原因なので、puma_worker_killerを入れて定期的に再起動させることにした。

gem 'puma'
gem 'puma_worker_killer'

これをbundle installする。 そして、config/initializers/puma_worker_killer.rbに以下を追加。

PumaWorkerKiller.enable_rolling_restart

jemallocのbuildpackを入れる

Herokuのドキュメントに、マルチスレッド環境ではjemallocを使ったら効果があると書かれていたので、入れてみる。 RubyKaigi2018でも、jemallocは効果的だという話だった。

patorash.hatenablog.com

しかし、Herokuだと使えないよなーと思っていたら、buildpackがあることを知ってびっくりした。ありがたい〜。

elements.heroku.com

まず、jemallocのbuildpackを入れる。

heroku buildpacks:add --index 1 https://github.com/mojodna/heroku-buildpack-jemalloc.git --app app_name

次に、Procfileを以下のように修正した。

web: jemalloc.sh bundle exec puma -C config/puma.rb

そして、デプロイ。

git push heroku master

とりあえず、これで様子を見てみることにする。

追記

WEB_CONCURRENCYを3、RAILS_MAX_THREADSを5に設定しておいた。

と書いていたが、結局R14がまた発生したため、WEB_CONCURRENCYは2に変更した。

やはりソフトウェア開発とマインドマップは相性が良さそう

この前の電脳書房さんの半額セールのときに結構買ったのですが、そのときに『ソフトウェア開発に役立つマインドマップ』を手に入れたので読みました。

ソフトウエア開発に役立つマインドマップ

ソフトウエア開発に役立つマインドマップ

マインドマップはノート術みたいなもので、中央のお題から外側に向かってどんどん枝を伸ばしていくようなものです。連想で枝を繋げていけるので、アイデアや感想をどんどん継ぎ足すことができ、ブレインストーミングなどにも向いています。今回の読書で内容をまとめるためにマインドマップを作ったのですが、以下のような感じ。

f:id:patorash:20180804082051p:plain

テンプレート集がよかった!

BOI(Basic Ordering Idea)という最初の枝が既に書かれているテンプレート集がすごくよさそうだった。何にもない状態からマインドマップを作っていくよりは、穴埋め的に枝を伸ばしていけるので、パターン化しやすいだろう。特にいいなぁと思ったものは、

など。マーケティング用の思考フレームワークのテンプレートもあった。こっちもよさそう。

マインドマップは応用の幅が広い

私も普段からマインドマップは使っていて、特にセミナー・勉強会などに参加したときのまとめは、マインドマップにしている。平鍋さんがマインドマップの特徴としてあげている、速記性・一覧性・プレイバック効果がとても相性がいいからだ。RubyKaigi2018とかOSO2018、この前あった中国地方DB勉強会もマインドマップにまとめている。マインドマップにまとめてあるから、どういう感じの勉強会だったかを雰囲気と共に思い出せて(プレイバック効果)、それを見ながらブログに記事を書けるため、ブログとの相性もいい。

本の中ではどう使っていたか

お客さんのところに要件を聞き取りに行き、その内容をまとめていき、UMLに起こすところまでやっていた。 マインドマップだと、話題ごとに枝(ブランチ)を伸ばしていくので、あの話はどの件についてだったか…などに強い。また、往往にしてよく話題が飛んだりすることがあるが、そういうときでもメモを書きやすい。

例であがっていた図書館システムの聞き取りの際の要望聞き取りテンプレートがよかった。

マインドマップモデリング

1章分使われていたけれど、これは「なるほどなぁ〜!」と思った。現実世界からドメイン分析モデルを作るのは、放っておくと個々人のスキルに大きく依存してしまうので、マインドマップ・モデル・テンプレートを作って、モデリング作業を定型的なものにしていくというのは、すごく効果的だなぁ〜と思った。これはこの章の著者の浅海 智晴さんが、大学のゼミで行った実習ベースの内容になっているということなので、信頼のおけるモデリング手法だと思う。

気づき

テンプレートにしといたら便利!っていうのはとてもよい気づきだったので、自分用にテンプレートをコツコツ作っていこうと思う。 ちなみに私が使っているマインドマップ・ツールはMindNodeです。有料だけれど、書きやすくてオススメ。

mindnode.com

elasticsearch-railsからElasticsearchの設定を変更する方法

Elasticsaerchのインポートを速くしたいと思って現在試行錯誤しているのだけれど、全然速くならなかったので一旦コードを元に戻そうかと思っている。しかし、設定を変更するための仕方を調べるの結構手間取ったので、とりあえずそれだけ残しておく。

Foo.__elasticsearch__.client.indices.put_settings(
  index: Foo.index_name,
  body: { refresh_interval: '30s' }
)

もしかしたら、MacのDockerってファイルシステムの関係?ですごく遅いという話があるので、それが原因なんだろうか?と推測しているけれど、推測するな、測定しろってことなのはわかっています、はい。

この記事を参考にしてやろうとしたんだけれど、Docker上のElasticsearchのメモリ割当とか増やしてなかったので、それが原因な気もする…。

kakakazuma.hatenablog.com

運用の視点での注意点に気付ける一冊

前回の投稿から3週間くらい経ってしまった。前回は中国地方DB勉強会に参加という話だった。

patorash.hatenablog.com

その後、紹介されていた本がKindleで半額になっていた。

(今はもう元の値段に戻っているので注意)

MySQLを普段使っていないので、その辺りに関してはかなり読み飛ばしたなんとか一通り読み終えた。

インフラの基礎でざっくり復習できる

フレームワークを使って動くものを作るところから入った人にとっては、概要が一通りまとまっていていいんじゃないかと思った。私はネットワークエンジニアからこの業界に入ったので、フムフムという感じで読み進めたが、久々にこういう内容に触れた気がする。

モニタリングの解説がいい

Mackerelの本でも、何をモニタリングするべきかを書いてあったけれど、こちらも監視とモニタリングについて丁寧に書いてあったので、普段この辺りを設定する機会が少ないアプリケーションエンジニア的には非常に嬉しい情報だなと思った。

まとめ

若手のアプリケーションエンジニアに読んでもらいたい

ある程度経験のある方ならば運用視点もしっかりしてきていると思うけれど、若手のエンジニアの場合はまだまだの場合もあるので、これらの基礎的なところを身につけてもらったほうが自信がついていくんじゃないかなと思った。

運用を考慮に入れるという視点を持ってもらうためにも、会社の推薦図書にしたいなと思った。

また、自分が知りたかった内容がスッキリとまとまっていてよかった。 ボトルネックの見つけ方、チューニングの仕方などは、正直コマンドを打ってもどこの数値を見ればいいのかよくわからないということがあったので、こういうふうにまとまっていると「読める、読めるぞ!」みたいなムスカ的な高揚感を得られて良い。

基礎・基本の章は何度も熟読し、モニタリング・チューニングの章は辞書的な感じで使っていけるかなと思う。

第23回中国地方DB勉強会に参加してきた

昨日はこれに参加してきました。

dbstudychugoku.github.io

LT枠で申し込んでいたのでLTもしましたよ!

今回は、AWSの藤原さん(@twingo_b)のAmazon Auroraの話と、オミカレの曽根さん(@soudai1025)のデータベースモニタリングの話で、どちらも内容が濃くて面白かったです。

togetterまとめ

まとめがありますので、当日の様子はこちらでご確認ください。

togetter.com

Amazon Aurora ベストプラクティスと最新アップデート

私自身はAWSほとんど使ったことがないのですが、RDSとAuroraは何が違うのかイマイチわかっていなかったので楽しみにしていました。

Auroraすごい

Auroraはハイエンドデータベースのような性能と可用性で、オープンソースデータベースのMySQLPostgreSQLのどちらかを選択できる。PostgreSQLの場合は、9.6互換。データベースを通常のRDSからAuroraに変更するだけで普通に動く。プログラム側の変更なしで高速化できる!5倍速いとかネット上で見かけた。

お試し用にt2のインスタンスが選択できる。ただし、MySQLのみ。PostgreSQLはまだ選べない。残念…。

速くて障害に強い理由は、DB専用に作られたストレージと、3つのアベラビリティゾーンに分散化されたストレージノードにストライピングされていること、各AZにコピーが作られていること、マスターとレプリカが同じストレージ上にあること、など。

AWSのサービスなので他のAWSとのサービスとの連携が強み。例えば…

  • プロシージャからLambdaの呼び出し
  • S3にバックアップ
  • IAMでアクセス制御
  • CloudWatchで監査ログやシステムメトリクスをとる

などなど。

一番嬉しいのは、ユーザーはアプリケーション最適化だけ考えればいいという点だと思う。DBのチューニングは、インスタンスサイズによって最初から適切な設定が行われているとか、最高。

RDSはスケールアップでもそんなに性能が上がらないことがあるけれど、Auroraにするとかなり上がる。そして移行は容易。Auroraにしない理由が見つからない…(お金以外で)。とはいえ、Auroraにすることでコスト削減っていう話もあった。管理コストも下がるし、商用DBからの移行で下がるという話だったかなと思う。商用DBからの移行用のサービス・ツールもある。データ移行サービス(DMS)と、スキーマコンバージョンツール(SCT)。

あとは新機能についての話題があったのだけれど、Aurora Serverlessが今後出るとか…。オンデマンドで起動するDBで、瞬発的にアクセスが多くなるようなものに使えるという話だった。ブログとか、BIツールとかによさそうとTLでは上がってたかなと思う。

Amazon Lockerすごい

話の導入前に、藤原さんがAmazonの本社に行ってたときの話があったのだけれど、Amazon Lockerという、買ったものがコインロッカーに届くというサービスが向こうでは始まってるらしい。日本でいうコンビニ配達みたいなものなのだけれど、人の手を介さずに荷物を受け取れるのですごくよさそう。流通も最適化されるだろうし、日本にも早く導入してほしい〜!

気づき

Auroraに変えるだけでパフォーマンスが向上する、というのは正直すごい。なんだかんだで多少は面倒臭いんだろうなとか思っていたけれど、移行が容易というのがよかった。PostgreSQLでもt2インスタンス使えるようになってほしい…。

今から始めるデータベース監視 ~あなたのデータベースは大丈夫?~

DBのモニタリング、スロークエリくらいは時々見ているけれど、あんまりちゃんとできていないしこれから趣味アプリでMackerelとか使っていくかーと考えていたので、ちょうどよかったです。

なぜモニタリングするのか?

モニタリングする理由は、

  • 素早く障害に気づくため
  • 原因究明のため
  • 未然に障害を防ぐため

そもそも障害が起きてしまってはいけないので、サービスが落ちる前に気づけるようにしないといけない。

監視の勘所

監視は、サービスが正しく動いているかをモニタリングするわけだけれど、定量データを取り続けることで、意図しない挙動に気づくことができる。

アクセス監視でいえば、

  • 特定のページにアクセスがない
  • 特定の時間帯でアクセスがない

サーバ監視でいえば、

  • batchのジョブ数の変化
  • キューの数の変化
  • DNS, NTPは問題ないか

サービス自体の監視でいえば、

  • ユーザーのプレイ状況(アクティブユーザー数など)
  • 検索ワード
  • 申し込みボタンのクリック数

これらで、サービスの課題に気づきやすくなる。

DBのモニタリング

DBのモニタリングにはMackerelってやつがいいよという話と、DBの得意・不得意を知っておくことの重要性と、おすすめの本の紹介など。あとは内部構造がわかれば何を監視すればいいかが見えてくるという話もあった。

DBの得意・不得意

MySQLはキー1発でデータを取ってくるのは得意だけれど、JOINとか、複数INDEXを扱うのが苦手なので、それを知っていれば、一部をNoSQLに逃すとかの対策が打てる。DBだけでなんとかしようとしてもどうにもならないことがあるのを知っておくことが大事。

おすすめの本

この本は知らなかったので、読んでみたい。というか、社内で読書会するかな〜。募ってみるか。

実践ハイパフォーマンスMySQL 第3版

実践ハイパフォーマンスMySQL 第3版

エキスパートのためのMySQL[運用+管理]トラブルシューティングガイド

エキスパートのためのMySQL[運用+管理]トラブルシューティングガイド

MySQLについてはこれらの本がおすすめらしい。もうMySQLは数年は触っていないな…。ちなみに家には結構MySQLの本ある(妻の持ち物とか)。

内部構造から学ぶPostgreSQL 設計・運用計画の鉄則 Software Design plus

内部構造から学ぶPostgreSQL 設計・運用計画の鉄則 Software Design plus

PostgreSQLに関してはこの本は本当にいい。この本はバージョン9.3の頃のやつなのだけれど、そろそろ新しいのが出るらしいという噂を聞いたので個人ではまだ持ってないけれど新しいのが出たら買う!

Mackerelプラグインのコードリーディングがいい

Mackerelプラグインのコードを読むと、どういうSQLでどういう値を取得しているかがわかって面白いらしい。オープンソースだからこそって話でよかった。

LT

LTは3枠あったのだけれど、キャンセルや交通機関の影響?で、私だけだった。

OSS-DB Silverという資格を取ったのだけれど、体系的にデータベースの基本知識を学べるのでおすすめですというLTをした。Goldいつ取るの?と煽られたので、頑張ろうと思う。

お悩み相談タイム

JPUGの中国支部長のお悩み相談タイム、のはずが、本人が用事で急遽参加できなくなったため、相談内容だけ残してみんなで喧々諤々の話し合いが行われた。割と、身もフタもない話が展開されつつも、面白い話題になってよかったと思います。雑にまとめたツイートを自分がしていたので、とりあえず貼っておくけれど、togetterまとめを見た方がいいでしょう。

自分なりの気づき

「SELECT INSERT or UPDATEはINSERT(UPDATE)中にSELECTの結果が変わってしまってはいけないからSELECTしている行もロックされる」という話題があって、なるほど〜!!と思いました。大量のデータで気軽にやったらシステム止まっちゃうよということだったので、忘れないようにしたい。

あとネットでは業務内容に絡むので話題にしづらいけれど、DB勉強会ではある程度踏み込んだ相談ができるので、そのあたり実際にDBに詳しい人たちに会って聞けるってのはほんま恵まれた機会だなと思います。

今回も充実した内容で楽しかったです!また参加したいと思います。

CircleCIのworkflowを使ってコードのチェックアウト、ライブラリのインストールを共通化した

私が担当している製品では、RailsのテストをCircleCIで4並列で動かしているのだけれど、これがいちいち各コンテナ毎にコードのチェックアウト、ライブラリのインストールをしているのは無駄じゃないかなー?と常々思っていました。これが、workflowを使ったら解決できそうだとわかったので、やってみることにしました。

目標を定義する

作業前は、以下のようになっていました。

  1. コードのチェックアウト・・・各コンテナ毎に実行
  2. bundle install・・・各コンテナ毎に実行
  3. yarn install・・・各コンテナ毎に実行
  4. rspec・・・各コンテナ毎に実行

作業後は、このようにしたい。

  1. コードのチェックアウト・・・1コンテナで実行
  2. ライブラリのインストール・・・並列実行
    1. bundle install・・・1コンテナで実行
    2. yarn install・・・1コンテナで実行
  3. rspec・・・各コンテナで実行

これならば、ライブラリのインストールを並列で行えるので、速度改善が見込めるんじゃないか?と思いました。

config.ymlを修正する

まずは、定義も何もないところから、workflowだけを書いていきます。

workflows:
  version: 2
  build:
    jobs:
      - checkout_code
      - bundle_dependencies:
          requires:
            - checkout_code
      - yarn_dependencies:
          requires:
            - checkout_code
      - test:
          requires:
            - bundle_dependencies
            - yarn_dependencies

まぁ、こんな感じでしょう。requiresを定義して、ライブラリのインストールが両方終わってからテストが実行されるようにしています。

その後は、以下のconfig.ymlを参考にして作ってみました。

github.com

順に書いてみます。

コードのチェックアウト

コードをチェックアウトして、キャッシュに保存しておきます。

jobs:
  checkout_code:
    docker:
      - image: circleci/ruby:2.5.1-node-browsers
    working_directory: ~/project
    steps:
      - checkout
      - save_cache:
          key: v1-repo-{{ .Environment.CIRCLE_SHA1 }}
          paths:
            - ~/project
  # 省略

ライブラリのインストール

bundle_dependencies

先ほどキャッシュしたコードと、過去のgemのキャッシュを戻した後、bundle installを実行し、またキャッシュに保存しておきます。

jobs:
  # 省略
  bundle_dependencies:
    docker:
      - image: circleci/ruby:2.5.1-node-browsers
    working_directory: ~/project
    steps:
      - restore_cache:
          keys:
            - v1-repo-{{ .Environment.CIRCLE_SHA1 }}
      - restore_cache:
          keys:
            - v1-bundle-{{ checksum "Gemfile.lock" }}
            - v1-bundle
      - run: bundle check --path=vendor/bundle || bundle install --path vendor/bundle --jobs 4 --retry 3
      - save_cache:
          key: v1-bundle-{{ checksum "Gemfile.lock" }}
          paths:
            - ./vendor/bundle
  # 省略
yarn_dependencies

こちらもbundle_dependenciesと同様に。yarnを行ってJSライブラリを入れて、またキャッシュに保存しています。

jobs:
  # 省略
  yarn_dependencies:
    docker:
      - image: circleci/ruby:2.5.1-node-browsers
    working_directory: ~/project
    steps:
      - restore_cache:
          keys:
            - v1-repo-{{ .Environment.CIRCLE_SHA1 }}
      - restore_cache:
          keys:
            - v1-yarn-{{ checksum "yarn.lock" }}
            - v1-yarn
      - run:
          name: Install dependencies
          command: yarn
      - save_cache:
          key: v1-yarn-{{ checksum "yarn.lock" }}
          paths:
            - ./node_modules
  # 省略

test

テストは、並列実行したいので、parallelismを指定しています。また、テストにDBが必要なので、DBのdocker imageの指定も行っています。

やっていることは以下の通り。

  1. 各コンテナで、ここまでのworkflowで行ったキャッシュを取得する
  2. DBのセットアップ
  3. RSpecの実行
  4. 結果の収集
jobs:
  # 省略
  test:
    docker:
      - image: circleci/ruby:2.5.1-node-browsers
         environment:
           RAILS_ENV: test
           TZ: "/usr/share/zoneinfo/Asia/Tokyo"
      - image: circleci/postgres:9.6-alpine-postgis
    working_directory: ~/project
    parallelism: 4
    steps:
      - restore_cache:
          keys:
            - v1-repo-{{ .Environment.CIRCLE_SHA1 }}
      - restore_cache:
          keys:
            - v1-bundle-{{ checksum "Gemfile.lock" }}
      - restore_cache:
          keys:
            - v1-yarn-{{ checksum "yarn.lock" }}
            - v1-yarn
      - run: bundle --path vendor/bundle

      # Database setup
      - run: bundle exec rake db:create
      - run: bundle exec rake db:schema:load

      # run tests!
      - run:
          name: run tests
          command: |
            mkdir /tmp/test-results
            TEST_FILES="$(circleci tests glob "spec/**/*_spec.rb" | circleci tests split --split-by=timings)"

            bundle exec rspec --format progress \
                            --format RspecJunitFormatter \
                            --out /tmp/test-results/rspec.xml \
                            --format progress \
                            $TEST_FILES

      # Save artifacts
      - store_test_results:
          path: /tmp/test-results

      - store_artifacts:
          path: /tmp/test-results
          destination: test-results
  # 省略

これでいいはずですが、./circleci/config.ymlの構造が間違ってないか、確認しておいた方がいいでしょう。

circleci config validate -c .circleci/config.yml

コマンドがない場合は、CircleCI local cliを入れておくこと。

circleci.com

CircleCIを実行する

workflowが正しく動いているか確認してみます。

f:id:patorash:20180624114543p:plain

おおー、動きました!!見た目は理想通りです。

config.ymlのリファクタリング

このままだと、重複コードがたくさんあるので、リファクタリングを行います。 こちらのブログを参考にさせてもらいました。

medium.com

共通部分を以下のように括り出しました。

references:
  defaults: &defaults
    working_directory: ~/project

  ruby_docker_image: &ruby_docker_image
    image: circleci/ruby:2.5.1-node-browsers
    environment:
      RAILS_ENV: test
      TZ: "/usr/share/zoneinfo/Asia/Tokyo"

  restore_code_cache: &restore_code_cache
    restore_cache:
      keys:
        - v1-repo-{{ .Environment.CIRCLE_SHA1 }}

  restore_gem_cache: &restore_gem_cache
    restore_cache:
      keys:
        - v1-bundle-{{ checksum "Gemfile.lock" }}
        - v1-bundle

  restore_node_module_cache: &restore_node_module_cache
    restore_cache:
      keys:
        - v1-yarn-{{ checksum "yarn.lock" }}
        - v1-yarn

これを適用していきます。

適用した完成品がこちら。

version: 2

references:
  defaults: &defaults
    working_directory: ~/project

  ruby_docker_image: &ruby_docker_image
    image: circleci/ruby:2.5.1-node-browsers
    environment:
      RAILS_ENV: test
      TZ: "/usr/share/zoneinfo/Asia/Tokyo"

  restore_code_cache: &restore_code_cache
    restore_cache:
      keys:
        - v1-repo-{{ .Environment.CIRCLE_SHA1 }}

  restore_gem_cache: &restore_gem_cache
    restore_cache:
      keys:
        - v1-bundle-{{ checksum "Gemfile.lock" }}
        - v1-bundle

  restore_node_module_cache: &restore_node_module_cache
    restore_cache:
      keys:
        - v1-yarn-{{ checksum "yarn.lock" }}
        - v1-yarn

jobs:
  checkout_code:
    <<: *defaults
    docker:
      - *ruby_docker_image
    steps:
      - checkout
      - save_cache:
          key: v1-repo-{{ .Environment.CIRCLE_SHA1 }}
          paths:
            - ~/project

  bundle_dependencies:
    <<: *defaults
    docker:
      - *ruby_docker_image
    steps:
      - *restore_code_cache
      - *restore_gem_cache
      - run: bundle check --path=vendor/bundle || bundle install --path vendor/bundle --jobs 4 --retry 3
      - save_cache:
          key: v1-bundle-{{ checksum "Gemfile.lock" }}
          paths:
            - ./vendor/bundle

  yarn_dependencies:
    <<: *defaults
    docker:
      - *ruby_docker_image
    parallelism: 1
    steps:
      - *restore_code_cache
      - *restore_node_module_cache
      - run:
          name: Install dependencies
          command: yarn
      - save_cache:
          key: v1-yarn-{{ checksum "yarn.lock" }}
          paths:
            - ./node_modules

  test:
    <<: *defaults
    docker:
      - *ruby_docker_image
      - image: circleci/postgres:9.6-alpine-postgis
    parallelism: 4
    steps:
      - *restore_code_cache
      - *restore_gem_cache
      - *restore_node_module_cache
      - run: bundle --path vendor/bundle

      # Database setup
      - run: bundle exec rake db:create
      - run: bundle exec rake db:schema:load

      # run tests!
      - run:
          name: run tests
          command: |
            mkdir /tmp/test-results
            TEST_FILES="$(circleci tests glob "spec/**/*_spec.rb" | circleci tests split --split-by=timings)"

            bundle exec rspec --format progress \
                            --format RspecJunitFormatter \
                            --out /tmp/test-results/rspec.xml \
                            --format progress \
                            $TEST_FILES

      # Save artifacts
      - store_test_results:
          path: /tmp/test-results

      - store_artifacts:
          path: /tmp/test-results
          destination: test-results

workflows:
  version: 2
  build:
    jobs:
      - checkout_code
      - bundle_dependencies:
          requires:
            - checkout_code
      - yarn_dependencies:
          requires:
            - checkout_code
      - test:
          requires:
            - bundle_dependencies
            - yarn_dependencies

だいぶすっきりとしたコードになりました。

テストは速くなったのか?

重複は排除できたのですが、肝心のテストの速度は速くなるどころか、少々遅くなっています…😢

原因は明白で、jobを細かく分けたため、docker imageのロードに時間がかかっているためです(各ワークフロー毎にだいたい30秒)。元々30秒かかっていたところが、3段階に分かれたので、30*3=90秒かかるようになりました。しかしこれも運次第で、テストが実行されたホストにdocker imageのキャッシュがあれば1〜2秒で終わるのですが、ないと30秒かかるという状態です。完全な条件下では、ライブラリのインストールが並列になるので、多少は速くなるのでは?🤔と思います。

とはいえ、好みとしてはこちらのコードなので、これでしばらく運用してみようかなと考えています。