isucon練習会めも
友人とisuconの練習会を実施しましたが、全然できなかったので次に活かすためのメモ
インフラを担当したけど、ツールのインストトールやMySQLの操作コマンドがすぐに出てこず時間をかけてしまったので、当日の流れを自分がやったこと以外もまとめておく
isuconとは
やったこと
☆が今回自分でやったこと
事前準備
- 環境構築
- チーム分担
- 役割極め
- アプリ
- ☆ インフラ
- サーバーへのSSHを渡しておく
練習中
- ☆ レギュレーション確認 isucon.net
☆ マニュアル確認 github.com
-
- Slack連携
- サーバーにgitの設定
- メンバーにリポジトリのアクセス・編集権限を渡す
- ☆ 調べたこと、わかったことは一つのissueにどんどん書いていく
Makefile作成
- ビルドやベンチなど、何度も実行するものをまとめておく
- go build
- ☆アプリケーションサーバーからベンチ実行
- ☆SSHコマンドで実行するため鍵交換しておく
- ビルドやベンチなど、何度も実行するものをまとめておく
bench: ssh -l ubuntu [ベンチサーバーのIP] cd /home/isucon/isucari \&\& /home/isucon/isucari/bin/benchmarker -target-url http://18.183.105.62:80 -shipment-url http://13.231.82.53:7000 -payment-url http://13.231.82.53:5555
※ /home/isucon/isucari
配下に必要なログファイルがあるためそこに移動して実行しないといけないが、sshコマンドでディレクトリ移動した後のコマンド実行は \&\&
とエスケープする必要があり(ないと移動して実行されない)
- ☆ミドルウェアのバージョン確認
MySQL Server mysql> select version(); +-------------------------+ | version() | +-------------------------+ | 5.7.33-0ubuntu0.18.04.1 | +-------------------------+ 1 row in set (0.00 sec) Client ubuntu@ip-172-31-39-97:~$ mysql --version mysql Ver 14.14 Distrib 5.7.33, for Linux (x86_64) using EditLine wrapper Nginx $ nginx -V nginx version: nginx/1.14.0 (Ubuntu) Go $ cat go/go.mod module github.com/isucon/isucon9-qualify/webapp/go go 1.12 require ( github.com/go-sql-driver/mysql v1.4.1 github.com/gorilla/sessions v1.2.0 github.com/jmoiron/sqlx v1.2.0 goji.io v2.0.2+incompatible golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 )
☆リソースモニタリングツールを入れる
- ☆Netdata github.com
- Dockerもあるが、curlで入れれば良い
-
- Nginxでログを取るため、80ポートを利用するように変更
- kataribeでログ集計 github.com
作戦を立てる
- kataribeを見て、実行時間が大きいものから着手
- (1つ大きいのを変えると他の順番が大きく変わることがあるので、2-3つめを並行してやるのは効果が薄かったりするらしい)
N+1クエリ改善
- 最重要
- 1箇所のみならず、複数箇所あることも
ユーザの一括取得とカテゴリのオンメモリ化
スロークエリ集計
- MySQLのスロークエリ出力設定
- 最初は0以上にして全て出してみる
- pt-query-digestで集計 www.percona.com
- MySQLのスロークエリ出力設定
インデックス作成
- スロークエリとなっており改善必要なものから
- EXPLAINを見て
☆DBを切り出す
$ sudo systemctl status isucari.golang.service | grep Active $ sudo systemctl stop isucari.golang.service $ sudo systemctl status isucari.golang.service | grep Active $ sudo systemctl status payment.service | grep Active $ sudo systemctl status shipment.service | grep Active $ sudo systemctl status nginx.service | grep Active $ sudo systemctl stop nginx.service
- ☆ MySQLへ外部ホストからのアクセス許可設定
- ☆ 設定確認
sudo mysql -e "SHOW VARIABLES LIKE 'bind%';"
- ☆
mysqld.cnf
の修正 - ☆ ユーザー毎の許可アクセス元の確認
☆ アプリケーションの向き先変更
- ☆ アプリケーションでDBの設定をしている箇所を修正
- 今回
env.sh
のMYSQL_HOST=XXX.XXX.XXX.XXX
を変える
DBコネクションプールの利用
connect: connection refused
が出ていたためコネクションを増やそうとする- 最大接続数は151だったので一旦そのままで良いという判断
☆ DBチューニング
[mysqld] innodb_buffer_pool_size=512MB query_cache_type=1
→この辺りはあまり効果が出ず
練習後
- 振り返り会
$ echo "* hard nofile 64000" >> /etc/security/limits.conf $ echo "* soft nofile 64000" >> /etc/security/limits.conf
次回に向けて
今回のisucon9はこちらの本でも取り上げられていたので再度一人でやっておく(本当は事前に一人でやっておくつもりができなかったので次回までは必ず・・。今回もチラチラ見ながらやってた)
Webサービスチューニングコンテスト ISUCONのススメ (技術の泉シリーズ(NextPublishing))
やることが多いため、繰り返しやりコマンド等に慣れておくのが大切(本業でのトラブル対応の役にも立ちそう)
2020年の振り返り
2020年の振り返って
2020年はコロナ渦で世間は大きく変化。
しかし、自分は幸いにも小さな動きの速い会社でエンジニアリングをしており、1月以降ずっとリモートワークができたため比較影響少なくラッキーだったと思う。
が、コロナを免罪符に個人としてはあまり飛躍できなかったなぁというのが2020年の総評
Work
今の会社で2年目となり、サーバーサイドのエンジニアとして(チーム内では)そこそこバリューを出せるようになれたと思う。
やったこと
- サーバーサイドのメイン担当(一人保守)
- サーバーサイド兼Webフロント:1名(自分)、アプリ:2名(Android/iOS)チームのスクラムマスター
- サービスのクローズ
- GolangとProtocolBuffuersを利用したバックエンドの新規開発
- バグだらけな新規開発に一人でE2Eテストを導入(0から100本以上書く)
- CIテストの導入
- 初学者へのレビュー(Githubのテンプレートを導入しレビューをし易くしたり)
一昨年まではElixirを書いていたが新規サービスではGolangを採用。
1から作成するのでドメインやDB設計など学ぶことができたのと、E2Eテストを何もわからない状態から自力で導入することができたのは良かった。
テストは今まであまり書いたことがなく、後回しにしていたが今では書かないと手間過ぎてやってられない身体に。
Golangも静的型付け言語で自分には理解しやすく好きだった。
型とテストはいいぞ。
反省・できなかったこと
- リモートワークで少しだらける
- 副業
- サービスがリリースしなかった(ベータリリースのみでまだ顧客つかず)
- チームの開発スピードこれで良いの?
リモートワークは慣れるまで少しリズムが掴めない点があり、正直だらだらとサボることもあってしまった。
引っ越しした後は環境を整えたりして、集中するときとしない時が切り替えられるようになったので今は割と慣れてきたのかなと。
仕事はリモートを気にスタンディングにしたけど、自分にはあってたのか集中できるようになって良かった。
という記事をQiitaのアドベントカレンダーに書いたり qiita.com
個人的に問題は3と4で、使われないことでチームのモチベも上がらないのか、ふとPullReqを見ると7割私がやってて年末はかなり自分のモチベも下がってしまった。
Golangもチームに深い知識を持つ人がおらず、自分が書いてレビューしても本当にこれでいいの?と思うことも多くなり、外の世界も見てみたいという気持ちになっているのが最近。
Hobby
年初めは趣味のロードバイクを頑張っていい感じに調整できていたが、コロナでレースがことごとくなくなり中盤やる気と目標と見失う。
年内の目標にしてた、
はレース事態開催されなかったので未達成。
パワーも減量もできなかったので成果はなし。
後半は友人からの誘いもあり、来季に向けトレーニングを開始することができたので、来季は開催されると信じて仕事とトレーニングの両立を目指す。
その他、4年ぶりスノボに行けて楽しかった。
ずっと捨てようか考えてて、今年行かないようなら捨てようと思ってたので良かった。
Private
去年までは遊びや飲みに行くことも多かったけど、今年はイベントというイベントが中止、リモートでのみもほぼなくなったのでプライベート活動は比較少なかったのかなと。
やったこと
- 都心シェアハウスから郊外に引っ越し
- リモート飲み会
- 家事の機械化(食器洗濯機・お掃除ロボ)
- 貯金
- 信託投資
ほぼ会社の往復+モノを減らしたいということで、昨年途中からシェアハウスに住んでたけどコロナの問題もあり引っ越し。
シェアハウスは周りとのゆるい交流もあり好きだったのだけど、たった一人でも問題起こす人がいると辛いという学びを得るいい経験だった。(モノも強制的に減らすことができてヨシ!)
引っ越しはすでにリモート勤務だったので思い切って郊外に。
東京で家賃3万で住めるとか思いもしなかった。
家賃やリモートでほぼ自炊+遊びがないで浪費が抑えられ、結構な額を貯金できたのも良かった。
先行き不透明な中、節約やお金の勉強もし始め楽天証券開設して信託投資も始めたので、来年もつみたてNISA+αの範囲で投資をやって行く。
後、リモートでの飲み会も何回かやり、やってみるとなかなか楽しい。
移動しなくて良いので楽だし、離れている地元の友達ともちょいちょいやるようになりこれも良いと思った。
反省・できなかったこと
- 彼女を作る
- ミニマリスト化
もう自分も30歳だし身を固めることも考え、転職して1年経ち慣れてきたのでそろそろ活動しなきゃと思ってたのが1月。
結果、コロナで動けず結局何もせず。
というのが言い訳で、やりようはいくらでもあったのにやらなかったというのが正直なところ。
仕事も趣味も充実しているので特に困っていることはないのだけど、そろそろ干物になってまずい?(それも一種のスタイルなので否定はしないが)で、来年は動くだけでも動きたいなぁと。
後、モノをなるべく減らそうとしているものの、書類の整理や古いPCやスマホの処分がめんどくさくてやってないのもあるので、どこかでやりたい。
スーツも、もう切るような仕事につかないつもりなので捨てて良いのだけど、オーダーで買ったものなので捨てるに捨てられず悩み。
以上、
振り返りと目標をさらっと書くつもりが書き始めると色々と書いてしまったので目標は別途
担当していたサービスが終了したり、情勢や会社の方針が変わって当初の予定が変わったこともあるけど、コロナを免罪符にあまり活動してなかった気がするので2021年は、
- 環境を変え、コンフォートゾーンから抜ける
- 明確な目標を立て、定期的に振り返りと計画の修正
を意識してやっていきたいと思う。
2020-11-01(日) 初めてAtCoderのコンテストに参加した
きっかけ、事前準備
転職したことから存在は知っていたAtCoder
なんとなくやろうとしてたけどやらず、けどちょうど良いタイミングでその日の夜に開催が予定されていたため登録して参加
参加したのはこのBeginner Contest
使うのは今使っているGolangで。事前に例題で解き方を確認したり。
転職時のスキルテストとかで入力して解答みたいなのはやったことがあるので、操作で何か違和感があるところもなく。
標準入力は fmt.Scanf
でやるんだなぁとか思いつつ準備
本番
やってみた感想
21時からやったところ、解けたのはB問題まで。 やってみると処理はかけるけど、実行時間に制限があったりで、数学やアルゴの効率が良い処理方法がわかってないと、どうすれば良いか調べるのにかなり時間がかかった。
文系出身であまりやってないツケがきてるけど、このあたりエンジニアでやってくには必須なのでどこかで勉強したいところ。
とりあえずはAtCoderを解きながら身につけていこうかなと。。
A問題解答
A問題はFizBazみたいな、与えられた入力パターンに応じて文字列を返す形
自分の解
package main import "fmt" func main(){ var i int fmt.Scanf("%d", &i) if (i % 2) == 0 { fmt.Println("White") } else { fmt.Println("Black") } }
これは特に問題なくすんなり。
B問題解答
問題はB問題
最初の解答
package main import "fmt" func main(){ var n int fmt.Scanf("%d", &n) var sum int var b, c int for i := 0; i < n; i++ { fmt.Scanf("%d %d", &b, &c) for a := b; a <= c; a++ { sum = sum + a } } fmt.Println(sum) }
最初はN回、Aを1ずつ、Bになるまで増やしていった数の和
を求めれば良いかなとか思いgolang range 1 to 10
とかで検索して以下を参考に。
動くは動くけど、提出するとTLE(時間切れ)となりパスできない。
数学の知識がなく効率の良い求め方がわからず1〜Nの和
とかで色々調べた結果、ガウスが見つけた解法で効率よく計算できるみたい。
www.youtube.com (これがガウス定理か・・?と思ったけど全然違うようだった)
等差数列
というらしい
hensa40.cutegirl.jp
パスした解答
package main import "fmt" func main(){ var n int fmt.Scanf("%d", &n) var sum int var b, c int for i := 0; i < n; i++ { fmt.Scanf("%d %d", &b, &c) sum += (c - b + 1) * (b + c) / 2 } fmt.Println(sum) }
C問題解答
解答ならず atcoder.jp
B問題を解いた時点で1時間ぐらい。 Cに取り組むも、どうしたら良いかまでたどり着かず終了
なぜ入力例 2がNoで入力例 3がYesかわからず、ボードにかいてみたりしたが、
y - xが一致or0が三つの時?
とか考えたが解答まで至らず。。
解説
解説を見て、傾きが等しいかで判断すれば良いとまでは理解したけど、 Goでどうかくか?までできなかったので、後日解説を見ながら取り組む。
gcloudが動かなくなった話と、GPUを積んだGCEのメンテナンスについて
今日仕事でハマったことの話
gcloudが動かなくなった
久しぶりのサーバーメンテでgcloudコマンドを実行しようとしたところ、実行できなくなっていた。
※解決策を探してこられた方は「解決法」まで
-> % gcloud ERROR:root:code for hash md5 was not found. Traceback (most recent call last): File "/usr/local/Cellar/python@2/2.7.15_1/Frameworks/Python.framework/Versions/2.7/lib/python2.7/hashlib.py", line 147, in <module> globals()[__func_name] = __get_hash(__func_name) File "/usr/local/Cellar/python@2/2.7.15_1/Frameworks/Python.framework/Versions/2.7/lib/python2.7/hashlib.py", line 97, in __get_builtin_constructor raise ValueError('unsupported hash type ' + name) ... (省略) ... def __init__(self, algorithm=hashlib.sha256): This usually indicates corruption in your gcloud installation or problems with your Python interpreter. Please verify that the following is the path to a working Python 2.7 or 3.5+ executable: /usr/local/Cellar/python@2/2.7.15_1/Frameworks/Python.framework/Versions/2.7/Resources/Python.app/Contents/MacOS/Python If it is not, please set the CLOUDSDK_PYTHON environment variable to point to a working Python 2.7 or 3.5+ executable. If you are still experiencing problems, please reinstall the Cloud SDK using the instructions here: https://cloud.google.com/sdk/
gcloudが動かなくなったので試したこと
pythonがおかしくなったのか、アップデートとか色々試してみる。
→Python 2系を使っているようなのでまずこれをやったけど変わらず。
→じゃあバージョン指定しなければ?とやったけど変わらず。
そこで、
If you are still experiencing problems, please reinstall the Cloud SDK using the instructions here: https://cloud.google.com/sdk/
(Cloud SDKを再インストールしろ)
とあったので
やろうとしたが、ここでも同じく
ERROR:root:code for hash md5 was not found. Traceback (most recent call last):
らがでる。
どうしたもんかと思いまたもGoogle先生に
gcloud ERROR:root:code for hash md5 was not found.
とかと助けを求めると、いくつかヒットするので皆同じようなことが起こってるんだなと。
まずこれを参考に
バージョンの問題で、全部アップグレードしてしまえと。 やってみたところ結果は変わらず。
解決法
最終的に解決になったのはこちら teratail.com
正確には回答の参考になったという以下のリンク
$ brew switch openssl 1.0.2r Cleaning /usr/local/Cellar/openssl/1.0.2q Opt link created for /usr/local/Cellar/openssl/1.0.2q
brew upgrade
の後にswitchで切り替える必要があった。
なのでまず
$ ls /usr/local/Cellar/openssl
でどのバージョンがインストールされているか確認し、
表示されたバージョンに切り替える
$ brew switch openssl 1.0.2r Cleaning /usr/local/Cellar/openssl/1.0.2q Opt link created for /usr/local/Cellar/openssl/1.0.2q
これにてgcloudが動くように。結局なぜこうなったか理解までは至らなかったものの、解決までに色々試したり(DL待ちが長い)ググったりで2〜3時間かかってしまった。
GPUを積んだGCEのメンテナンス
GCEの仕組みについてわかっておらず、サーバーが勝手に再起動されていたのでその調査をして分かったこと。
保守を引き継いだサーバーでcronが仕込まれてないのに再起動された形跡があったので調べる。
StackDriverのログとかと追ったところ、再起動されたタイミングに
compute.instances.terminateOnHostMaintenance
というログが。
これを調べるとメンテナンスが、ライブマイグレーションで行われない際に実行されるものらしい。
インスタンス可用性ポリシーの設定 | Compute Engine ドキュメント | Google Cloud
こちらは、GCEには「ライブマイグレーション*1」と言う無停止でメンテナンス(ソフトやハードの更新)を行ってくれる素晴らしい機能があるのだけど、GPU積んでる場合は使えず再起動されるとのことであった。
メンテナンスも月1行われるけど、いつされるかはされる60分前にならないとわからない。
これが最初の導入者が理解して織り込み済みだったかは定かではないが、おそらくこれまで気にせず運用されていたと想定・・
60分前にならないとわからず、その時に別のホストに移すとは厳しいなと思いつつ、 一旦は月に 1 回実行であり、そこまでクリティカルなサーバー出ないとのことで対応は見送り。
こんなそんなで1日トラブル対応に費やした日であった。
*1:参照:apps-gcp.com
【学習メモ】Nuxt.jsビギナーズガイド 〜ログイン機能の実装②〜
昨日の続き。
ログイン機能がうまく働かないので調査。
まず、検証ツールを立ち上げてみると、errorが 。
vue.runtime.esm.js?2b0e:619 [Vue warn]: The client-side rendered virtual DOM tree is not matching server-rendered content. This is likely caused by incorrect HTML markup, for example nesting block-level elements inside
, or missing
. Bailing hydration and performing full client-side render.クライアント側でレンダリングされた仮想DOMと、サーバーサイド側でレンダリングしたコンテンツが異なってるので、HTML間違ってね?という指摘。
なのでまずはHTMLタグが正しくかけているか見直し。
テンプレートがおかしくないか調べ、誤りはないのでググる・・
すると、以下のような
Vue.js - Vueのプラグインを入れたらエラーが出てしまいました……|teratail
閉じタグやインデントの誤りを気にされていたようですが、そうでなくともこの指摘は発生します。 特にインデントは、人間が見やすいように入れるものであって、インデントの深さが適当でも、なんならインデントも改行も一切なくても、問題ありません。 ~~~ 当件の直接的な回答を得るのは難しいと思います。 「vue-data-table」がどんな構造のDOMツリーを最終的に吐き出すのかわからないと何とも言えません。
ふむふむ、プラグインによって、人が書いた想定しているものと異なる場合があると。
これと気になったのが、
{ src: '~plugins/vue-data-tables', ssr: false }ssr: falseとなっているのが気になります。 nuxt.jsを使うということはSSRするはずなので、ここをfalseにしちゃうと、サーバー側とクライアントで違いが出る言われてしまうのではと推測しました。
という点。 ここはまさに昨日やったところで、確かにUniversal(SSR)でアプリを作成しようとしているのに、
ssr: false
にするのはおかしい。試しに、戻して実行してみる。
nuxt.config.js
** Plugins to load before mounting the App */ plugins: [ // { src: '@/plugins/element-ui', ssr: false } // SSRをサポートしていないプラグインを追加すると`Unexpected token 'export'`になるため、`ssr: falseとする` '@/plugins/element-ui' ],すると今度はエラーが発生せず・・
エラーになっていたはずのnotifyも問題なく動作する。 エラーの解消のため、と思い入れた
plugins: [ '@/plugins/element-ui', '@/plugins/vue-notifications', ],の
vue-notifications
がダメだった?闇雲に解決策を試すのではなく、何をやろうとしているのか理解しないといけないという学び。
ただ、検証ツールにerrorは出なくなったもののまだアカウント作成はできないので引き続き調査は続く。
【学習メモ】Nuxt.jsビギナーズガイド 〜ログイン機能の実装①〜
バックエンドメインでしたが、最近はNuxt/TypeScriptで作られてフロントも触ることも多くなり、 体系的な知識不足を感じたのでNuxt.jsビギナーズガイドを先週あたりからやっている。
CHAPTER4の中規模以上の開発を意識した〜の認証機能の実装部分を。
テキストを見ながら写経をしていたところ、ずっとこのエラーが。 'export'と書いているところを調べても
export default
とデフォルトのエクスポートを宣言している以外は特になし。何度Kindleを見返してもわからず、サンプルコードを見てどこが違うか見比べて見てタイポは多少見つけたものの、 エラーの原因ではない箇所。
app/store/index.js
export const store = () => ({ isLoggedIn: false, user: null })(なぜここでstoreを宣言。。stateでした。)
そもそも原因である「Unexpected token 'export'」の意味がよくわからないので、エラーメッセージでググるといくつかヒット。
Qiitaの以下記事を見て、この通りに対応すると解消 https://qiita.com/kozakura16/items/5cae173bcdc0dff7f9a7
nuxt.config.js (修正前)
plugins: [ '@/plugins/element-ui', '@/plugins/vue-notifications', ],(修正後)
plugins: [ { src: '@/plugins/element-ui', ssr: false } ],→SSR(mode: 'universal'時)、SSRをサポートしていないプラグインを追加するとエラーとなるため、明示的に
ssr: false
としてあげる必要がある模様。→本に指定がなかったかチェックしたところ、サンプルではこの記載をしてないので変わった? TODO: プロジェクトを新規作成時の選択肢もサンプルと異なっていたので、どう変わったか調べて見たい。
プロジェクト作った時も、
const pkg = require('./package') module.exports =はなかった。
export default { mode: 'universal', srcDir: 'app', router: { middleware: [ 'auth-cookie' ] }, /* ** Headers of the page */ head: { title: process.env.npm_package_name || '', meta: [ { charset: 'utf-8' }, { name: 'viewport', content: 'width=device-width, initial-scale=1' }, { hid: 'description', name: 'description', content: process.env.npm_package_description || '' } ], link: [ { rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' } ] },その前にnotifyがないと行ったエラーになるのでpluginsに
vue-notifications
追加で記載したが、 本にはelement-uiをインポートしていれば使えるという記述があり、確かに消しても使えたので、ここの読み込みが失敗していたのだと気づく。→TODO: element-uiについてもあとで調べる
これでサーバー起動することはできたが、登録に失敗するため、引き続きどこがおかしいか調べる。。 TODO: まずはconsole.log仕込んでどのようなエラーになっているかから。
【メモ】私が愛する Elixir/Erlang の楽しさと辛さ
元資料が素晴らしいためまとめる必要など全くないのですが、自己の整理のため、箇条書きにて書き起こししているものです
- Elixirのメリット
- Elixirの関数型について
- オススメの学習方法
- プログラミングElixirを読む前に
- 活用の鍵
- Elixirのリスト
- ※糖衣構文:プログラミング言語において、読み書きのしやすさのために導入される書き方であり、複雑でわかりにくい書き方と全く同じ意味になるものを、よりシンプルでわかりやすい書き方で書くことができるもののこと 語源は「取り扱いやすい」を意味するsweetの第一義が「(砂糖のように)甘い」であることから https://eow.alc.co.jp/search?q=syntactic+sugar
- 例)[1, 2, 3] = [1 | [2 | [3 | ]]] 再帰=headとtailの繰り返し(プログラミングElixir参照)
- Enum
- パイプライン
- (JavaScriptにもパイプラインが導入されるかも?) * http://tc39.github.io/proposal-pipeline-operator/
- do
- マクロ、ビヘイビア、プロトコルを後回しにして良い理由
- Elixir/Erlang の辛さOTPについて
- 断捨離して良いもの
- モジュールの2つの役割
Elixirのメリット
- Web開発のための優秀なツール完備
- サーバ構成をシンプルに保てる
- コードをシンプルに保てる
- 夜眠れる
なぜ流行らないのか?
- 学習コストが高い(と思われれている) × 納期が短い = 採用されない
- 学習コストが高い(と思われれている)・・並行処理、関数型、マクロと難しそうな概念を学ぶ必要がありそう
伝えたいこと
- Webシステムの開発ならば、学習コストは高くも低くもない
- 加えて、安全性が高く、生産性が高く、それ以外の性質は程良い
- →Elixirは学習コストのわりには恩恵が大きい
難しいのであれば学ぶことを取捨選択して学習コストを下げる以下、選択して学習することで学習コストを下げてElixirの恩恵を受けることができる
- 並行処理、マクロ、ビヘイビア、プロトコルは最後に学ぶ
- ここで紹介した以外はリスト操作に習熟してから学ぶ
EVMの並行処理について巷の良い話
- アクターモデル →?
- メッセージパッシング →?
- メモリを共有しないからシンプル
巷の悪い話
- 概念自体の学習にコストが必要
- (共有メモリ+スレッドより楽だけど)メッセージパッシングですら人類には早かった
→ 前提条件を変更する
PhoenixでWebサービスを開発するなら、逐次処理を書くだけで、軽量プロセスの恩恵を享受できる
→よってしばらく並行処理は忘れて良い、学習コストを払うのも後回しで良い
以下のように考えるとわかりやすい
「Erlangで作られたWebサーバは一つの接続ごとに一つのプロセスを生成する。10万個のチャットのセッションが必要なら10万個のWebサーバが起動する。仮にその中の一つが落ちたとしても、誰も気にしない」
EVM=OSと似たプロセスの仕組みを持つプロセスとは
- 独立したCPUを持つ
- 独立したメモリを持つ
- ネットワーク上の一意な住所を持つ
どういうことか
- EVMは各軽量プロセスにCPUを使う権限を与える
- 軽量プロセスが一定量処理すると別の軽量プロセスに権限が移る
メリット
- 一つの軽量プロセスがCPUを占有しづらい
- (極論)軽量プロセスないで無限ループしてもシステム全体に影響が出にくい
- 軽量プロセス内はシンプルな逐次処理となる
- Elixirであれば同じ処理を簡単な逐次処理として記述できる
→「どこでコンテキストが切り替わるのか」考慮不要
ネットワーク上の一意な住所を持つとは?
- EVMは各軽量プロセスに一意なアドレスを割り当てる
- PIDは複数のEVMで構成されたクラスタ上で一意
(利点)
- 軽量プロセス同士がメッセージを送受信し、共有メモリを使わずに連動できる
- 軽量プロセス同士がシグナルを送受信し監視や管理(再起動など)ができる
(注意点)
- とはいえ、高頻度に再起動するとCPU負荷が上がるため注意
- プロセスを使いまわすと、プロセス停止によるメモリ解放が行われないため、メモリリークしないように気を配る必要がある
速さはおまけ
- 軽量プロセスは耐障害性の向上するための仕組みであり、速さはおまけ
=> OSと似たことをしているのに早いわけがない
- Node.jsは他の動的言語と比較すると圧倒的に速い(V8というものが頑張っているらしい)
- 結果として、Elixir => 並行処理の学習コスト不要で、ほぼほぼに遅くないとても楽で安全な言語
Elixirの関数型について巷の良い話
- 副作用少ない
- データ処理に集中
- gotoは処理フローの迷子、○○は状態の迷子
巷の悪い話
- 概念自体の学習にコストが必要
- 敷居が高すぎる
- しかも何が嬉しいか不明
- 世間一般には関数型言語が難しいとされている
Elixirは純粋な関数型ではない
- そのため関数型由来の仕組みが少ない
- ○○由来の仕組みが少ない
- 複雑さが排除されていて、理解するための文脈が少ない
- 他者の記述したコードを読むのが楽
(注意)マクロ自体やマクロ由来の仕組みは複雑さを招きやすい
純粋だと言われる条件
- 参照透過な関数で表現できることが多い → ?
- e.g. 演算子、例外、IOなどが関数で表現されている
オススメの学習方法
- プログラミングElixir:p11-157を読む
- Elixir入門:11プロセス、16プロトコル、20ビヘイビア以外
- ElixirSchool:基礎の章だけ読む
- Phoenix GUIDES:Up and Runningから始める(始めはChannel,Presenceを読み飛ばす)
プログラミングElixirを読む前に
活用の鍵
Elixirのリスト
- リストとはペアで作ったリンクリスト
- [1]というのは[1 | ]の糖衣構文
※糖衣構文:プログラミング言語において、読み書きのしやすさのために導入される書き方であり、複雑でわかりにくい書き方と全く同じ意味になるものを、よりシンプルでわかりやすい書き方で書くことができるもののこと
語源は「取り扱いやすい」を意味するsweetの第一義が「(砂糖のように)甘い」であることから
https://eow.alc.co.jp/search?q=syntactic+sugar
例)[1, 2, 3] = [1 | [2 | [3 | ]]]
再帰=headとtailの繰り返し(プログラミングElixir参照)Enum
- Enumモジュールで定義されている関数は様々な言語で類似する名前と機能が提供されているため、一度覚えると他の言語でも応用が効きやすい
- よく使われるものから覚える
- mapが多い
- (Enumよりforの可読性が高いことがある(forを使えという理由でfilter_mapという関数がEnumから削除された))
パイプライン
- Shellでいう | (パイプ)みたいなもの
- パイプラインの存在意義=可読性の上昇
- 関数定義に一貫性が生じる
- パイプラインが使えるように関数を定義する癖がつく
- 第一引数 = 操作前データ
- 戻り値 = 操作後データ
(JavaScriptにもパイプラインが導入されるかも?)
* http://tc39.github.io/proposal-pipeline-operator/do
- doの法則
- do ブロックを引数に取るものは、変数のスコープを持つ値(戻り値)を持つ
- 例) defmodule, def, case, if, forなど
- ブロックにスコープと戻り値があるため、ブロックネストの深いところで、上位ブロックの変数を再束縛(する必要がない|できない)
- よって「この値はどこから来たのか?」を調べる手間が減り、可読性が上がる
マクロ、ビヘイビア、プロトコルを後回しにして良い理由
- Webシステムの開発に限定すると、新たに自作のこれらを定義しなくとも開発できるから
Elixir/Erlang の辛さ
OTPについて
- OTPは高性能もWebシステム開発ではほぼ必要ない
- OTPは高性能だが断捨離する
- モジュールで設計(抽象化)するなら関数中心で考える
- Elixirの用途を割り切る
- APIサーバをElixirで書く
- APIサーバはステートレスにしてオートスケーリングに対応する
- キャッシュやDBなどは既存品を使う
断捨離して良いもの
- ETS
- オンメモリDB
=>キャッシュ用途に便利だが、共有メモリでスレッドプログラミングする世界に逆戻りする
- DETS
=>オンディスクDB
- Mnesia
=> ETSとDETSの上に構築された分散DB
APIサーバにDBのロールを担わせるため、サーバのオートスケーリングの難易度を上げる
=>末尾再帰する関数をフルネームで評価すると発生する
* プロセスを越境してコードホットスワップがシステム全体に適用されている時せいは見たことない
https://blog.stenmans.org/theBeamBook/* 起動しているプロセス全てに対して、完全にコードホットスワップに対応していると保証するコストより、再起動コストの方が低い
* サービスの無停止を実現するならば、Blue-Green Deploy(※)で良い
※デプロイをより安心して行うために、サーバの内容を変更する際には既存のサーバに手を加えるのではなく、新規に作り直して切り替える、という方法
https://www.publickey1.jp/blog/14/blue-green_deployment.html
- これらの良さは「すごいErlangゆかいに学ぼう!」を参照
→APIサーバを作るだけなら、無駄な複雑性をシステムに呼び込まないよう、利用を避ける
- ETSはEcto内でPrepareしたDB Queryを一ノード内でキャッシュして使いまわすなどに利用されている
- Elixir入門直後はこのようなチューニングを実績あるライブラリに任せる
混線する関数型と○○
- 一般的に○○の開発者たちは、Elixirのモジュールを最小のコード構造とみなして関数を忘れてしまう(それ故、全てをモジュール化する)
- https://groups.google.com/forum/m/#!topic/elixir-lang-talk/Ib11890jfmY
モジュールの2つの役割
- 関数をまとめる
- 構造体や型を定義する
- →クラス定義に見える
- クラス定義に見えると何がいけないのか?
- Elixir で OO ができる!と勘違いして, 無駄な抽象化を持ち込み, コードが複雑化する事案が発生しやすい
- 例えば, Ecto で repo という単語を使っているため, リポジトリ パターンによる抽象化のベストプラクティスを持ち込む話しなど
- プロトコルとビヘイビアの混合
- Trait などの知識があると, プロトコルやビヘイビアは, いつ, ど こで使うのか?という判断が OO に紐付いてしまう
- プロトコルはデータが主役
- ビヘイビアは関数が主役
- Elixir は, プロトコルとビヘイビアの定義がモジュールに紐付いて いる。プロトコルはデータに, ビヘイビアは関数に紐づけて考えると良い
マクロは高性能
- マクロは言語作者が楽をする機能
- あまりに強力すぎる
→ Elixir は, あらゆるものがマクロで実装されている
- マクロの理解は, Web システム開発においては必須ではないが、マクロが読めないと正しい理解に時間がかかる機能もあるため、Elixir に慣れてからゆっくり学習するとよい