Slide 1

Slide 1 text

PHPでデータベースを作ってみた @hanhan1978 PHPカンファレンス福岡2024

Slide 2

Slide 2 text

@hanhan1978 名前 富所 亮 所属 株式会社カオナビ CTO室 BackEnd Re-architecturing Team (BERT) Blog https://blog.hanhans.net Podcast https://podcasters.spotify.com/pod/show/yokohama-north-am 2

Slide 3

Slide 3 text

免責事項

Slide 4

Slide 4 text

この登壇への注意 本発表はRDBMSっぽく振る舞うプログラムを 気軽なノリで作っていくお話です。

Slide 5

Slide 5 text

この登壇への注意 ● パーサー、オプティマイザ ● ストレージエンジン ● などなどのRDBMSの構成要素 取り扱いません!

Slide 6

Slide 6 text

そもそも目的は!?

Slide 7

Slide 7 text

目的 ● データベースを深く理解する ● プロトコルを深く理解する ● プログラムへの解像度を上げる

Slide 8

Slide 8 text

目的 ● データベースを深く理解する ● プロトコルを深く理解する ● プログラムへの解像度を上げる 全部ウソ!

Slide 9

Slide 9 text

(答え)なんとなく作ってみたかった

Slide 10

Slide 10 text

ではデータベースを自作するための 参考資料とは?

Slide 11

Slide 11 text

参考書1 WEB+DB PRESS Vol.122 作って学ぶRDBMSのしくみ - 技術評論社 2021

Slide 12

Slide 12 text

参考書2 詳説データベース - ストレージエンジンと分散データシステムの仕組み - O’Reilly 2021

Slide 13

Slide 13 text

参考書3 Software Design 2024年6月号 - 技術評論社

Slide 14

Slide 14 text

参考書4 https://speakerdeck.com/hanhan1978/b-plus-tree-101

Slide 15

Slide 15 text

否!!

Slide 16

Slide 16 text

まじめに作ろうとすると 大変な努力が必要になる

Slide 17

Slide 17 text

もっと迂闊に作りたい

Slide 18

Slide 18 text

真の参考書 ゼロからトースターを作ってみた結果 - 新潮文庫 2015

Slide 19

Slide 19 text

不格好でもいいので、動く完成品を作ること は学びにとって実に良いことです

Slide 20

Slide 20 text

駄目な例 Learn to draw fast - https://www.alibati.com/work/horse

Slide 21

Slide 21 text

動く完成品を作ろう https://blog.crisp.se/2016/01/25/henrikkniberg/making-sense-of-mvp

Slide 22

Slide 22 text

目標の具体化 ここまで4分

Slide 23

Slide 23 text

MySQLと何なのか?

Slide 24

Slide 24 text

PHPからみたMySQL よくみるPDOのサンプルコード

Slide 25

Slide 25 text

PHPからみたMySQL 3306ポートでTCP/IPのソケット通信

Slide 26

Slide 26 text

つまり

Slide 27

Slide 27 text

目標 同じ通信内容ならPHPからはデータベースに見える!

Slide 28

Slide 28 text

目標を具体化 1. 特定のポート番号でソケット通信を行う 2. MySQLと同じ通信内容を行う データベースを作る

Slide 29

Slide 29 text

余談

Slide 30

Slide 30 text

この考え方は様々なミドルウェアに対応 ● Redis ● ElasticSearch ● Apache ようするにTCP/IPでソケット通信を行うアプリケーションは 既存の挙動を真似ることさえ出来れば同じように作れる

Slide 31

Slide 31 text

では!作っていく!

Slide 32

Slide 32 text

1. 特定のポート番号でソケット通信を行う 2. MySQLと同じ通信内容を行う やること

Slide 33

Slide 33 text

1. 特定のポート番号でソケット通信を行う 2. MySQLと同じ通信内容を行う やること ネットワークプログラミング

Slide 34

Slide 34 text

参考書 UNIXネットワークプログラミング Vol.1 ※もっと新しい本 でよいです

Slide 35

Slide 35 text

echoサーバー 今回の用途であれば 「echoサーバー」という検索ワードを使うと サンプル実装がいっぱい引っかかります

Slide 36

Slide 36 text

echoサーバー 今回の用途であれば 「echoサーバー」という検索ワードを使うと サンプル実装がいっぱい引っかかります が!

Slide 37

Slide 37 text

2年前に書いておきました PHPerKaigi2022 - パンフレット

Slide 38

Slide 38 text

具体的なコード

Slide 39

Slide 39 text

実際に起動してみると... うごくぞ...

Slide 40

Slide 40 text

MySQLの場合 3306番で動作してる

Slide 41

Slide 41 text

TCP/IPの状態確認 Mac Linux

Slide 42

Slide 42 text

1. 特定のポート番号でソケット通信を行う 2. MySQLと同じ通信内容を行う やること 進捗50%!

Slide 43

Slide 43 text

1. 特定のポート番号でソケット通信を行う 2. MySQLと同じ通信内容を行う やること

Slide 44

Slide 44 text

1. 特定のポート番号でソケット通信を行う 2. MySQLと同じ通信内容を行う やること ???どうやんの???

Slide 45

Slide 45 text

通信内容をみるには? ● Wireshark ● tcpdump ● ngrep

Slide 46

Slide 46 text

通信内容をみるには? ● Wireshark ● tcpdump ● ngrep 今回はお手軽な ngrep を採用

Slide 47

Slide 47 text

さっそくMySQLの通信を見る -x -q -d 通信の中身を16進数表記でdump、かつ右側にテキスト表示 header, payload 以外の通信内容はdumpしない 通信をみるネットワークインタフェースを指定

Slide 48

Slide 48 text

さっそくMySQLの通信を見る 引数は match と filter 上の例は match は無指定、filter に port 3306 を指定 これで3306番ポートにまつわるローカル ホストの通信が確認できる

Slide 49

Slide 49 text

通信内容をみる1

Slide 50

Slide 50 text

通信内容をみる1 まずサーバー側からスタート

Slide 51

Slide 51 text

通信内容をみる1 クライアントの認証リクエスト

Slide 52

Slide 52 text

通信内容をみる1 サーバー側からのOK応答

Slide 53

Slide 53 text

こんなもの読めるのか?

Slide 54

Slide 54 text

MySQL公式ドキュメント https://dev.mysql.com/doc/dev/mysql-server/latest/

Slide 55

Slide 55 text

Handshake https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_connection_phase.html

Slide 56

Slide 56 text

Protocol::HandshakeV10

Slide 57

Slide 57 text

これで読めそうやん!

Slide 58

Slide 58 text

プロトコル読解 - Handshake

Slide 59

Slide 59 text

プロトコル読解 - Handshake いきなり違う!

Slide 60

Slide 60 text

おちつけ!

Slide 61

Slide 61 text

MySQL Packets

Slide 62

Slide 62 text

MySQL Packets 基本仕様

Slide 63

Slide 63 text

プロトコル読解 - Handshake 最初の3Byteは payloadのサイズ ▶ 4a 00 00 ▶ 74 Byte 次の1ByteはシーケンスID ▶ 00 ▶ 0

Slide 64

Slide 64 text

プロトコル読解 - Handshake このパケットは Header 4 Byte + Payload 74 Byte の 78 Bytea MySQLのパケットを考える際は常にこの Header を考慮にいれる

Slide 65

Slide 65 text

プロトコル読解 - Handshake 0x0a ▶ 10進数の10

Slide 66

Slide 66 text

プロトコル読解 - Handshake サーバーバージョン文字列

Slide 67

Slide 67 text

ASCIIコード表 https://www.sciencebuddies.org/science-fair-projects/references/ascii-table

Slide 68

Slide 68 text

ASCIIコード表 https://www.sciencebuddies.org/science-fair-projects/references/ascii-table 8 ▶ 0x38

Slide 69

Slide 69 text

サーバーからの認証OKパケット

Slide 70

Slide 70 text

通信内容をみる1 サーバー側からのOK応答

Slide 71

Slide 71 text

General Response Packets

Slide 72

Slide 72 text

プロトコル読解 - General Response 7ByteのPayload header ▶ 0x00 affected_rows ▶ 0x00 last_insert_id ▶ 0x00

Slide 73

Slide 73 text

プロトコル読解 - General Response 7ByteのPayload header ▶ 0x00 affected_rows ▶ 0x00 last_insert_id ▶ 0x00 君はここにいたのか!!

Slide 74

Slide 74 text

PDO::lastInsertId https://www.php.net/manual/ja/pdo.lastinsertid.php

Slide 75

Slide 75 text

読める!読めるぞ!

Slide 76

Slide 76 text

だが、このまま細かくプロトコルを 実装するのは骨が折れる 第一面倒くさい。俺はデータベースっ ぽいやつを早く作りたい

Slide 77

Slide 77 text

こうして...

Slide 78

Slide 78 text

こうじゃ! ※良い子はマネしない

Slide 79

Slide 79 text

さらに、こうして...

Slide 80

Slide 80 text

こうじゃ!!!! ※良い子はマネしない

Slide 81

Slide 81 text

クライアント側のソースコード なんと、こんなものでも PDOをコロッとだますと接続できる

Slide 82

Slide 82 text

このままDEMOになだれ込みたいが... 結果セット返却の解説は必要 もうちょっと我慢してくれ 残り15分以上

Slide 83

Slide 83 text

SELECTの通信

Slide 84

Slide 84 text

SELECTの通信 Header ▶ Payloadサイズは37

Slide 85

Slide 85 text

SELECTの通信 Header ▶ Payloadサイズは37 Payload ▶ 先頭に 0x03 ▶ ETX 制御コード

Slide 86

Slide 86 text

SELECTの通信 Header ▶ Payloadサイズは37 Payload ▶ 先頭に 0x03 ▶ ETX 制御コード Payload残り ▶ クエリの文字列 SELECT id, value1, value2 FROM items

Slide 87

Slide 87 text

SELECTの通信 結果セットの送信パケットを適切に理解することが大切 このパケットは合計8つのMySQLパケットで構成されている

Slide 88

Slide 88 text

SELECTの通信 1Byte の Payload 最初のパケットは結果セットのカラム数 0x03

Slide 89

Slide 89 text

SELECTの通信 40Byte の Payload 2番目以降はカラム定義 x コラム数のパケット

Slide 90

Slide 90 text

SELECTの通信 40Byte の Payload 03 646566 ▶ 3byte の文字列メッセージ def 固定値

Slide 91

Slide 91 text

SELECTの通信 40Byte の Payload 04 74657374▶ 4byte の文字列メッセージ test データベース名

Slide 92

Slide 92 text

SELECTの通信 40Byte の Payload 04 74657374▶ 4byte の文字列メッセージ test データベース名 05 6974656d73▶ 5byte の文字列メッセージ items テーブル名 05 6974656d73▶ 5byte の文字列メッセージ items オリジナルのテーブル名

Slide 93

Slide 93 text

SELECTの通信 40Byte の Payload 04 74657374▶ 4byte の文字列メッセージ test データベース名 05 6974656d73▶ 5byte の文字列メッセージ items テーブル名 05 6974656d73▶ 5byte の文字列メッセージ items オリジナルのテーブル名 02 6964▶ 2byte の文字列メッセージ id カラム名 02 6964▶ 2byte の文字列メッセージ id オリジナルのカラム名

Slide 94

Slide 94 text

SELECTの通信 40Byte の Payload 0c ▶ 固定値 3f00 ▶ キャラセット binary

Slide 95

Slide 95 text

SELECTの通信 40Byte の Payload 0c ▶ 固定値 3f00 ▶ キャラセット binary 0b000000 ▶ カラムサイズ int(11)

Slide 96

Slide 96 text

SELECTの通信 40Byte の Payload 0c ▶ 固定値 3f00 ▶ キャラセット binary 0b000000 ▶ カラムサイズ int(11) 03 ▶ データ型

Slide 97

Slide 97 text

SELECTの通信 40Byte の Payload 0c ▶ 固定値 3f00 ▶ キャラセット binary 0b000000 ▶ カラムサイズ int(11) 03 ▶ データ型 0350 ▶ ビット演算されたフラグ

Slide 98

Slide 98 text

SELECTの通信 40Byte の Payload 0c ▶ 固定値 3f00 ▶ キャラセット binary 0b000000 ▶ カラムサイズ int(11) 03 ▶ データ型 0350 ▶ ビット演算されたフラグ 000000 ▶ Packet終端

Slide 99

Slide 99 text

SELECTの通信 結果で返却するカラム数の数だけカラム定義のPacketを返す 今回は3カラム分

Slide 100

Slide 100 text

参考資料 - データタイプ https://github.com/mysql/mysql-server/blob/trunk/include/field_types.h

Slide 101

Slide 101 text

参考資料 - データタイプ https://github.com/mysql/mysql-server/blob/trunk/include/field_types.h

Slide 102

Slide 102 text

参考資料 https://dev.mysql.com/doc/dev/mysql-server/latest/group__group__cs__column__definition__flags.html

Slide 103

Slide 103 text

フラグのビット演算例 NOT NULL 0110

Slide 104

Slide 104 text

フラグのビット演算例 PRIMARY KEY 0350

Slide 105

Slide 105 text

SELECTの通信 カラム定義と結果セットの間のデリメター(だと思う) fe00002200 ▶ シーケンス番号以外は固定

Slide 106

Slide 106 text

SELECTの通信 結果行 - 1行目 0131 ▶ idカラムの結果 文字列 1 05686f676531 ▶ value1カラムの結果 文字列 hoge1 05686f676532 ▶ value2カラムの結果 文字列 hoge2

Slide 107

Slide 107 text

SELECTの通信 結果行 - 1行目 0131 ▶ idカラムの結果 文字列 1 05686f676531 ▶ value1カラムの結果 文字列 hoge1 05686f676532 ▶ value2カラムの結果 文字列 hoge2 intは文字列で返ってくる PDO, ORMなどで型変換の不具合イシューが上がっ てくるのはここらへんの仕様が関係ありそう

Slide 108

Slide 108 text

SELECTの通信 最後にもう一回デリミタが入ってくる 以上で長かった結果セットの説明も終わりです。

Slide 109

Slide 109 text

簡単にまとめると... Handshake ▶ OKパケット DML, 更新系クエリ ▶ OKパケット 参照系クエリ ▶ カラム定義を含む、結果セットのパケット 細かくはもっといろいろあるのだけど 偽データベースを動作させるには十分

Slide 110

Slide 110 text

余談

Slide 111

Slide 111 text

クエリのパース https://github.com/greenlion/PHP-SQL-Parser

Slide 112

Slide 112 text

クエリのパース ここまで理解できれば、作れるはず だ!雑なRDBMSが!

Slide 113

Slide 113 text

ore no database

Slide 114

Slide 114 text

ore no database

Slide 115

Slide 115 text

ore no database

Slide 116

Slide 116 text

DEMO 残り2分

Slide 117

Slide 117 text

今後の展開 ● テストを拡充 ● データの保存・読取 ● IO多重化 ● インデックス ● Information Schema対応

Slide 118

Slide 118 text

クエリのパース 最低限の動作を満たした動くDB そこから正しい形に整形していけばいい

Slide 119

Slide 119 text

まとめ

Slide 120

Slide 120 text

自作は楽しい!

Slide 121

Slide 121 text

みんなも変なモノ作っていこうな!