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

週刊Railsウォッチ(20201006後編)Rubyの`defined?`キーワード、Ractorベースのジョブスケジューラ、Caddy Webサーバーほか

こんにちは、hachi8833です。

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

Ruby

Ractorベースのジョブスケジューラを書く(Ruby Weeklyより)

# 同記事より
pipe = Ractor.new do
  loop do
    Ractor.yield(Ractor.recv, move: true)
  end
end

workers = 4.times.map do |index|
  worker = Worker.new(index)
  Ractor.new(pipe, worker) do |pipe, worker|
    loop do
      job = pipe.take
      puts "taken job from pipe by #{Ractor.current} and work it by worker #{worker.id}"

      worker.work(job)
    end
  end
end

redis = Redis.new
loop do
  puts "Waiting on work"
  queue, job = redis.brpop(QUEUE_NAME)
  puts "Pushing work from #{queue} to available workers"
  pipe.send(job, move: true)
end

つっつきボイス:「この間のRactorでWebサーバー記事に続いてジョブスケジューラ記事も出ていました」「こういうブロック図なのね↓」


同記事より

「もしかするとマイクロサービスを書くときには高いマルチスレッド性能と大量のリクエストを捌ける要件を満たすために今後Ractorが使われるようになるかもって思いました」

defined?キーワードはblock_given?メソッドより速い(Ruby Weeklyより)


つっつきボイス:「そうそう、defined?はメソッドではなくてキーワードなんですよね」「ここを理解しとかないとたまにハマる」

参考: クラス/メソッドの定義 (Ruby 2.7.0 リファレンスマニュアル) -- defined?

「で記事はblock_given?メソッドとdefined?(yield)のパフォーマンスを比較している」「言われてみればyield if defined?(yield)っていう書き方は可能ですね」

# 同記事より
def method_with_defined_yield
  yield if defined?(yield)
end

method_with_defined_yield {42} # => 42
method_with_defined_yield      # => nil

「でdefined? yieldの方が1.3倍近く速い↓」「たしかにメソッド呼び出ししない分defined?キーワードの方が速そうですね」「defined?(yield)が使われているのを見たときにblock_given?と同じ意味だとすぐわかるだろうか...」

# 同記事より
Warming up --------------------------------------
        block_given?   988.926k i/100ms
      defined? yield     1.259M i/100ms
Calculating -------------------------------------
        block_given?      9.897M (± 1.1%) i/s -     50.435M in   5.096367s
      defined? yield     12.561M (± 1.8%) i/s -     62.944M in   5.012616s

Comparison:
      defined? yield: 12561217.9 i/s
        block_given?:  9897436.6 i/s - 1.27x  (± 0.00) slower

後でベンチマークコード↓をRuboCop(0.92.0)でチェックしてみたところ、yield if defined?(yield)について特に怒られはありませんでした。

# frozen_string_literal: true

require 'benchmark/ips'

def method_with_block_given
  yield if block_given?
end

def method_with_defined_yield
  yield if defined?(yield)
end

Benchmark.ips do |x|
  x.config(time: 5, warmup: 2)

  x.report('block_given?') { method_with_block_given { 42 } }
  x.report('defined? yield') { method_with_defined_yield { 42 } }

  x.compare!
end

down: ストリーミングダウンロードgem(Ruby Weeklyより)

janko/down - GitHub


つっつきボイス:「ダウン?」「ストリーミングダウンロード用のgemみたいです」「BASIC認証もやってくれるのね」「見た感じ、Rubyでファイルをストリーミングダウンロードしながらそれを開いたり処理を実行したりということもできるんでしょうね」

# 同リポジトリより
remote_file = Down.open("http://example.com/image.jpg")
remote_file.size # "Content-Length" ヘッダーから読み出す

remote_file.read(1024) # 最初の1KBをダウンロードして返す
remote_file.read(1024) # 次の1KBをダウンロードして返す

remote_file.eof? #=> false
remote_file.read # ファイルの残りをダウンロードして返す
remote_file.eof? #=> true

remote_file.close # HTTPコネクションをクローズして内部Tempfileを削除する

「こうやってチャンクに対してeachできる↓のを見ると、結構頑張って作った感じがする」

# 同リポジトリより
remote_file = Down.open("http://example.com/image.jpg")
remote_file.each_chunk { |chunk| ... }
remote_file.close

「チャンクというのは?」「HTTP 1.1のTransfer-Encoding: chunkedというもので、この機能を使うとファイルの特定の範囲を転送したり、いわゆるファイルダウンロードの『レジューム』ができたりします」「ふむふむ」「たとえばチャンクが1MB単位だとすると1MBごとにeachで処理するとか」「なるほど!」

参考: Transfer-Encoding - HTTP | MDN -- chunked encodingについて

chunked
データはチャンク (塊) の連続で送られます。この場合は Content-Length ヘッダーが省略されます。それぞれのチャンクの先頭に現在のチャンクの長さを16進数の形式で追加し、その後で '\r\n' が続き、チャンク自体ももう一つの '\r\n' が続きます。最後のチャンクは通常のチャンクですが、長さが0であるという点が異なります。この後に、一連のエンティティのヘッダーフィールド (おそらく空) から成るトレイラーが続きます。
developer.mozilla.orgより

「このdown gemは、Webアプリで使うというよりクライアントRubyで使うときに便利そうかな👍 : たとえばですけど、音声データのリストをストリーミングでダウンロードしながら音声の中身をRubyで解析して、ある条件に一致したら途中でもダウンロードを停止して次の音声データに進むとか」「なるほど、全部取ってこなくて済めばネットワーク帯域やディスク領域も節約できそうですね」

Rubyの!付きメソッド


つっつきボイス:「このツイート自分も見ました」「!付きのメソッドの扱いをどうするかについては諸説ありますね」「基本的には!付きメソッドがあるなら!なしメソッドもペアとしてあるべき、という考え方でだいたいやれるかな」「ときどきペアにしにくいメソッドもありますけど」

「そういえば前に翻訳した記事↓で!付きメソッドだけがある状態を『プリマドンナメソッド』って呼んでたのを思い出しました↓」「『思わせぶりだからプリマドンナ』なんですか?😆」「プリマドンナの由来は未だに裏が取れてませんけど😅」

Ruby:「プリマドンナメソッド」の臭いの警告を私が受け入れるまで(翻訳)

「考えてみれば!を使わなくても、メソッド名そのもので危険性を示すことも可能ですよね」「Linuxのrmコマンドの--no-preserve-rootオプションみたいな名前にするとか」「その長〜いオプションを付けないとrm -rf /を実行できないようにするヤツですね」「それでも入力に慣れるとうっかり実行しちゃったりするんですよ...😇」

参考: 実行してはいけないLinuxコマンド(2) Ubuntu Desktopで『rm -rf /』を実行 | マイナビニュース

「メソッド名の!だけで危険を知らせるよりは、長い名前のような別の方法も活用するのがよい気がします」「たとえば危険な操作はno_preserve_root: trueみたいな引数を与えないと実行できないようにするとか、他の方法もいろいろ考えられますよね」「そのうえで@jnchitoさんも言うように、原則として!ありメソッドと!なしメソッドの定義がペアになるようにするのがよさそう」

DB

PostgreSQL 13リリースと新機能検証


つっつきボイス:「どちらもBPS社内Slackに貼っていただいた記事です」「13のGA版は少し前から出ていましたけど、このたびGAが取れて正式にリリースされました🎉」「虎の巻、PostgreSQL 11や12のときにも出ていたんですね」

「13はどこが変わったんでしょう?」「13にはいわゆる破壊的な変更はそれほどなかったと思いますけど、そこも含めてこの「虎の巻」記事に詳しく書かれてますのでそちらを読んでみるとよいと思います」「『B-Treeインデックスの重複排除機能がデフォルト有効に』とかよさそう」

CSS/HTML/フロントエンド/テスト/デザイン

Caddy: HTTP/2がデフォルトのWebサーバー

caddyserver/caddy - GitHub


つっつきボイス:「キャディ?」「HTTP/2がデフォルトのWebサーバーだそうなんですけど、その『Goodbye Nginx, hello Caddy』という記事で初めて知りました」「Nginxのオルタナっぽいですね」「Automatic HTTPSというのはTLS証明書を更新してくれるということみたい」

「Caddyには使いやすい設定がいろいろあるっぽい」「たしかにNginxのコンフィグは面倒ですよね」「Nginxのコンフィグはサイト構成が複雑になったりconfigファイルが肥大化してくると設定が難しくなってきます...」

# 同サイトより
example.com

# Templates give static sites some dynamic features
templates

# Compress responses according to Accept-Encoding headers
encode gzip zstd

# Make HTML file extension optional
try_files {path}.html {path}

# Send API requests to backend
reverse_proxy /api/* localhost:9005

# Serve everything else from the file system
file_server

Nginx

「Nginxのコンフィグって、あくまでコンフィグでしかなくてスクリプト的に動かない(=設定ファイルを上から順に評価していくわけではない)のが面倒...」「それほんまにそう思います: ngx_mruby↓とか入れたら違うかもしれませんけど、そこまでするかどうかというのもありますし」

matsumotory/ngx_mruby - GitHub


「使ってみないとわかりませんけど、このCaddyは、凝った構成よりもWebサーバーとしての代表的な構成にしたい場合によさそうなのかなと推測してみました」「代表的な構成のリバースプロキシとして楽に導入できるとか、そういうのだといいですよね」「まあリバースプロキシとして使うだけならどれを使ってもいいと思います」

「やっぱりWebサーバーの選定って悩みますよね...ちなみにこの間h2o↓を使ってみました」「お〜使ったんですね!」「Railsの前段に何置こうかなとあれこれ考えて結局h2oにしました」

h2o/h2o - GitHub


ちなみにRailsやDockerでのCaddyの利用記事もありました。

言語/ツール/OS/CPU

GitHub Code Scanningがリリース


つっつきボイス:「GitHub Code Scanningがpublicリポジトリで正式に利用できるようになった🎉」「とりあえず有効化しておくとよさそう」「どのぐらい怒られるかな...」「秘密鍵を登録してないかとかそのあたりをチェックされるかも」

「CodeQLという、GitHubが定義したDSLがあるのか↓」「おそらくRuboCopみたいに自分でルールを追加できるんでしょうね」「たとえばRails用のCodeQLがメンテされるようになったら嬉しいかも」

参考: CodeQL - GitHub Security Lab

# securitylab.github.comより
from DataFlow::PathNode source, DataFlow::PathNode sink, UnsafeDeserializationConfig conf

where conf.hasFlowPath(source, sink)

select sink.getNode().(UnsafeDeserializationSink).getMethodAccess(), source, sink,
    "Unsafe deserialization of $@.", source.getNode(), "user input"

「CodeQLがオープンな規格ならGitLabでも使えることが期待できそうですけど、まだその辺の情報は見当たらないかな...BPSではGitLab↓がメインなのでその辺が気になってます」

GitLab自社運用のための注意点とノウハウ(2018/06版)


後編は以上です。

バックナンバー(2020年度第4四半期)

週刊Railsウォッチ(20201005前編)Ruby 2.7.2がリリース、Shopifyのモジュラー化gem「packwerk」、stimulus_reflexほか

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

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

Ruby Weekly


CONTACT

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