Tech Racho エンジニアの「?」を「!」に。
  • Ruby / Rails関連

週刊Railsウォッチ: Rubyの可変長アロケーションプロジェクト、サーキットブレーカーgem、EC2-Classicが終了へほか(20210804後編)

こんにちは、hachi8833です。

週刊Railsウォッチについて

  • 各記事冒頭には🔗でパーマリンクを置いてあります: 社内やTwitterでの議論などにどうぞ
  • 「つっつきボイス」はRailsウォッチ公開前ドラフトを(鍋のように)社内有志でつっついたときの会話の再構成です👄
  • お気づきの点がありましたら@hachi8833までメンションをいただければ確認・対応いたします🙏

TechRachoではRubyやRailsなどの最新情報記事を平日に公開しています。TechRacho記事をいち早くお読みになりたい方はTwitterにて@techrachoのフォローをお願いします。また、タグやカテゴリごとにRSSフィードを購読することもできます(例:週刊Railsウォッチタグ)

🔗Ruby

🔗 Rubyの可変長アロケーションプロジェクトがフェーズIIに


#4680より


つっつきボイス:「Rubyのアロケーターをリニューアルして高速化するプロジェクトが進行中なんですね」「スゴい」「これはRubyKaigiに向いていそうなお題」「そのうち解説記事が出るといいですね」

サマリー:

  • 現在はアロケーション高速化のために、スロットサイズの異なるページをプールで使っている。要求されたサイズを収納できる最小のスロットサイズでオブジェクトが割り当てられる。これによって、フリーリストベースのアロケーションアルゴリズムに立ち返り、アロケーションのパフォーマンスが大幅に向上した。
  • サイズプールの成長パターンに合わせてヒープの成長アルゴリズムが変更された。ヒープのアロケーションと解放を繰り返すと「成長ヒープ」とみなされ、サイズの安定したヒープと異なる扱いになる。
    同issueより

🔗 time_up gemでRubyコードを計測する(Ruby Weeklyより)

testdouble/time_up - GitHub


つっつきボイス:「time_upというgemを作って測定してみた記事だそうです」

「測定したいコードにTimeUp.startでタイマー名を:processing:queryingのように複数指定できる」

# 同記事より
transactions = TimeUp.start(:querying) {
  Transaction.where(user: @user)
}

process_timer = TimeUp.start(:processing)
categorized_transactions = group_by_category(transactions)
monthly_spending = arrange_by_month(categorized_transactions)
process_timer.stop

payments = TimeUp.start(:querying) {
  PaymentFinder.find(@user, monthly_spending)
}

render json: TimeUp.start(:presenting) {
  present_summary(
    spending: monthly_spending,
    payments: payments
  )
}

「するとTimeUp.timerでタイマー名ごとのカウントや最大最小平均が取れるようになる」

# 同記事より
> TimeUp.elapsed(:presenting)
=> 1.0050820000469685
> query_timer = TimeUp.timer(:querying)
> query_timer.count
=> 2
> query_timer.timings
=> [0.5051469999598339, 0.8050760000478476]
> query_timer.min
=> 0.5051469999598339
> query_timer.max
=> 0.8050760000478476
> query_timer.mean
=> 0.6551115000038408

TimeUp.all_statsすると全タイマー名の測定結果を取れる」

# 同記事より
> TimeUp.total_elapsed
=> 2.822688000043854
> TimeUp.all_stats
=> {
  :querying=>{:elapsed=>1.3102230000076815, :count=>2, :min=>0.5051469999598339, :max=>0.8050760000478476, :mean=>0.6551115000038408},
  :processing=>{:elapsed=>0.5073829999892041, :count=>1, :min=>0.5073829999892041, :max=>0.5073829999892041, :mean=>0.5073829999892041},
  :presenting=>{:elapsed=>1.0050820000469685, :count=>1, :min=>1.0050820000469685, :max=>1.0050820000469685, :mean=>1.0050820000469685}
}

TimeUp.print_detailed_summaryで整形できる」

# 同記事より
> TimeUp.print_detailed_summary

=================================================================================
   Name     | Elapsed | Count |   Min   |   Max   |  Mean   |  Median  |  95th %
---------------------------------------------------------------------------------
:querying   | 1.31028 | 2     | 0.50520 | 0.80507 | 0.65514 | 0.65514  | 0.78221
:processing | 0.50314 | 1     | 0.50314 | 0.50314 | 0.50314 | 0.50314  | 0.50314
:presenting | 1.00508 | 1     | 1.00508 | 1.00508 | 1.00508 | 1.00508  | 1.00508

「測定ポイントごとのデータを一括で管理しつつ簡単な集計が行えるようですね: 記事タイトルにはベンチマークとあるけど計測という方が近いかな」「なるほど」「通常だと自分でグローバルな場所に値を保存することになるでしょうけど、time_up gemを使うと一括で管理できる: 知っていたら使うかも👍」

🔗 メモリ上のRuby文字列を見る


つっつきボイス:「お〜、 /procファイルシステムからプロセスの生ユーザーメモリ空間を解析して、その中からRStringとして使われている部分を参照して復元したんですね」「これは面白い❤️」「RStringなら比較的やりやすいでしょうね: これがRObjectだと難易度が上がりそう」「こういう作業は理解が深まりますね: Ruby内部でどのようにメモリをマッピングして使っているかを学ぶのによさそう👍」

🔗 サーキットブレーカーgem 2種類

yammer/circuitbox - GitHub

wsargent/circuit_breaker - GitHub


つっつきボイス:「Jeremy Evansさんの近著『Polished Ruby Programming』↓を読んでいて、サーキットブレーカーはgemでやる方が楽という記述があったので探してみました」

「今さらですけどサーキットブレーカーって何でしょう?」「サーキットブレーカーはKubernetesやマイクロサービスなどでサービス同士がチェインする部分で使われるパターンですね」「お〜」「以下のIstioドキュメントに簡単な説明があった↓」

参考: Istio / Circuit Breaking

サーキットブレーキングは、レジリエントな(回復力のある)マイクロサービスアプリケーション構築で重要なパターンです。サーキットブレーキングを使うと、障害やレイテンシの急増、その他のネットワークの特異性による望ましくない影響を制限するアプリケーションを書けます。
istio.ioより

「たとえば、あるサービスがリクエストを返すために別のサービスに問い合わせる必要があってみたいなチェインが繰り返されると、途中で1箇所でも障害が起きたときにさんざん待たされた末に全部コケたりしますけど、そういうのをうまく扱うためにサーキットブレーカーを挟む感じですか?」「だいたいそんな感じ: タイムアウトなども細かく管理できます」「お〜」「サーキットブレーカーはKubernetesやIstioのようなサービスには必ずあって、マイクロサービスのコントロールプレーンの話題にもよく登場します」「なるほど」

参考: コントロールプレーン | TED用語集 | 東京エレクトロンデバイス

参考: 分散サービス環境へのCircuit Breakerの適用 - LINE ENGINEERING



engineering.linecorp.comより

「サーキットブレーカーでは、たとえばチェインしているサービスの手前の段の処理が軽くて奥の段の処理が重いとすると、軽い手前のサービスがリクエストをたくさん投げると奥の処理が詰まってしまうので、奥の段の処理が詰まっていることをサーキットブレーカーが何らかの形で検知すると、手前の段で早期に失敗させる、というような処理を行います」「なるほどそういう感じですか」

「でgemの方を見てみると、Circuitboxは文字どおり失敗時に転送している↓」

Circuitbox.circuit(:your_service, exceptions: [Net::ReadTimeout]) do
  Net::HTTP.get URI('http://example.com/api/messages')
end

「以下のコンフィグでタイムアウトやスリープやスレッショルドなどを指定できる↓」

# github.com/yammer/circuitboxより
class ExampleServiceClient
  def circuit
    Circuitbox.circuit(:your_service, {
      # exceptions circuitbox tracks for counting failures (required)
      exceptions:       [YourCustomException],

      # seconds the circuit stays open once it has passed the error threshold
      sleep_window:     300,

      # length of interval (in seconds) over which it calculates the error rate
      time_window:      60,

      # number of requests within `time_window` seconds before it calculates error rates (checked on failures)
      volume_threshold: 10,

      # the store you want to use to save the circuit state so it can be
      # tracked, this needs to be Moneta compatible, and support increment
      # this overrides what is set in the global configuration
      cache: Circuitbox::MemoryStore.new,

      # exceeding this rate will open the circuit (checked on failures)
      error_threshold:  50,

      # Logger to use
      # This overrides what is set in the global configuration
      logger: Logger.new(STDOUT),

      # Customized notifier
      # overrides the default
      # this overrides what is set in the global configuration
      notifier: Notifier.new
    })
  end
end

「もうひとつのcircuit_breaker gemの方はincludeして使う感じで、できることは上と似ているかな」「なるほど」

# github.com/wsargent/circuit_breakerより
require 'circuit_breaker'
class TestService
  include CircuitBreaker

  def call_remote_service() ...

  circuit_method :call_remote_service

  # Optional
  circuit_handler do |handler|
    handler.logger = Logger.new(STDOUT)
    handler.failure_threshold = 5
    handler.failure_timeout = 5
    handler.invocation_timeout = 10
    handler.excluded_exceptions = [NotConsideredFailureException]
  end

  # Optional
  circuit_handler_class MyCustomCircuitHandler
end

「サーキットブレーカーをちゃんと使ったことはまだありませんが、サービスが大規模に連携するようになるとこういうgemが欲しくなるのかも」

🔗 サーキットブレーカー自作は避けたい

「そういえばJeremy Evansさんが『サーキットブレーカーを自分で作るとつらいよ』みたいなことを書いていました」「はい、この種の機能を自作するのは避けた方がよいと思います」

「たとえば自分の実装で排他制御周りをしくじってリソースが永遠に開放されなくなってしまうバグを踏むと、サーキットブレーカーが誤動作してすべてのリクエストが止まってしまったりします」「ありゃ〜それはつらい」「かといって逆に排他制御の設定を厳しくしすぎると、今度はサーキットブレーカーがボトルネックになってしまったりします」「う〜む」「どちらもサーキットブレーカー的なものを自分でちょろっと作ったときにありがちなバグなので、自前実装は避けたい」

参考: 排他制御 - Wikipedia


Azureのドキュメントによると、以下の『Release It!』という書籍でサーキットブレーカーパターンが広まったそうです。

🔗 その他Ruby


つっつきボイス:「GSoCって何だろうと思ったらGoogle Summer of Codeのようですね」「オープンソースソフトウェアをテーマに毎年開催されている学生向けのイベント」「そういえばGoogle Summer of CodeでRubyのプロジェクトも見たことあります↓」

参考: Ruby | Google Summer of Code
参考: Google Summer of Code - Wikipedia

🔗DB

🔗 rails-pg-extras(Ruby Weeklyより)

pawurb/rails-pg-extras - GitHub


つっつきボイス:「RailsのPostgreSQL関連機能を拡張するのかな」「heroku-pg-extrasをRailsに移植したgemのようです↓」「Herokuと無関係に使えそうですね」

heroku/heroku-pg-extras - GitHub

「何ができるんでしょう?」「MySQLで言うINFORMATION_SCHEMA的な情報をRubyオブジェクトとして取得できるみたい↓」「お〜、Railsコンソールでも取れるのかな?」「サンプル出力を見るとRubyのコードからメトリクス情報がオブジェクトとして取得できているようなので、できるということでしょうね」

# 同リポジトリより
RailsPGExtras.index_cache_hit

$ rake pg_extras:index_cache_hit

| name                  | buffer_hits | block_reads | total_read | ratio             |
+-----------------------+-------------+-------------+------------+-------------------+
| teams                 | 187665      | 109         | 187774     | 0.999419514948821 |
| subscriptions         | 5160        | 6           | 5166       | 0.99883855981417  |
| plans                 | 5718        | 9           | 5727       | 0.998428496595076 |
(truncated results for brevity)

「この種のデータベースメトリクスをRubyのコードから参照したいと思ったことはなかったけど、こういうふうにRubyのコンテキストで使えるなら、たとえばメトリクスをrakeタスクで処理したりRailsコンソールでデバッグしながら見たりできそう」「おぉ」「他にも、最近だとWebアプリのステータスを返す専用のURLを用意して各種情報を見られるようにすることがありますけど、そこにこういう情報を追加してメトリクスを継続的に収集できるようにするといった使い方も考えられますね👍」

🔗クラウド/コンテナ/インフラ/Serverless

🔗 EC2-Classicが終了に向かう


つっつきボイス:「お〜我のツイート、morimorihogeさんのツイートを見て反応したヤツです」「EC2-Classicを使っていた人はただでさえ相当少数派だと思うので、EC2-Classic終了の影響をすぐに理解できる人が果たしてどれだけいるのかなと思ったりしました」「自分はEC2-Classicから移行した経験あるのでわかりますけど、EC2-Classicって随分昔の話ですよね」「そうそう」

「自分もツイートしましたけど、EC2-Classicが終わるとPVインスタンスが動かなくなるのが問題」「あ〜わかります」「PVがなくなるとHVMだけになるので、PV->HVMの移行はそれなりに大変そう」

参考: インスタンスタイプマトリックス | AWS

「まだPVで動いていたものが世の中にあったとは」「AWSはそう簡単に古いサービスを終了しないのがよい点ですが、長期間サポートにも限度はあるので終了は仕方がないでしょうね」「これはもう終了やむなし」

「それにPVインスタンスだった頃のAWSの常識的な運用は、現代のAWS運用から見るとかなりかけ離れているんですよ」「たしかに」「たとえば当時はRDSのようなサービスが高い割にメリットがあまり感じられなくて、コストを考えるとRDSを使わずに自分でEC2にMySQLサーバーを立てることが多かった」「そうそう、当時はよくそれでやってました」「今は?」「今はRDSを使うメリットが圧倒的に大きくなりましたね」

参考: Amazon RDS(マネージドリレーショナルデータベース)| AWS

「現代のAWSの常識で組まれたシステムなら比較的移行しやすいんですが、EC2-Classicという時点で昔の常識でシステムが組まれている可能性が高いので、PVからHVMに移行したりDBを移行したりするのは大変そうだなと思いました」

「今もEC2-Classic使ってるサービスってどのぐらいあるんだろう?」「HVMインスタンスへの移行が終わってさえいればそれほど大変ではないんですが、うんと古くからあるサービスだと移行する機会のないままEC2-Classicを使い続けているところもあるでしょうね」「つらそう...」「PV->HVM移行ではたとえばプライマリパーティションが変わるのでMBR(Master Boot Record)も変わるんですよ」「え〜!」「だからddコマンドでコピーして終わりというわけにいかない」

参考: マスターブートレコード - Wikipedia
参考: dd (UNIX) - Wikipedia

「調べればPV->HVM移行のノウハウは見つかりますけど、何しろ古いのでやってみないとわからない部分も多いし、そもそもEC2-Classicをやっていた人数が少ない」「EC2-Classic自体を知らない人も多そうですよね」「Classic-ELBのことだと思う人がいたりして」「ありそう」「Classic-ELBは今も問題なく使えますけど、EC2-Classicの古さはレベルが違う」

参考: What is a Classic Load Balancer? - Elastic Load Balancing

「EC2-Classic終了は大きいニュースなんですね」「古くからやっているところにとってはかなり大きいニュースだと思います」「リリース記事を見ると2021/10/30には動いていないEC2-Classicのインスタンスが起動できなくなって、翌年の2022/08/15にはすべて終了するとありますね」「割とすぐなのか〜」「AWSの終了スケジュールは少なくともまず前倒しになることはないと思います: 逆に何やかやで延期する可能性の方があるかも😆」

「EC2-Classicがサービスインしたのが2006年か: 当時はCore 2ぐらいの時代だから、サービス止めたいのもわかる」「あの当時はAWSコンソールもなければシンガポールリージョンもなかった」「USのリージョンだけだったんですね」「自分がAWSを使い始めたのはEIPが始まった頃だから2008年ぐらいかな」

参考: Intel Core 2 - Wikipedia
参考: Elastic IP アドレス - Amazon Elastic Compute Cloud

🔗言語/ツール/OS/CPU

🔗 PHPチュートリアルのSQLインジェクション


つっつきボイス:「ググって見つかったPHPチュートリアルで30件中16件にSQLインジェクションが見つかったという記事でした」「mysqli_queryを直接使っている時点でヤバい」「これ直接使わないヤツですよね」

// 同記事より
// Don't do this!
mysqli_query("SELECT * FROM user WHERE id = '" . $_POST["user'] . "'");

「考えてみれば、RailsはいわゆるCGIも経験せずにいきなりWeb開発ができるわけで、むしろ特異なのかも」「まあたしかに」「他の言語だと、昔ながらのCGIから始めてHTML埋め込みでWebページにhello worldや日付を表示するところから学んだものですが、Railsはrails newから始まりますから」「まるで違いますよね」

参考: Common Gateway Interface - Wikipedia

「Railsだけやってきた人から見るとわかりにくいかもしれませんが、昔は他の言語のチュートリアルの一環としてこういうふうに生SQLを動かしてみるという教え方が普通でしたね」「そうそう」「インターネット昔話になってきた」

「脆弱性うんぬんを別にすれば、チュートリアルなどでこういうふうに1行1行何が行われているかを確かめながら学ぶことは初学者にとって有用な面もあると思うんですよ」「わかります」「業務で使わなければOK」

「逆にRailsは初学者にとって便利すぎて裏で何が行われているのかさっぱりわからないですよね」「ブレークポイント置いても追いきれない😆」「Web開発をRailsから始めた人の中にはCGI時代のWebアプリの仕組みを知らずに普通に開発している人もいると思うので、昔と今とどちらの学び方がいいのか自分には断定しきれないかなという気持ちです」


後編は以上です。

バックナンバー(2021年度第3四半期)

週刊Railsウォッチ: SorbetでRailsアプリの型シグネチャを書く、activerecord-cte gemとanycable-client gem(20210803前編)

今週の主なニュースソース

ソースの表記されていない項目は独自ルート(TwitterやはてブやRSSやruby-jp SlackやRedditなど)です。

Ruby Weekly

Publickey

publickey_banner_captured


CONTACT

TechRachoでは、パートナーシップをご検討いただける方からの
ご連絡をお待ちしております。ぜひお気軽にご意見・ご相談ください。