Slide 1

Slide 1 text

技術的ミスと深堀り RECRUIT TECH CONFERENCE 2025 フロントエンドエンジニアが挑むプロダクト価値向上 雫石 卓耶 株式会社リクルート プロダクトディベロップメント室

Slide 2

Slide 2 text

雫石 卓耶 経歴 / Career 2021年に、リクルートへ新卒入社。社会人4年目。 HR系領域のWebフロントエンド / バックエンドエンジニア 現在は、『リクルートダイレクトスカウト』の開発メンバー プログラミング。最近は、それ以外の趣味がないのが悩み 趣味 / Hobbies プロダクトディベロップメント室 アプリケーションソリューションユニット 横断エンジニアリング部 アプリケーションソリューショングループ

Slide 3

Slide 3 text

リクルートダイレクトスカウト ● 転職スカウトサービス ● 登録しプロフィールを入力すると、 企業 / エージェントからスカウトが届く ● スカウトを受諾すると、企業 / エージェント との、右のようなチャット画面が開く

Slide 4

Slide 4 text

開発メンバーの一員として ● 普段から、技術的なミスをしてしまわないように注意している ○ ミスが、求職者や企業 / エージェントの、利益や人生に影響して しまうかもしれず、それが怖いから

Slide 5

Slide 5 text

開発メンバーの一員として ● 普段から、技術的なミスをしてしまわないように注意している ○ ミスが、求職者や企業 / エージェントの、利益や人生に影響して しまうかもしれず、それが怖いから ● しかし、システムからミスを完全に無くすのは難しいのも事実

Slide 6

Slide 6 text

Agenda 明らかになったミスに対して、私は深堀りを行うことがあります。どのような深 堀りを行ったのかについて、3つの事例をご紹介します。 ● 事例1:データベースとnull文字 ● 事例2:WebSocketとHTTP ● 事例3:WebSocketのクローズ忘れ ● まとめ

Slide 7

Slide 7 text

事例1

Slide 8

Slide 8 text

事例1:データベースとnull文字 ● 事例 ○ チャット画面に入力されたメッセージをデータ ベースに挿入する際、null文字が含まれていて 失敗した ● 補足 ○ エラーの発生件数は1件だけ ○ ユーザーへの直接の不利益は無し(メッセージ は通常通り見られる) ● 対応 ○ 入力内容からnull文字を排除する処理を追加

Slide 9

Slide 9 text

事例1の深堀り ● 今回の件を単純化すると、反省点は「DBのvarchar(1)にどのような文字を挿 入できるか」がわかっていなかったこと ● そもそも、「文字」はどのように表される? ● DBの文字コード(charset)はUTF-8を使用

Slide 10

Slide 10 text

Unicodeと文字数 a U+61 コードポイント 各文字に一意なコードポイントが 割り振られている

Slide 11

Slide 11 text

Unicodeと文字数 a U+61 61 UTF-8 コードポイント バイト列 コードポイントをエンコードしてバイト列を得る。 バイト列は、ファイルへの読み書きや、通信などで使用される

Slide 12

Slide 12 text

Unicodeと文字数 a U+61 61 UTF-8 コードポイント バイト列 仮説1:バイト数を文字数としている?

Slide 13

Slide 13 text

複数のバイトにエンコードされる文字もある a U+61 61 UTF-8 コードポイント バイト列 あ U+3042 E3 81 82 UTF-8 1バイトに収まらない 複数バイトにエンコード

Slide 14

Slide 14 text

複数のバイトにエンコードされる文字もある a U+61 61 UTF-8 コードポイント バイト列 あ U+3042 E3 81 82 UTF-8 バイト数を数えると 「あ」は3文字になる

Slide 15

Slide 15 text

複数のバイトにエンコードされる文字もある a U+61 61 UTF-8 コードポイント バイト列 あ U+3042 E3 81 82 UTF-8 仮説2:コードポイント数を 文字数としている?

Slide 16

Slide 16 text

複数のコードポイントで表現される文字もある 文字画像の出典: https://ja.wikipedia.org/wiki/%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB:U845b-k.svg https://ja.wikipedia.org/wiki/%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB:U845b.svg 「かつしかく」 の1文字目 「かつらぎし」 の1文字目 U+845B U+E0101 U+845B U+E0100

Slide 17

Slide 17 text

複数のコードポイントで表現される文字もある 文字画像の出典: https://ja.wikipedia.org/wiki/%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB:U845b-k.svg https://ja.wikipedia.org/wiki/%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB:U845b.svg 「かつしかく」 の1文字目 「かつらぎし」 の1文字目 U+845B U+E0101 U+845B U+E0100 基底文字としては区別されない

Slide 18

Slide 18 text

コードポイントは、文字の同一性ごとに割り当てられる ● Unicodeは、文字の形の詳細に立ち入らない ○ 文字の形はフォントが決める ● いろいろな「a」があるが、抽象的には同一の文字 ● 同一の文字なので、おなじコードポイントが割り当てられている 表の出典: Unicode® Technical Report #17: Unicode Character Encoding Model, https://www.unicode.org/reports/tr17/

Slide 19

Slide 19 text

複数のコードポイントで表現される文字もある 文字画像の出典: https://ja.wikipedia.org/wiki/%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB:U845b-k.svg https://ja.wikipedia.org/wiki/%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB:U845b.svg 「かつしかく」 の1文字目 「かつらぎし」 の1文字目 U+845B U+E0101 U+845B U+E0100 異体字: 見た目は異なるが意味は同じ文字 →同一な文字とみなされて同じコードポ イントが割り当てられた →賛否両論あったらしい

Slide 20

Slide 20 text

複数のコードポイントで表現される文字もある 文字画像の出典: https://ja.wikipedia.org/wiki/%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB:U845b-k.svg https://ja.wikipedia.org/wiki/%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB:U845b.svg 「かつしかく」 の1文字目 「かつらぎし」 の1文字目 U+845B U+E0101 U+845B U+E0100 異体字セレクタによって指定できる

Slide 21

Slide 21 text

複数のコードポイントで表現される文字もある a U+61 61 UTF-8 コードポイント バイト列 あ U+3042 E3 81 82 UTF-8 U+845B U+E0100 E8 91 9B F3 A0 84 80 UTF-8 コードポイント数を数えると は2文字になる

Slide 22

Slide 22 text

複数のコードポイントで表現される文字もある a U+61 61 UTF-8 コードポイント バイト列 あ U+3042 E3 81 82 UTF-8 U+845B U+E0100 E8 91 9B F3 A0 84 80 UTF-8 書記素クラスタを使うと、 「ユーザが認識する『文字』の境界」 を決定できる。 仮説3:書記素クラスタ数を 文字数としている?

Slide 23

Slide 23 text

どの仮説が正しい? ● 仮説1:バイト数? ● 仮説2:コードポイント数? ● 仮説3:書記素クラスタ数?

Slide 24

Slide 24 text

実験してみた ● 使用しているDBMSはPostgreSQL ● varchar(1)の列を定義し、いろいろな文字を挿入してみた ○ a(U+0061):挿入可 ○ あ(U+3042):挿入可 ○  (U+845B U+E0100):挿入不可 ● →文字数は、コードポイント数でカウントされるようだ(仮説2が正しそう) ● →varchar(1)には「コードポイント1つ分」が挿入できそうだ

Slide 25

Slide 25 text

すべてのコードポイントが挿入可能なのか? null文字(0x0000)以外のすべてのコードポイントを挿入するプログラムを作成 してみた Unicodeコードポイント(約110万個) 0 0x10ffff

Slide 26

Slide 26 text

すべてのコードポイントが挿入可能なのか? null文字(0x0000)以外のすべてのコードポイントを挿入するプログラムを作成 してみた →一部の範囲のコードポイントが挿入できなかった Unicodeコードポイント(約110万個) 0 0x10ffff

Slide 27

Slide 27 text

すべてのコードポイントが挿入可能なのか? Unicodeコードポイント(約110万個) 0 0x10ffff UTF-16のサロゲートペアに使用される領域。 単独では文字を表さない

Slide 28

Slide 28 text

すべてのコードポイントが挿入可能なのか? Unicodeスカラ値(USV) 0 0x10ffff

Slide 29

Slide 29 text

すべてのコードポイントが挿入可能なのか? Unicodeスカラ値(USV) 0 0x10ffff varchar(1)にどのような文字を挿入できるか? →USV - {0}の任意の値1つ(PostgreSQLでは)

Slide 30

Slide 30 text

追加の調査 ● フロントエンドやバックエンドでは、バリデーション処理で文字数を数えて いる。このカウント方法はコードポイント数によるか? そうでない場合、 DBのカウント方式と差異があるが、おかしな挙動にならないか? ● サロゲートペアが単独で入力されることはないか? された場合、DBではエ ラーになるが、システム全体としてはどうなる? →深堀りを行うことで、新しい観点が得られ、横展開調査ができた →結果としては問題なかった

Slide 31

Slide 31 text

事例2

Slide 32

Slide 32 text

事例2:WebSocketとHTTP ● 背景 ○ チャット機能では、メッセージサーバーとの通 信をWebSocketで実現している ● 事例 ○ リリース前試験において、リクルート社内の一 部のセキュリティが厳しい環境で、チャット機 能が使えない ● 補足 ○ その環境では、ファイアウォールにより、あら かじめ許可された種類の通信しかできない

Slide 33

Slide 33 text

チャット機能の通信を許可してもらうことに ファイアウォール担当「WebSocket通信を利用したいのですね。であれば、 HTTPの許可は不要ですかね? TCPポートだけ許可すれば良いですか?」

Slide 34

Slide 34 text

チャット機能の通信を許可してもらうことに ファイアウォール担当「WebSocket通信を利用したいのですね。であれば、 HTTPの許可は不要ですかね? TCPポートだけ許可すれば良いですか?」 私「はい……おそらくHTTPの許可は必要ないと思います……? TCPポートは 443番を使用しているようなので、許可していただけますか……?」

Slide 35

Slide 35 text

チャット機能の通信を許可してもらうことに ファイアウォール担当「WebSocket通信を利用したいのですね。であれば、 HTTPの許可は不要ですかね? TCPポートだけ許可すれば良いですか?」 私「はい……おそらくHTTPの許可は必要ないと思います……? TCPポートは 443番を使用しているようなので、許可していただけますか……?」 →TCPだけ許可してもチャット機能は使えず

Slide 36

Slide 36 text

チャット機能の通信を許可してもらうことに ファイアウォール担当「WebSocket通信を利用したいのですね。であれば、 HTTPの許可は不要ですかね? TCPポートだけ許可すれば良いですか?」 私「はい……おそらくHTTPの許可は必要ないと思います……? TCPポートは 443番を使用しているようなので、許可していただけますか……?」 →TCPだけ許可してもチャット機能は使えず 私「あっ、やっぱりHTTPも関係するみたいなので許可をお願いします……」 →ようやくチャット機能が利用可能に

Slide 37

Slide 37 text

私は、WebSocketのことを 何もわかっていない……

Slide 38

Slide 38 text

私の趣味 ● プロトコルスタックの自作 ○ イーサネット〜TCPまでの、C言語での簡易的な自作実装はすでにあっ た ● イーサネット〜WebSocketまでを(簡易的にでも)全部実装すれば、理解を 深められるかもと思った ● 送ったデータをそのまま返す、WebSocket Echoサーバーを実装してみる

Slide 39

Slide 39 text

実装したWebSocket Echoサーバーの動作

Slide 40

Slide 40 text

TCP接続をパッシブオープン ● クライアントからのWebSocketリクエストを待ち受け

Slide 41

Slide 41 text

クライアントからの最初のリクエスト ● GET / HTTP/1.1 ● Host: xxx ● Upgrade: websocket ○ WebSocket通信にアップグレードしたいと言っている ● Connection: Upgrade ● Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== ○ 次に応答を返すために必要 ● Sec-WebSocket-Version: 13

Slide 42

Slide 42 text

サーバーからのレスポンス ● HTTP/1.1 101 Switching Protocols ● Upgrade: websocket ● Connection: Upgrade ● Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo= ○ 先ほどのSec-WebSocket-Keyに、仕様で決まっているマジックナンバーを追 加し、sha1をとって更にbase64エンコードしたもの ○ 正しい手順でWebSocket要求が受け入れられたことをクライアントに示す →WebSocket接続完了 →先ほどのファイアウォール問題で、TCPだけでなくHTTPも許可する必要があった理由

Slide 43

Slide 43 text

ここから先はバイナリで通信する ● 以下のような形式のバイナリデータがTCPソケットで送られてくる ● 仕様通りにフラグやマスキングの処理を行うと、ペイロードが得られる ● 同じようにして、ペイロードをそのまま返却すると…… 出典:FETTE, Ian; MELNIKOV, Alexey. Rfc 6455: The websocket protocol. 2011.

Slide 44

Slide 44 text

動作した! →うれしい

Slide 45

Slide 45 text

事例3

Slide 46

Slide 46 text

事例3:WebSocketのクローズ忘れ ● 事例 ○ チャット画面を閉じた後も、WebSocketの接続が残り続けてしまう場合 がある ■ システムに余分な負荷がかかってしまう ● 原因 ○ WebSocketのクローズ処理の実装漏れ ● 対策 ○ クローズ処理を追加する

Slide 47

Slide 47 text

チャット画面を開閉してChrome DevToolsの表示を確認 クローズ処理の追加前後で、通信状況を比較してテスト クローズ処理追加前 クローズ処理追加後 →この表示だけを証拠に「接続が閉じた」と判断するのは心もとなかった

Slide 48

Slide 48 text

WebSocketについて学んだ今なら ● 「WebSocketの通信=TCPソケット」とわかっている ○ →OSのTCPスタックに問い合わせれば状況がわかる ○ ESTAB状態のソケット数の増減を調べることで、確実にWebSocketがク ローズされていることを確認して、修正をリリースできた ● 学んでいなかった以前の私なら「そもそもWebSocketの『接続』とは何だろ う……?」となっていた気がする

Slide 49

Slide 49 text

まとめ ● どの事例も、修正自体は軽微なもの

Slide 50

Slide 50 text

まとめ ● どの事例も、修正自体は軽微なもの ● しかし、そこから深堀りすると、物事の仕組みがわかって楽しい ○ →私の楽しみ

Slide 51

Slide 51 text

まとめ ● どの事例も、修正自体は軽微なもの ● しかし、そこから深堀りすると、物事の仕組みがわかって楽しい ○ →私の楽しみ ● それが、類似の問題を防いだり、確実に解決したりすることにつながる ○ →サービスの利用者、求職者や企業 / エージェントのため

Slide 52

Slide 52 text

まとめ ● どの事例も、修正自体は軽微なもの ● しかし、そこから深堀りすると、物事の仕組みがわかって楽しい ○ →私の楽しみ ● それが、類似の問題を防いだり、確実に解決したりすることにつながる ○ →サービスの利用者、求職者や企業 / エージェントのため ● 「私の楽しみ」と「サービス利用者のため」を両立させていきたい