Link
Embed
Share
Beginning
This slide
Copy link URL
Copy link URL
Copy iframe embed code
Copy iframe embed code
Copy javascript embed code
Copy javascript embed code
Share
Tweet
Share
Tweet
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
まとめ ● どの事例も、修正自体は軽微なもの ● しかし、そこから深堀りすると、物事の仕組みがわかって楽しい ○ →私の楽しみ ● それが、類似の問題を防いだり、確実に解決したりすることにつながる ○ →サービスの利用者、求職者や企業 / エージェントのため ● 「私の楽しみ」と「サービス利用者のため」を両立させていきたい