三月までのエンジニアの目標を決める
2021年度はエンジニアとして対外的な成果を出したいと思っているので、3月までの目標を決めることにした。
Railsに引き続きコントリビュートする
OSS活動に力を入れていきたいと思っており、特に Rails周りをちゃんとやろうと思っている。 Railsのコードを読んで関心することは多いのだが、その中でいけてないと思うところはPRを出して行こうと思っている。
普段Railsを使っていると、欲しい機能がなかったりすることもあるので、そういう機能を作っていこうかな。 目標は3月末までに、5comiitくらい積んでいきたらと思っている。 リファクタやら小さい修正が大半になりそうだが、できれば新機能を提案というところをやりたい。
Rubyの処理系についての理解を深める
自作言語など小さいコードベースならともかく、Rubyは非常にコード量も多く理解をするのは大変だが、なんとか1つづつ切り開いて理解していきたい。 特にスレッド周り、yarrv周り、JIT周りの話がなんとなくしか理解できていないのでコードベースで理解していきたい。 ここら辺は、Rubyの仕組みという本をちゃんと読んだり、CRubyをデバッグしながら理解を進めていきたい。
定量的な目標がないといけないから、Ruby周りに関してはRubyの処理系に関するQiita記事を5本出すとかにしようかな
Pumaにコントリビュート
これは2月の初めから決めてた目標なので継続して行う。 よくあるrack周りのwebサーバーだが、現状コードベースで全て理解できているミドルウェアはpumaだけなので、良い感じのパッチを投げれたらなと思う
まとめ
3月までの目標はこんな感じにしよう。
結構Ruby周りやOSS開発にこだわっているが、現状自分が一番好きな言語はrubyだし、rubyを使って便利なものを作っているコミュニティに加わりたいという気持ちがあるので、この目標を達成できるようにちゃんと頑張ろうと思う。
Railsに初めてコントリビュートした
railsとpumaのPRを毎日読んでいって、2ヶ月以内にコントリビュートする個人的な挑戦を5日くらい前からやっていました。
結果、PRを出すことができて、初めてRailsにコントリビュートできました。
投稿したPRはこれです。
Rails7.0.0から、rubyのversionが2.7以上になったので、後方互換性のために実装されていた実装を消しただけです。 めちゃくちゃ小さい+機能を提案した訳ではないですが、しっかり目標は達成できてよかったです。
次はちょい高めの目標で、Railsに自分で機能を提案して採用してもらうことを目標にやっていきたいと思います。
あと、Pumaの方ではまだ目標を達成できていないので、引き続きやっていきたいと思います。
【Rails Code Reading 4回目】Break up abstract connection pool #41339
RailsのPRを読んで、記事にまとめています。
今回は、ActiveRecordの ActiveRecord::ConnectionAdapters::ConnectionHandler
のファイルの変更に関する実装を読んだ。
PRの概要にも書かれてるように、単純にファイルを分割しただけのPRの模様。
こういうPRでもmergeされんですね。
ちなみに、ActiveRecord周りの実装をまともに読んだのは今日が初めてでした。
ActiveRecord::Base
のクラスメソッドに、DBコネクションを確立したり、クエリ生成を実装するメソッド群があり、ActiveRecord::Base
を継承したインスタンスが、このコネクションやらクエリを使ってDBに書き込んでいく、
みたいな設計になっているみたいだった。
バリデーションや、コールバック、アトリビュートの負荷などは、ActiveModel
やActiveSupport
側に実装されているのも確認できた。
一方、Relation周りの機能はActiveRecordに実装されていた。
多少、Railsの内部実装がわかってきた気がする。
【Rails Code Reading 3回目】Read all records at once when using update with an array #40682
Rails Code Reading 3回目です。 今回は題目のPRを読んでみました。
このPRは今から2ヶ月以上前に作成されたのにも関わらず、誰からもコメントが付いてなく放置気味だったので、好奇心から読んでみました。
内容としては、ActiveRecord::Model
を継承したPerson
モデルがあった時、
people = { 1 => { "first_name" => "David" }, 2 => { "first_name" => "Jeremy" } } Person.update(people.keys, people.values)
で引数に渡されたpeople
で一括更新できるメソッドがあります。
これに関して、
現状、このupdateの内部実装はこのようになっています。
id.map { |one_id| find(one_id) }.each_with_index { |object, idx| object.update(attributes[idx]) }
idというハッシュをループして、一つ一つupdateしています。
これに対して、投稿されたパッチでは、idのうち重複したキーを排除した上でこの更新処理を行うようにしています。 これによって、sqlのupdateクエリが走る回数が減るというのが、実装者の目的だそうです。
例えば、[1,1,3]
でidが与えられた場合、[1,3]
と重複を削除してからupdateします。
とここまで読んできて、おや??と思いました。
この場合、id.uniqで十分では? 著者は、
merged_attributes = Hash.new { |hash, key| hash[key] = {} } id.size.times { |i| merged_attributes[id[i]].merge!(attributes[i]) }
と、第二引数のattributesの重複を削除していますが、そもそもidの要素で検索してupdateしているので、重複削除するならidの方ではないかと思った。
これは実装があんまりよくないから、マージされないパターンなのかなあ。
【Rails Code Reading 2回目】Reduce memory allocations #41335
Reduce memory allocations
今日読んでみたRailsのPRはここです。
タイトルは、メモリーアロケーションを減らすとのこと。
これはどんな実装が見れることやら。
と思って読んでみたらこんな感じでした。
DEC2HEX = (0..255).to_a.map { |i| ENCODE % i }.map { |s| s.force_encoding(US_ASCII) }
を
DEC2HEX = (0..255).map { |i| (ENCODE % i).force_encoding(US_ASCII) }
rangeオブジェクトを、to_aでArrayに変換せずに直接map
で扱うというもの。
benchable
というgemでベンチマークを取得すると、確かに20%弱ほどメモリアロケーションが減ってるとのこと。
大したことない実装ですが、ちゃんと根拠を示しているのはさすが世界的なOSSですね。
コミッターからは、HotPath(よく呼ばれる場所?)ではないが、まあniceでしょうみたいなコメントと共にmergeされていました。
ちなみに、この箇所の実装は、ActionDispatch
のルーティングで入ってきたユーザーからのリクエストパスをURIエンコードするモジュールで、
US_ASCII文字を入手するための配列を初期化してるみたいです。
【Puma Code Reading 2回目】Add Client#io_ok?, check before Reactor#register
ここ最近はpumaで採用されている、nio4rというgemについてちょくちょく調べてた。
どうやら、rubyのスタンダードライブラリではサポートされていない、epollやkqueueなどの非同期I/Oのシステムコールをサポートするライブラリのようだ。
rubyはネイティブの機能としてselectはサポートしているが、epollは採用していない。
またselectは計算量がo(n)であり、計算量がO(1)のepollを使った方が当然良いので、pumaではnio4rを利用している。
(最近ではnio4rを剥がす動きもあるっぽいのだが)
今日は、このnio4rに関係するこのPRを読んだ。
https://github.com/puma/puma/pull/2432
実装としては、Minitorにソケットを追加する前にソケットが閉じている場合にエラーが出てしまうので、無効かどうかを確認する`is_ok(socket)`というメソッドを追加し、Minitorに追加する前に`is_ok`を呼んで分岐をしているだけである。
ちなみに、CI上でテストを動かすときにしか再現できないバグらしいが、念の為このような実装を取り入れたそうだ。
pumaはversion3台から現在で、テストの量がかなり増えたので、それに応じてテストが通らないようなバグが発見されたのだろう。
今日のpumaリーディングはこれでおしまい。
nginx実践入門を読んだ感想
ある程度流し読みだが、nginx実践入門という本を読んだのでその感想を3分でかく。
この本を読もうと思ったモチベーションは、webサーバーという広く使われているソフトウェアの内部実装を知りたいと思ったから。
結論からいうと、この本ではnginxの内部実装について知れる情報はほとんどなかった。
nginxがlibuvやepollなどを使ってイベント駆動モデルで実装されているという話止まりで、詳細のc実装が載ってるとかは一切なかった。
内部実装についての記載はほとんどなかったが、その代わり組み込みモジュールや、設定ファイルの文法などが詳しく記載されていたり、コンテンツキャッシュやHTTPS終端、リバプロなど、用途別のnginxの設定方法が詳しく記載されていた。
本のタイトルにもあるように、具体的にnginxを使って何か作る人向けにとっては非常に良い本なのではないか。
一方、自分のような内部実装を知ろうというモチベーションの人には物足りない本であった。
【Puma Code Reading 1回目】:Bug Fix - MiniSSL::Socket#write - use data.byteslice(wrote..-1) #2543 を読んだ
毎日PumaとRailsの新しく投稿されたPRを読んで、2ヶ月以内にコントリビュートを目指す個人的な挑戦をしています。
今日は、PumaのPRを読んでみました。
まず新着のIssueをチェックしました
リバプロにNginxを使って、バックエンドにSSLモードで起動した ver5.2のpumaを使ってる時にバグが起こっているよう。
具体的には、44kbよりも大きいレスポンスをpumaが返すときにエラーが発生するようです。
これに対して、すでに5日前ほどにパッチが作成され、すでにmergeされているとのこと
このPRを読んでいこうと思いましたが、なんと1行。。
SSLで起動したPumaがクライアントにレスポンスを返す際に、
data.byteslice(wrote..-1)
とバイト列で返すようなパッチを作成していました。
ふーむ、これでなぜ正常に動くようになるのか。
PR上で議論が繰り広げられていましたが、個人的に疲れたので今日はこれくらいで読むのをやめました。
他にもpuma/test以下のテストスイートを読んでいました。
pumaってrspecではなく、minitestで書かれているんですね。
一旦今日はこれまでにします。
【Rails Code Reading 1回目】Add validate numericality in range #41022
今日からなんとなくやってみる。
rails/railsにmargeされたPRを軽く読み倒して、個人的な感想をまとめるやつ。
対象PR
今回はこれ。
ざっくり、ActiveModelのvalidationの1つである、numericalityに関するもの。
引数に、in
をkeyとしてvalueがRangeオブジェクトのハッシュを渡すことで、Rangeのvalidatetionを可能にする機能の追加である
ざっくり
validates :percentage, numericality: { greater_than_or_equal_to: 0, less_than_or_equal_to: 100 }
と書く必要があったのを、
validates :percentage, numericality: {in: 0..100 }
とかけるようになるらしい。 確かにこれは便利である。
実装方針
どうやっているか。 どうやら、ActiveModelをいじっている。Modelのバリデーション絡みの実装だから当たり前か。
NumericalityValidator:: CHECKS
にin: :in?
が追加実装されている。
むー。
あたりも色々触られている。
NumericalityValidator.check_validity!
で引数の型チェックみたいなことをしている。
では、ActiveModel
のバリデーションのメインの実装はどこだ。
おそらく、validate_each
メソッドっぽい。
この中身は、ほとんど実装が変わっていないが、たったこれだけでバリデーションが追加できるのか。
ActiveModelのライブラリとしての完成度の高さを伺えた。
あとでもう少し詳しく読んでみよう。
activemodel/lib/active_model/validations/numericality.rb