Tech Hotoke Blog

IT観音とは私のことです。

【ネットワーク】SessionとCookieについて

What is this?

Sessionと Cookieについて言語化して整理したもの。メモ書き。

assumption

HTTP通信における話に焦点が当たりがち

Cookie

  • Webブラウザに保存されたデータのこと
  • CookieはサーバーからのHTTPレスポンスのSet-Cookieヘッダーを使用してWebブラウザに送られ、テキストファイル形式で保存される
  • 保存されたCookieはクライアントからサーバーにリクエストするたびに自動送信される
  • 有効期限が設定されていないCookieは、Webブラウザが閉じられると一緒に削除される(「セッションCookie」とも言う)
  • 有効期限が設定されたCookieは、期限が過ぎるまではWebブラウザを閉じても削除されない
  • 「クッキー名=値」の形(1つのクッキーは4KBまで)
  • HTML DOMの一部としてアクセスできる
  • Session IDをCookieに格納する用途で使用されることが多い

Session

  • 一連の処理の始まりから終わりまでを表す概念(HTTP通信においては通信の確立から遮断までを指す)
  • セッション情報はサーバーに保存されることが多い
  • セッションはWebブラウザを閉じるまで保存する
  • リンクなどでページを移動しても内容を保持することができる

HTTPにおけるSessionとCookie

CookieにSession IDを格納することで、HTTPにステートフルな性質を持たせることを実現している。 HTTPはサーバーが一連の処理の状態などを記憶しないという性質(ステートレス)を持っているため、ログイン情報を記憶させる、ショッピングカートの商品情報を記憶させることが出来ない。

Sessionがそれらの情報を記憶して、CookieにSession IDを格納することでHTTP通信においてサーバーが一連の処理の状態などを記憶する性質(ステートフル)を付与することができる。

セキュリティ

  • セッションハイジャック:Session IDを第三者が傍受することで、Sessionが保持している認証情報などを使ってなりすますことができる
  • ラッキング・クッキー:Cookieはそのユーザからの他の要求と関連付けることができる。インターネット広告の配信において、バナー広告は、業者のサーバ(サードパーティー)へのリンクを介して画像を取得する形式が一般的である。クッキーはHTMLに限らず、画像にも設定することができる。HTTPではリンク元のURL情報も送信することが一般的なので、結果として広告業者は、同社を利用するすべてのサイトを対象としてそのユーザのアクセス履歴を把握することが可能になる。

参考

【DB】インデックスについて

What is this?

達人に学ぶDB設計徹底指南書を参考にインデックス、Explainによる実行計画の調査方法などをメモ的にまとめたもの

assumption

インデックスとは?

まず、DBのパフォーマンスを決める要素は主に以下の二つが存在する。

  • インデックス
  • 統計情報

その中でも、DBのパフォーマンス改善の手法としてインデックスが用いられることが最もポピュラー

インデックスは * 探すレコードを識別するデータの項目

  • 対象レコードの格納位置を示すポインタ

で構成されており、これを利用してデータの格納位置を特定し、その位置を直接アクセスする事で、表の検索速度を上げることが出来るもの。

インデックスがない場合は、検索を先頭の行から順に行うのでデータ量が多くなるほど検索に時間がかかる事がある。

【理由】

  • アプリケーションのコードに影響を与えない

  • テーブルのデータに影響を与えない

  • 性能改善の効果が高くコスパが良い

さらに、その中でもB-treeインデックスが最も基本(この他にもビットマップインデックス、ハッシュインデックスなどがある)なので、今回はB-treeインデックスを深堀する。

B-treeインデックスについて

特徴

下記の項目において、バランスが良く汎用性が高い

  1. 均一性:各キー値の間で検索速度にバラツキが少ない。
  2. 持続性:データ量の増加に比してパフォーマンス低下が少ない
  3. 処理汎用性:検索、挿入、更新、削除のいずれの処理もそこそこ速い。
  4. 非等値性:等号(=)に限らず、不等号(<、>、<=、>=)を使ってもそこそこ速い。
  5. 親ソート性:GROUPBY、ORDERBY、COUNT/MAX/MINなどなどソートが必要な処理を高速化できる。

引用:達人に学ぶDB設計徹底指南書

均一性

  • B-treeインデックスはどのリーフからもルートへの距離が均一の平衡木構造のため、検索を同じ計算量で行える
  • 更新削除などで平衡木構造が崩れることがあるので、常に検索の速度が一定となる訳ではないので注意が必要

持続性

  • 平衡木の高さは平均的に3〜4と平たい構造のため、データ量が増えても更新や検索にかかる時間や負荷は上がりにくいと言う性質

引用:達人に学ぶDB設計徹底指南書

処理汎用性

  • 検索、挿入、更新、削除いずれの処理もデータ量が増えても処理コストの差が少ないと言う性質

非等値性

  • =のみでなく>=やBETWEENのような範囲検索においても高速に行える性質
    • B-treeインデックスではキー値が必ずソートされるため、範囲検索では特定のノードよりも左というように探索範囲を絞り込むことが可能のため
  • <>などの否定条件による絞り込みは、特定のリーフ以外というような場合があるので非等値性の恩恵は受けられないので注意が必要

親ソート性

  • B-treeインデックスのキーを使ってソートを行うことでソート処理をスキップすることが出来る性質
  • RDBMSにおいてソート処理は別メモリが確保されるほどコストの高い処理という前提

B-treeインデックスの使いところ

  1. 規模の大きなテーブルに付与する
  2. カーディナリティ(列の値がどの程度の種類を持つか。例えば営業日など)の高い列に付与する:基準は特定のキーを指定して、5%程度に絞り込めること
  3. SQL文でWHERE句の選択条件、結合条件に使用されている列に作成する

ユニークインデックスについて

表内のデータの 2 つの行が同一のキー値を持たないようにすることによりデータ整合性の維持に貢献する索引

主キーとユニークインデックスの違い

  • 主キー(primary key)
    • テーブルのレコードを一意(ユニーク)に決定するための列名の組み合わせ
    • テーブルに一つしか持てない
    • 主キーは内部的にユニークインデックスが貼られる
  • ユニークインデックス(UNIQUE INDEX)
    • テーブルのレコードを一意(ユニーク)に決定するための列名の組み合わせ
    • テーブルの列(の組み合わせ)何箇所も持てる

EXPLAINでの調査について

SQLの実行計画(どのインデックスを使ってクエリを取得するかなど)を取得するためのステートメント

インデックスが正しく使われているか、クエリを効率的に処理出来ているかを確認する際に用いる

<見方>

pending....

【DB × Rails】トランザクション、排他制御について 時々 Rails

What is this?

一般的なトランザクション排他制御に関するまとめとそれをRailsで実現するためのメモ書き

Assumption

  • RDBSにおける話
  • Railsを使う前提

トランザクションについて

> ITの分野では、取引記録などの意味の他に、ソフトウェアの処理方式の一つで、互いに関連・依存する複数の処理をまとめ、一体不可分の処理単位として扱うことを指す場合が多い。
  • 要するに、相互に関係する処理をこれ以上分割できない一つのまとまりとして捉えたもの

    例)銀行口座の送金処理(出金と入金処理が不可分一体となっている)

  • トランザクションにおいてはACID特性が保たれる必要がある

ACID特性について

排他制御について

  • 代表的な例
    • ロック: 共有資源(DBやファイル)にロックをかけて同時にアクセスさせない方式
    • ミューテックス:「未使用」と「使用中」で判断し、共有資源を同時にアクセスさせない方式
    • セマフォ:同時にアクセスできる数を管理し、その人数までしか共有資源に対して同時にアクセスさせない方式。

今回取り扱うのはロックに限ります。

  • 代表的なロック
    • 楽観的ロック
    • 悲観的ロック
    • 共有ロック
    • 排他ロック

今回は、特によく使われる(印象の)悲観ロックと楽観ロックについてまとめます。(Active Recordに備わっているロック機構もこの二種類なので)

悲観ロックと楽観ロックについて

  • 悲観ロック

    • 前提:他者が同時更新は頻繁に起きる
    • 方法:SELECT 〜 FOR UPDATEなど
    • デメリット:ロックの解放漏れがあるとツラい(多分最近のFWはこの辺りもよしなにやってくれているので、意識しなくてもいいことが多いような気がする)
    • 備考:一般的に採用される。ECサイトのセールような決まった時間に同時多発的にアクセスが集中するような場合が典型的な気がする。
  • 楽観ロック

    • 前提:他者との同時更新は滅多に起きない
    • 方法:資源そのものにロックはかけずに、更新対象のデータがデータ取得時と同じ状態かを確認してから更新を行う
    • デメリット:更新されている場合に処理が全てロールバックされるため、処理待ちや処理のやり直しの負荷が高まる。
    • 備考:採用されるシーンはあまり多くない。同時アクセスが起こりづらいがデータの整合性は保たれねばならない場合に使う。例えば経理の管理画面など?

Railsにおける排他制御

楽観ロック

  • テーブルにlock_versionという名前のinteger型カラムを作成する
  • Active Recordは、レコードが更新されるたびにlock_versionカラムの値を1ずつ増やす
  • 更新リクエストが発生したときのlock_versionの値がデータベース上のlock_versionカラムの値よりも小さい場合、更新リクエストは失敗し、ActiveRecord::StaleObjectErrorエラーが発生する(例外処理が必要
  • ActiveRecord::Base.lock_optimistically = falseを設定するとこの動作をオフにできる
  • ActiveRecord::Baseには、lock_versionカラム名を上書きするためのlocking_column属性が用意されている

悲観ロック

  • リレーションの構築時にlockを使うと、選択した行に対する排他的ロックを取得できる
  • lockを用いているリレーションは、デッドロック(互いの処理がロックされて処理が進まなくなった状態)条件を回避するために通常トランザクションの内側にラップされる
例

Book.transaction do
  book = Book.lock.first
  book.title = 'Algorithms, second edition'
  book.save!
end
Mysql

SQL (0.2ms)   BEGIN
Book Load (0.3ms)   SELECT * FROM `books` LIMIT 1 FOR UPDATE
Book Update (0.4ms)   UPDATE `books` SET `updated_at` = '2009-02-07 18:05:56', `title` = 'Algorithms, second edition' WHERE `id` = 1
SQL (0.8ms)   COMMIT

SELECT 〜 FOR UPDATEが内部的に走ってるんですね

Railsガイドには他のロックを実装する方法も乗っているので折に触れてそちらも確認して行こうと思います。

参考

【DB】木構造

what is this?

テーブル設計の際に、木構造のデータを表現する方法をメモとしてまとめたもの

assumption

木構造とは?

こんなやつ

  • ノード 木の結節点
  • ルートノード 木の始点となるノード(treeはルートノードを一つしか持たない)
  • リーフノード 自分よりも下位のノードを持たない「終着点」のノード
  • 内部ノード 中間に位置するノード
  • 経路(path) ノードからノードへの道筋

隣接リストモデル(ナイーブツリー)

  • ノードのレコードに親ノードの情報(ポインタ)を持たせようとするもの
  • SQL木構造を表現する最も古くポピュラー
  • 更新・検索のクエリが複雑になりパフォーマンスが悪化する 例)

入れ子集合モデル

  • ノードを点ではなく、円として捉える
  • 座標を持たせる
  • 座標の絶対値が重要ではなく、包含関係であるという相対的な関係性が表現されていればよい
  • 検索が得意
  • 更新が苦手

検索

  • ルートノードを取得するクエリ
SELECT
*
FROM
role_relation AS rr
WHERE
rr.left_end = 1
;

  • リーフノードを取得するクエリ
SELECT
*
FROM
role_relation AS nleaf
WHERE NOT EXISTS
    (SELECT 
        * 
     FROM role_relation AS leaf
     WHERE 
        leaf.left_end > nleaf.left_end
     AND
        leaf.right_end < nleaf.right_end
    )
;

  • ツリーの深さを求めるクエリ
SELECT 
 children.`role`, COUNT(parent.`role`) AS 深さ
FROM
role_relation AS parent 
INNER JOIN role_relation AS children
ON children.left_end BETWEEN parent.left_end AND parent.right_end
GROUP BY 
children.`role`
;

更新

  • 円の数が増えれば増えるほど、更新する機会が多ければ多いほど負荷が高まる
  • 削除する場合はデータが歯抜けになっていても、包含関係が表現されていれば良いので比較的楽
  • 座標の値を実数まで含めれば更新の際に座標をずらす必要がなくなる(入れ子区間モデル)

上記画像の問題は、下記画像のように実数を用いることで整数と整数の間に無限にノードを追加することができる。 つまり、ノードを追加するたびに隣接するノードの座標をずらす必要がなくなる。

ただし、DBの制約上無限ではないため、いづれリソースは枯渇する。

DBの性能向上によってこの制約を過度に意識する必要がなくなれば、入れ子区間モデルを採用するのが木構造RDBで表現するのに最も良いのでは?

閉包テーブルモデル(クロージャーテーブルモデル)

pending...

経路列挙モデル

pending....

参考

【DB】正規化について

正規化の目的

データの冗長性の排除し、データの一貫性の担保、更新時などの効率上げること

正規化について

  • 基本的に第三正規化まで原則として行う
  • 第四正規化以降は高次正規化と呼ばれる

  • 検索パフォーマンスとデータ生合成のトレードオフ

引用:達人に学ぶDB設計 徹底指南書

  • 正規形はいつでも非正規形に戻せる

引用:達人に学ぶDB設計 徹底指南書

  • 正規化は必ず可逆的な操作出なければならない

※ボイスコッド正規化は不可逆になる可能性がある

  • 正規化は常に1対多の関係になるように行われなければならない

正規化のメリット

データの冗長性の排除により、更新時のデータ不整合を防ぐことができる テーブルの持つ意味が明確化されることで、開発者の理解が容易になる

正規化のデメリット

テーブル数が増えることでパフォーマンスが悪化する

第一正規化

  • 一つのセルの中には一つの値しか含まない状態

Before

After

  • 一つのセルに一つの値が含まれている時、この値をスカラ値と呼ぶ(スカラは単一のという意味)

  • なぜ一つのセルに複数の値が含まれている状態はダメなのか?→主キーが各列の値をユニークに識別できないから

  • つまり、リレーショナルデータベースの世界において、必ず第一正規形を満たすテーブルから構成されている必要がある

関数従属性

  • 正規化 = テーブルの全ての列が関数従属性を満たすように整理していくこと
  • Y = f(X)これをYはXに従属すると表現する[X列の値を決めればY列の値が一つに決まる]

第二正規化

comming soon...

参考

  • 達人に学ぶDB設計 徹底指南書

【Rails】Railsアプリケーションの国際化対応

目的

Railsアプリケーション国際化対応の備忘録として

環境

i18nとは?

ソフトウェアを特定の地域の言語、仕様に縛られることなく、世界各国で共通して利用できるようにすることを意味する「Internationalization」を省略した表記方法。間の「18」は、単語の先頭の"I"と末尾の"n"の間にある文字の数を意味している。単語自体が長いために便宜的に使われる表記方法である。 引用:

I18nとは何? Weblio辞書

アプリケーションの「国際化」

使われるすべての文言やロケール固有の要素 (日付や通貨フォーマットなど) の抽象化までの作業を指す 引用:

Rails 国際化(i18n)API - Railsガイド

一方で、ローカライズ(localization)という言葉もあり具体的な翻訳方法を提供したり、そのためのフォーマットを提供したりすることを指すそうです。

また、Railsフレームワーク内のすべての静的文字列(Active Recordのバリデーションメッセージ、時刻や日付のフォーマットなど)の 国際化部分は既に完了しています。

Railsにおける国際化

  • APIが実装されています。
translate # 訳文を参照する
localize  # DateオブジェクトやTimeオブジェクトを現地のフォーマットに変換する

上のメソッドにはそれぞれ#tと#lという別名メソッドがあるので、以下のように簡潔に書けます。

I18n.t 'store.title'
I18n.l Time.now

config/locales以下にあるすべての.rbファイルと.ymlファイルは、自動的に訳文読み込みパスに追加されます。

en:
  hello: "Hello world"

en=ロケール

hello=キー

enというロケールにおいて、helloというキーがHello Worldという文字列に対応付けられることを意味しています。

また、I18nライブラリでは、Englishをデフォルトのロケールとして扱います。デフォルトのロケールに他の言語を指定しなかった場合は、訳文の検索に:enが使われます。

config/application.rbにおいて、デフォルトのロケールを変更したり訳文読み込みパスを設定したりできます。

config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb, yml}')] #今回はここは特別指定しませんが、例として記載
config.i18n.available_locales = [:en, :ja] #アプリケーションでの利用を許可するロケールのリストを渡す
config.i18n.default_locale = :ja #日本語化させたいので

リクエスト間でロケールを管理する

  • I18n.locale=I18n.with_locale を使わない場合、すべての訳文でデフォルトのロケールが使われます。

  • I18n.localeの設定がすべてのコントローラで一貫していないと、同じスレッドやプロセスによって処理される今後のリクエストにI18n.localeが漏出する可能性があります。

  • I18n.locale =の代わりに、漏出が発生しないI18n.with_localeを利用することもできます。

  • ロケールはApplicationControllerのaround_actionで設定できます。

  around_action :switch_locale

  def switch_locale(&action)
    locale = params[:locale] || I18n.default_locale
    I18n.with_locale(locale, &action)
  end
  

この他にもドメインサブドメインをもとにロケールを変更する設定も可能のようです。

around_actionについて

  • after : アクションの実行後(アクションが実行された後にしか処理されないため、リクエストサイクルで問題が発生した場合は実行されないので注意が必要)
  • before :アクションの実行前(アクションを実行する前に認証を求めるとか)
  • around :アクションの実行前後(フィルタ内のどこかで必ずyieldを実行して、関連付けられたアクションを実行する義務が生じます。フィルタの作業にはレンダリングも含まれる)

参考:

Action Controller の概要 - Railsガイド

ロケールファイルの作成

  • ja.ymlを作成していきます。

  • 今回は、下記のgemを使用します。

github.com

  • rails g i18n_translation jaを実行すると、transration_ja.ymlが生成されるため、これらの内容をja.ymlにコピペします。
ja.yml
---
ja:
  activerecord:
    errors:
      messages:
        record_invalid: 'バリデーションに失敗しました: %{errors}'
        restrict_dependent_destroy:
          has_one: "%{record}が存在しているので削除できません"
          has_many: "%{record}が存在しているので削除できません"
>>>>>>>>>>>transration_ja.yml
    models:
      book: 本

    attributes:
      book:
        author: 著者
        memo: メモ
        picture: 写真
        title: タイトル
<<<<<<<<<<<<<<<<<<<<<<<<<
>>>>>>>>>>>>独自で追加
  back: 戻る
  edit: 編集
  show: 詳細
  destroy: 削除
  new_book: 新規作成
  editing_book: 編集
  created_success_message: "正常に作成されました"
  updated_success_message: "正常に更新されました"
  destroyed_success_message: "正常に削除されました"
<<<<<<<<<<<<<<<<<<<<<<<<<
  date:
    abbr_day_names:
    - 日
    - 月
    - 火
    - 水
    - 木
    - 金
    - 土
    abbr_month_names:
    - 
    - 1月
    - 2月
    - 3月
    - 4月
    - 5月
    - 6月
    - 7月
    - 8月
    - 9月
    - 10月
    - 11月
    - 12月
    day_names:
    - 日曜日
    - 月曜日
    - 火曜日
    - 水曜日
    - 木曜日
    - 金曜日
    - 土曜日
    formats:
      default: "%Y/%m/%d"
      long: "%Y年%m月%d日(%a)"
      short: "%m/%d"
    month_names:
    - 
    - 1月
    - 2月
    - 3月
    - 4月
    - 5月
    - 6月
    - 7月
    - 8月
    - 9月
    - 10月
    - 11月
    - 12月
    order:
    - :year
    - :month
    - :day
  datetime:
    distance_in_words:
      about_x_hours:
        one: 約1時間
        other: 約%{count}時間
      about_x_months:
        one: 約1ヶ月
        other: 約%{count}ヶ月
      about_x_years:
        one: 約1年
        other: 約%{count}年
      almost_x_years:
        one: 1年弱
        other: "%{count}年弱"
      half_a_minute: 30秒前後
      less_than_x_seconds:
        one: 1秒以内
        other: "%{count}秒未満"
      less_than_x_minutes:
        one: 1分以内
        other: "%{count}分未満"
      over_x_years:
        one: 1年以上
        other: "%{count}年以上"
      x_seconds:
        one: 1秒
        other: "%{count}秒"
      x_minutes:
        one: 1分
        other: "%{count}分"
      x_days:
        one: 1日
        other: "%{count}日"
      x_months:
        one: 1ヶ月
        other: "%{count}ヶ月"
      x_years:
        one: 1年
        other: "%{count}年"
    prompts:
      second: 秒
      minute: 分
      hour: 時
      day: 日
      month: 月
      year: 年
  errors:
    format: "%{attribute}%{message}"
    messages:
      accepted: を受諾してください
      blank: を入力してください
      confirmation: と%{attribute}の入力が一致しません
      empty: を入力してください
      equal_to: は%{count}にしてください
      even: は偶数にしてください
      exclusion: は予約されています
      greater_than: は%{count}より大きい値にしてください
      greater_than_or_equal_to: は%{count}以上の値にしてください
      inclusion: は一覧にありません
      invalid: は不正な値です
      less_than: は%{count}より小さい値にしてください
      less_than_or_equal_to: は%{count}以下の値にしてください
      model_invalid: 'バリデーションに失敗しました: %{errors}'
      not_a_number: は数値で入力してください
      not_an_integer: は整数で入力してください
      odd: は奇数にしてください
      other_than: は%{count}以外の値にしてください
      present: は入力しないでください
      required: を入力してください
      taken: はすでに存在します
      too_long: は%{count}文字以内で入力してください
      too_short: は%{count}文字以上で入力してください
      wrong_length: は%{count}文字で入力してください
    template:
      body: 次の項目を確認してください
      header:
        one: "%{model}にエラーが発生しました"
        other: "%{model}に%{count}個のエラーが発生しました"
  helpers:
    select:
      prompt: 選択してください
    submit:
      create: 登録する
      submit: 保存する
      update: 更新する
  number:
    currency:
      format:
        delimiter: ","
        format: "%n%u"
        precision: 0
        separator: "."
        significant: false
        strip_insignificant_zeros: false
        unit: 円
    format:
      delimiter: ","
      precision: 3
      separator: "."
      significant: false
      strip_insignificant_zeros: false
    human:
      decimal_units:
        format: "%n %u"
        units:
          billion: 十億
          million: 百万
          quadrillion: 千兆
          thousand: 千
          trillion: 兆
          unit: ''
      format:
        delimiter: ''
        precision: 3
        significant: true
        strip_insignificant_zeros: true
      storage_units:
        format: "%n%u"
        units:
          byte: バイト
          eb: EB
          gb: GB
          kb: KB
          mb: MB
          pb: PB
          tb: TB
    percentage:
      format:
        delimiter: ''
        format: "%n%"
    precision:
      format:
        delimiter: ''
  support:
    array:
      last_word_connector: "、"
      two_words_connector: "、"
      words_connector: "、"
  time:
    am: 午前
    formats:
      default: "%Y年%m月%d日(%a) %H時%M分%S秒 %z"
      long: "%Y/%m/%d %H:%M"
      short: "%m/%d %H:%M"
    pm: 午後

あとは、viewファイルに下記のように記載するかtlメソッドを用いて出力すると良い

# activerecord.models.bookを参照
<h1><%= Book.model_name.human%></h1>
# activerecord.attributes.book.titleを参照
 <th><%= Book.human_attribute_name(:title) %></th>

参考

【Ruby】mysql2 gemインストール時のトラブルシュート

f:id:TechHotoke:20220404184502p:plain

目的

トラブルシューティングの備忘録として

環境

エラー内容

current directory: /Users/yudai/.rbenv/versions/3.0.2/lib/ruby/gems/3.0.0/gems/mysql2-0.5.3/ext/mysql2
/Users/yudai/.rbenv/versions/3.0.2/bin/ruby -I /Users/yudai/.rbenv/versions/3.0.2/lib/ruby/3.0.0 -r ./siteconf20220404-12423-h3cixo.rb extconf.rb
checking for rb_absint_size()... yes
checking for rb_absint_singlebit_p()... yes
checking for rb_wait_for_single_fd()... yes
-----
Using mysql_config at /usr/local/opt/mysql@5.7/bin/mysql_config
-----
checking for mysql.h... yes
checking for errmsg.h... yes
checking for SSL_MODE_DISABLED in mysql.h... yes
checking for SSL_MODE_PREFERRED in mysql.h... yes
checking for SSL_MODE_REQUIRED in mysql.h... yes
checking for SSL_MODE_VERIFY_CA in mysql.h... yes
checking for SSL_MODE_VERIFY_IDENTITY in mysql.h... yes
checking for MYSQL.net.vio in mysql.h... yes
checking for MYSQL.net.pvio in mysql.h... no
checking for MYSQL_ENABLE_CLEARTEXT_PLUGIN in mysql.h... yes
checking for SERVER_QUERY_NO_GOOD_INDEX_USED in mysql.h... yes
checking for SERVER_QUERY_NO_INDEX_USED in mysql.h... yes
checking for SERVER_QUERY_WAS_SLOW in mysql.h... yes
checking for MYSQL_OPTION_MULTI_STATEMENTS_ON in mysql.h... yes
checking for MYSQL_OPTION_MULTI_STATEMENTS_OFF in mysql.h... yes
checking for my_bool in mysql.h... yes
-----
Don't know how to set rpath on your system, if MySQL libraries are not in path mysql2 may not load
-----
-----
Setting libpath to /usr/local/opt/mysql@5.7/lib
-----
creating Makefile

current directory: /Users/yudai/.rbenv/versions/3.0.2/lib/ruby/gems/3.0.0/gems/mysql2-0.5.3/ext/mysql2
make DESTDIR\= clean

current directory: /Users/yudai/.rbenv/versions/3.0.2/lib/ruby/gems/3.0.0/gems/mysql2-0.5.3/ext/mysql2
make DESTDIR\=
compiling client.c
client.c:787:14: warning: incompatible pointer types passing 'VALUE (void *)' (aka 'unsigned long (void *)') to parameter of type 'VALUE (*)(VALUE)' (aka 'unsigned long (*)(unsigned long)') [-Wincompatible-pointer-types]
  rb_rescue2(do_send_query, (VALUE)&args, disconnect_and_raise, self, rb_eException, (VALUE)0);
             ^~~~~~~~~~~~~
/Users/yudai/.rbenv/versions/3.0.2/include/ruby-3.0.0/ruby/internal/iterator.h:51:25: note: passing argument to parameter here
VALUE rb_rescue2(VALUE(*)(VALUE),VALUE,VALUE(*)(VALUE,VALUE),VALUE,...);
                        ^
client.c:795:16: warning: incompatible pointer types passing 'VALUE (void *)' (aka 'unsigned long (void *)') to parameter of type 'VALUE (*)(VALUE)' (aka 'unsigned long (*)(unsigned long)') [-Wincompatible-pointer-types]
    rb_rescue2(do_query, (VALUE)&async_args, disconnect_and_raise, self, rb_eException, (VALUE)0);
               ^~~~~~~~
/Users/yudai/.rbenv/versions/3.0.2/include/ruby-3.0.0/ruby/internal/iterator.h:51:25: note: passing argument to parameter here
VALUE rb_rescue2(VALUE(*)(VALUE),VALUE,VALUE(*)(VALUE,VALUE),VALUE,...);
                        ^
2 warnings generated.
compiling infile.c
compiling mysql2_ext.c
compiling result.c
compiling statement.c
linking shared-object mysql2/mysql2.bundle
ld: library not found for -lssl
clang: error: linker command failed with exit code 1 (use -v to see invocation)
make: *** [mysql2.bundle] Error 1

make failed, exit code 2

ld: library not found for -lssl

ということでライブラリが見つかりませんというエラーのよう

そもそもldって?

まず、ldGNUリンカーと呼ばれるコマンドです。

lsslってなに?

-lsslとは、mysql2 gemインストール時にldコマンドに与えられたオプションの1つ。

  • lオプション

-lx This option tells the linker to search for libx.dylib or libx.a in the library search path. If string x is of the form y.o, then that file is searched for in the same places, but without prepending lib' or appending.a' or `.dylib' to the file- name.

libx.dylibまたはlibx.aを検索するようにリンカに指示するとあるように、mysql2 gemはlibssl.dylibをライブラリ検索範囲から探してねと指示しているということになるらしいです

Setting libpath to /usr/local/opt/mysql@5.7/lib

実際に覗いてみると、お目当てのlibssl.dylibはありません。

libmysqlclient.20.dylib  libmysqlclient.a    libmysqlclient.dylib    libmysqld.a     libmysqlservices.a  pkgconfig       plugin

brew info openssl を実行します

openssl@3: stable 3.0.2 (bottled) [keg-only]
Cryptography and SSL/TLS Toolkit
https://openssl.org/
/usr/local/Cellar/openssl@3/3.0.2 (6,429 files, 28.3MB)
  Poured from bottle on 2022-04-04 at 16:36:04
From: https://github.com/Homebrew/homebrew-core/blob/HEAD/Formula/openssl@3.rb
License: Apache-2.0
==> Dependencies
Required: ca-certificates ✔
==> Caveats
A CA file has been bootstrapped using certificates from the system
keychain. To add additional certificates, place .pem files in
  /usr/local/etc/openssl@3/certs

and run
  /usr/local/opt/openssl@3/bin/c_rehash

openssl@3 is keg-only, which means it was not symlinked into /usr/local,
because macOS provides LibreSSL.

If you need to have openssl@3 first in your PATH, run:
  echo 'export PATH="/usr/local/opt/openssl@3/bin:$PATH"' >> ~/.zshrc

For compilers to find openssl@3 you may need to set:
  export LDFLAGS="-L/usr/local/opt/openssl@3/lib"
  export CPPFLAGS="-I/usr/local/opt/openssl@3/include"

For pkg-config to find openssl@3 you may need to set:
  export PKG_CONFIG_PATH="/usr/local/opt/openssl@3/lib/pkgconfig"

==> Analytics
install: 153,065 (30 days), 365,265 (90 days), 676,991 (365 days)
install-on-request: 121,259 (30 days), 284,267 (90 days), 531,231 (365 days)
build-error: 5,474 (30 days)
  export LDFLAGS="-L/usr/local/opt/openssl@3/lib"
  export CPPFLAGS="-I/usr/local/opt/openssl@3/include"

ここをオプションに指定するらしい(詳しくは後日調べます)

下記を実行

sudo gem install mysql2 -v '0.5.3' --source 'https://rubygems.org/' -- --with-cppflags=-I/usr/local/opt/openssl@3/include --with-ldflags=-L/usr/local/opt/openssl@3/lib

行けました!

Building native extensions with: '--with-cppflags=-I/usr/local/opt/openssl@3/include --with-ldflags=-L/usr/local/opt/openssl@3/lib'
This could take a while...
Successfully installed mysql2-0.5.2
Parsing documentation for mysql2-0.5.2
Installing ri documentation for mysql2-0.5.2
Done installing documentation for mysql2 after 0 seconds
1 gem installed

参考