- Ruby / Rails関連
週刊Railsウォッチ: Rubyの実行モデル解説記事、shale gem、HTTP/3がRFC 9114にほか(20220614後編)
こんにちは、hachi8833です。
🔗Ruby
🔗 shale: Rubyデータ構造をJSONやYAMLやXMLなどと相互変換(Ruby Weeklyより)
つっつきボイス:「shaleってシェールガスのシェールかな?」「たしかに頁岩(けつがん)のshaleと同じスペルですね」
「よくあるgemかなと思ったら、JSON SchemaやXML Schemaも扱えるらしい↓」「お〜」「ちゃんと動くならですけどね」「一応★付けておこうっと」「できるかどうかわからないけど、XSDを渡してJSONを出力できたら嬉しい」
# 同リポジトリより
require 'shale/schema'
Shale::Schema.to_xml(Person, pretty: true)
# =>
#
# {
# 'schema0.xsd' => '
# <xs:schema
# elementFormDefault="qualified"
# attributeFormDefault="qualified"
# xmlns:xs="http://www.w3.org/2001/XMLSchema"
# xmlns:foo="http://foo.com"
# >
# <xs:import namespace="http://foo.com" schemaLocation="schema1.xsd"/>
# <xs:element name="person" type="Person"/>
# <xs:complexType name="Person">
# <xs:sequence>
# <xs:element name="name" type="xs:string" minOccurs="0"/>
# <xs:element ref="foo:address" minOccurs="0"/>
# </xs:sequence>
# </xs:complexType>
# </xs:schema>',
#
# 'schema1.xsd' => '
# <xs:schema
# elementFormDefault="qualified"
# attributeFormDefault="qualified"
# targetNamespace="http://foo.com"
# xmlns:xs="http://www.w3.org/2001/XMLSchema"
# xmlns:foo="http://foo.com"
# >
# <xs:element name="address" type="foo:Address"/>
# <xs:complexType name="Address">
# <xs:sequence>
# <xs:element name="city" type="xs:string" minOccurs="0"/>
# </xs:sequence>
# </xs:complexType>
# </xs:schema>'
# }
参考: JSON Schema | The home of JSON Schema
参考: XML Schema - Wikipedia
🔗 Rubyの実行モデル解説(Ruby Weeklyより)
つっつきボイス:「Shopifyの技術記事です」「Copy on Write(CoW)やスレッドの話がある」「ライブラリコードなどをプリロード可能で親プロセスと子プロセスで共有できるメモリをstatic memory、子プロセス実行時の処理に応じて使われるメモリをprocessing memoryと呼んでいるんですね↓」
「static memoryは親子プロセスが同じように参照しますが、書き換えが発生しない前提のメモリなので、親プロセスで一度確保してしまえば子プロセスでは書き込みを行わない限り追加のメモリ確保不要で利用できます」「ふむふむ」「そして以下の図のように、static memoryを共有している2スレッドのプロセス1個の方が、1スレッドの独立したプロセス2個よりもメモリ消費が小さくなる↓」「なるほど、記事の最後で"常に可能な限りプリロードしておけ"と書かれているのはそういうことなんですね」「そうそう、オレンジのstatic memoryの部分をプリロードで大きくしておくことで、緑のprocessing memoryの追加を少なくできます」
「記事はGVLのレイテンシの話や、RactorやFiberが銀の弾丸ではないという話もありますね」「YJITが状況を変革するかもしれないという話もある」「かなりよさそうなコアな技術記事👍」「さすがShopify」
🔗 dry-rbを探ってみた
つっつきボイス:「翻訳記事でもお世話になっているBrandon Weaverさんの記事です」「シリーズ物っぽいタイトルだけど、dry-rbのどの機能を掘っているんだろう?」
参考: dry-rb - Home
「流し読みしただけですが、前置きが割と長いかも: JSONを返すHTTPレスポンスを扱うのにEnumerable
を使ってもいいけど同時に実行されるわけではないよね、というのが前半の流れっぽい」「後半でdry-monadsを使うといいよと書かれているのが本題なんでしょうね」「dry-monadsでResult
モナドを作ることで同時実行できるようにしたり、パターンマッチングと組み合わせたりするとさらにいいよという感じかな」「やっぱりWeaverさんはモナドとかモノイドが好きなんですね」「モナドまだわからない😅」「このあたりに興味のある人は読んでみるといいかも👍」
# 同記事より
require "dry/monads"
extend Dry::Monads[:result]
# Pretend it's a DB of some sort
IDS = { "a" => "Red", "b" => "Blue" }
def offering_a(id)
return Failure("ID not found: #{id}") unless IDS.key?(id)
Success(IDS[id])
end
offering_a("a")
# => Success("Red")
offering_a("nope")
# => Failure("ID not found: nope")
# Remember Enumerable? What happens if we chain each of these?
offering_a("a").fmap { |name| "We found #{name}!" }
# => Success("We found Red!")
offering_a("nope").fmap { |name| "We found #{name}!" }
# => Failure("ID not found: nope")
# 同記事より
result = offering_a("a")
case result
in Success("Red" | "Blue") then "Found who we were looking for"
in Success then "Not who we expected, but still ok"
in Failure(/_why/) then "He's still in our hearts though"
in Failure then "I give, you win"
end
🔗 workflow: ステートマシンgem(Ruby Weeklyより)
つっつきボイス:「このworkflowは新し目のステートマシンgemみたいですけど、★が1700超えと多いですね」「workflowという名前、ググりにくそう...」
# 同リポジトリより
class Article
include Workflow
workflow do
state :new do
event :submit, :transitions_to => :awaiting_review
end
state :awaiting_review do
event :review, :transitions_to => :being_reviewed
end
state :being_reviewed do
event :accept, :transitions_to => :accepted
event :reject, :transitions_to => :rejected
end
state :accepted
state :rejected
end
end
「Rubyのステートマシンgemというとaasmが昔から有名で↓、workflowのインターフェイスもaasmと似ている感じですけど、workflowのgemspecやGemfileを見るとRailsに依存していないので、そこが売りなのかもしれない」「なるほど、Rails以外でも使えるんですね」
参考: 有限オートマトン - Wikipedia
参考: Rails で使える StateMachine Gem 3 つをしらべてみた - Qiita
🔗 その他Ruby
> Rubyのprivateメソッドってself付きで呼べないはず
どうもこんにちは。実はRuby 2.7以降ではふつうのprivateメソッドもself付きで呼べるようになっているので、その旨記事に追記しておきました🙏
— Junichi Ito (伊藤淳一) (@jnchito) June 4, 2022
つっつきボイス:「そういえばself
を付けてprivateメソッドを呼び出せるようになってましたね」
「ところでこれはホントその通り↓」「その通り」「普段からインスタンス変数を直接書き換えていたから、privateメソッドの仕様変更を気にしていなかった😆」「長年Rubyでよくない書き方を避けていると、言語仕様の変更に気づきにくいというのはあるかもしれませんね」
ただ、このようなコードは混乱を招きやすいので、何か特別な理由がなければセッターメソッドを使わずにインスタンス変数を直接書き換える方がよいでしょう。
https://qiita.com/jnchito/items/451018811842c2631e1eより
🔗DB
🔗 動画: Railsのバルクインポートを100倍高速化する(Ruby Weeklyより)
つっつきボイス:「動画だと時間がかかるので、サンプルコードの差分URLを貼っておきました↓」
「perform_later
で非同期化も行っているみたい↓」
# app/models/event_stream.rb
class EventStream < ActiveSupport::CurrentAttributes
attribute :updated_movie_ids
# effectively a pre- and post- request/job callback
before_reset do
if updated_movie_ids&.any?
# enqueue a job to process any updated movies
MovieNotificationJob.perform_later(updated_movie_ids)
end
end
# stores when any given movie has been updated
def movie_updated(movie_id)
self.updated_movie_ids ||= []
self.updated_movie_ids << movie_id
end
end
参考: Rails API perform_later
-- ActiveJob::Enqueuing::ClassMethods
「予想はしていたけど、やっぱり生SQLで高速化している↓」「さもありなんですね」
# https://github.com/ryantownsend/bulk-import-exercise-solution/compare/d70a5076c927a4cb68703207dd3a2b59d68d7e93...8981231a1b0bf84aa4a1dc335928d468372c4e8b#diff-41f85d153f0416158af73a3ec7531d403d459861ecaccb145ddb47a6e2383aa0R2
UPSERT_QUERY_TEMPLATE = <<~SQL
with movie_import_entries as (
select
(row_number() over()) + %{batch_offset_start} as index,
t.id,
t.title,
t.description,
t.rating,
t.published,
t.subscriber_emails,
movies.id is not null as already_exists,
(
(movies.id is not null or t.title is not null) and
(movies.id is not null or t.description is not null) and
(
(movies.id is not null and (t.rating is null or t.rating between 1 and 5)) or
(t.rating is not null and t.rating between 1 and 5)
)
) as is_valid
from
movie_imports,
jsonb_to_recordset(jsonb_path_query_array(movie_imports.entries, '$[%{batch_offset_start} to %{batch_offset_end}]')) as t(
id uuid,
title text,
description text,
rating numeric(2,1),
published boolean,
subscriber_emails text[]
)
left join
movies on movies.id = t.id
where
movie_imports.id = '%{movie_import_id}'
),
...
「UPSERT_QUERY_TEMPLATEを作っているということは、やはりINSERT INTO SELECTしていますね↓: DBの中で実行するならINSERT INTO SELECTは高速」「なるほど」「DBの外で行うなら、TSVなどの段階で前処理しておくと速いですね」
# https://github.com/ryantownsend/bulk-import-exercise-solution/compare/d70a5076c927a4cb68703207dd3a2b59d68d7e93...8981231a1b0bf84aa4a1dc335928d468372c4e8b#diff-41f85d153f0416158af73a3ec7531d403d459861ecaccb145ddb47a6e2383aa0R70
-- where the movie doesn't exist, import it
inserted_movies as (
insert into
movies (
id,
title,
description,
rating,
publishing_status,
subscriber_emails,
created_at,
updated_at
)
select
movie_import_entries.id,
movie_import_entries.title,
movie_import_entries.description,
movie_import_entries.rating,
case
when movie_import_entries.published = true then 'published'
else 'unpublished'
end,
movie_import_entries.subscriber_emails,
now() as created_at,
now() as updated_at
from
movie_import_entries
where
movie_import_entries.already_exists = false and
movie_import_entries.is_valid = true
returning
id
),
参考: Tab-Separated Values - Wikipedia
「全般に、教科書的な定番の高速化手法ですね👍」「なるほど」「生SQLを使うとRailsのアプリケーション層によるチェックが行われなくなりますが、高速化のためにこうすることも多い」「アプリケーション層を通すこと自体がオーバーヘッドですもんね」
🔗クラウド/コンテナ/インフラ/Serverless
🔗 HTTP/3がRFC 9114で標準化(Publickeyより)
つっつきボイス:「HTTP/3は完全にQUICに乗るんですね」「フローコントロールがTCPじゃない世界が来た」「従来でも動画配信などではTCPを使わずにUDPを使ったりしていたので、それ自体はそれほど新しくはないけど、動画配信並の即応性を一般のWebブラウジングでも求められるようになったんだなという気持ちと、従来のネットワークレイヤモデルが崩れつつあるのかなという気持ちがありますね」「たしかに」
参考: インターネット用語1分解説~QUICとは~ - JPNIC
参考: TCP/IPとは?通信プロトコルの階層モデルを図解で解説 | ITコラム|アイティーエム株式会社
「HTTP/3になるとプロトコル番号もUDPになるので、ファイアウォールなどにも結構影響がありそうな気がしますね: L4までのファイアウォールがうまく機能しなくなってアプリケーションロードバランサーでないと扱えなくなったり」「う〜む」
「アプリケーションロードバランサー周りがHTTP/3でどう変わるかは気になる: データグラムを一度フローに展開することになってアプリケーションロードバランサーの仕事がすごく増えそうな気もするけど、TLSで暗号化されればエンドツーエンド制御になりそうだし、このあたりは今後調べる必要がありそうかな」
参考: TLS(Transport Layer Security / SSL/TLS)とは - 意味をわかりやすく - IT用語辞典 e-Words
参考: Cloud CDN とロード バランシングで QUIC を使用して HTTP/3 でコンテンツを取得 | Google Cloud Blog
🔗言語/ツール/OS/CPU
🔗 M1 Macのサーバーサイド開発環境
つっつきボイス:「お〜、昨年はいったん諦めてたの↓がついに対応できたんですね🎉」「arm64に対応したものもだんだん増えてきてますね」「MySQLも対応してる!」「Oracleがやっている本家のMySQLですね: もうM2が発表されたし、対応はしておきたいでしょうね」「以前のバージョンのMySQLがarm64に対応していない可能性はあるかも」
参考: Repro のサーバーサイド開発環境を M1 Mac に対応させるまでの道のり(撤退編) - Repro Tech Blog
参考: Apple、画期的なパフォーマンスと能力を備えたM2を発表 - Apple (日本)
後編は以上です。
バックナンバー(2022年度第2四半期)
週刊Railsウォッチ: Hotwireをアプリ構築で学ぶ、Active RecordのDurationとPostgreSQL intervalデータ型ほか(20220613前編)
- 20220607後編 Shopifyのlanguage server ruby-lsp、PostgreSQL 15 Beta 1リリースほか
- 20220606前編 BasecampのHotwireページネーション、Query Object、Lograge gemほか
- 20220531 Railsコミュニティアンケート結果発表、書籍『Sustainable Web Development with Ruby on Rails』ほか
- 20220524後編 Railsコアチームとコミッターに新メンバー、ruby-buildでのRust YJITサポートほか
- 20220523前編 Hotwireの用途解説記事、RubyKaigi 2022プロポーザル募集開始ほか
- 20220517後編 rubygemsに「scoped gems」の提案、RSpecのブロック構文ほか
- 20220516前編 Active Modelで属性のパターンマッチをサポート、猫でもわかるHotwire入門ほか
- 20220511後編 Ruby 3.2.0devにRust版YJITがマージ、Docker Compose V2ほか
- 20220510前編 Active RecordにPromiseと非同期集計メソッドがマージ、climate_control gemほか
- 20220419後編 RubyのGCコンパクション改修、jemalloc、ReDoSの自動検出修正ほか
- 20220418前編 RailsConf 2022が5月17〜19日開催、認可機能解説記事ほか
- 20220412後編 HashieでRubyのハッシュを強化、最近のRubyコア解説記事ほ
- 20220411前編 Turbo Railsチュートリアル、Active Recordの「Leaky Abstraction」を削減ほか
- 20220406後編 RBS関連記事、Ruby formatterプロジェクト、Google Cloud Runほか
- 20220404前編 Ruby 3.2.0 Preview 1リリース、Rails向けDocker環境ジェネレータ、scientist gemほか
今週の主なニュースソース
ソースの表記されていない項目は独自ルート(TwitterやはてブやRSSやruby-jp SlackやRedditなど)です。
週刊Railsウォッチについて
TechRachoではRubyやRailsなどの最新情報記事を平日に公開しています。TechRacho記事をいち早くお読みになりたい方はTwitterにて@techrachoのフォローをお願いします。また、タグやカテゴリごとにRSSフィードを購読することもできます(例:週刊Railsウォッチタグ)