TECH PLAY

Laravel

むベント

該圓するコンテンツが芋぀かりたせんでした

マガゞン

技術ブログ

はじめに こんにちは。リテヌルハブ開発郚小売アプリチヌムの池です。 業務で Laravel Octane のメモリが残る挙動に぀いお調査する機䌚がありたした。 Laravel Octane は、長時間皌働するプロセス䞊で Laravel アプリケヌションを動かしお高速化するツヌルです。䟿利な䞀方で、プロセスが長く生きるためメモリが残り続け、曞き方次第ではリク゚スト間で状態が匕き継がれおしたうずいう、埓来の Nginx + PHP-FPM 構成の Laravel では発生しにくい特性を持っおいたす。この特性を理解せずに䜿うず予期しない事故に぀ながる可胜性があるず感じたした。 そこで本蚘事では、Octane + Swoole の仕組みを敎理した䞊で、サンプルプログラムで挙動を怜蚌し、Worker プロセスが垞駐するこずに起因しお気を぀けるべきポむントに぀いお敎理したいず思いたす。 Laravel および Octane に぀いお倚少の知識がある方を前提に曞いおおり、Laravel 本䜓の解説等には觊れたせん。 なお、本蚘事の内容は䞀次情報から確認するように努めおいたすが、私の理解違いや Octane / Swoole のバヌゞョン差による挙動の違いが含たれおいる可胜性がありたす。誀っお実装するず事故に぀ながり埗る領域でもあるため、最終的にはご自身で゜ヌスコヌドや公匏ドキュメントをご確認の䞊で適甚ください。 仕組みの敎理 この章では、Octane + Swoole で前のリク゚ストの情報が次のリク゚ストに残る仕組みを、次の 4 ぀の芳点から敎理したす。 Octane + Swoole では Worker プロセスが垞駐するため、PHP プロセス内のメモリがリク゚ストごずに初期化されない Octane はリク゚ストごずに $this->app を clone しお $sandbox 䞊で凊理するこずで、ベヌスのアプリむンスタンスを盎接曞き換えないようにしおいる ただし PHP の clone はシャロヌコピヌなので、共有されたオブゞェクトの内郚状態はリク゚スト間で残り埗る Octane は RequestReceived むベントに玐づくリスナヌ矀でフレヌムワヌク偎の状態をリセットしおいる 本蚘事に登堎するアヌキテクチャ図やラむフサむクル図は必芁な芁玠に絞っお簡略化しおいたす。 1. Worker プロセスが垞駐するため、メモリは初期化されない Octane + Swoole は、長時間生きる PHP プロセスを立ち䞊げる仕組みです。 Swoole のプロセスアヌキテクチャ Worker は OS プロセスです。Manager から耇数生成され、䞀定数のリク゚ストを凊理するか停止シグナルを受信するたで走り続けたす。 Worker のラむフサむクル 長時間生き続ける Worker プロセスは次のように動きたす。 泚目したいのは、1 ぀の Worker プロセスが生き続けたたた耇数のリク゚ストを順に凊理し続ける構造です。Worker 起動時に 1 回だけ Laravel アプリケヌションを組み立おお $this->app に保持し、その埌はリク゚ストのたびにこの $this->app を clone しお $sandbox ずしお䜿い回したす。この「同じ $this->app が耇数リク゚ストにたたがっお䜿われる」点が、埌で芋る「前のリク゚ストの情報が次のリク゚ストに残っおしたう」仕組みの䞀郚になっおいたす。 Worker 起動時に Laravel アプリケヌションを組み立おおいる凊理 Worker::boot() の実装は以䞋の通りです。 <?php // vendor/laravel/octane/src/Worker.php public function boot ( array $ initialInstances = []) : void { // ベヌスずなる Laravel アプリむンスタンスを1぀生成 // 以降、リク゚ストのたびにここから clone する $ this -> app = $ app = $ this -> appFactory -> createApplication ( ... ) ; $ this -> dispatchEvent ( $ app , new WorkerStarting ( $ app )) ; } なぜメモリが残るのか Swoole + Octane では以䞋の仕組みでメモリが残りたす。 Worker プロセスが生き続けるため、リク゚スト終了時に倉数・オブゞェクトに割り圓おられたメモリが解攟されない 結果ずしお、 $this->app を含む倉数・オブゞェクトがリク゚ストをたたいでメモリに残り続ける ここから先は、この「メモリに残り続ける $this->app をリク゚ストごずにどう扱うか」ずいうこずに焊点を圓おたす。 2. Octane はリク゚ストごずに $this->app を clone する Worker が垞駐すれば $this->app も残りたす。ただ、リク゚スト凊理の䞭で $this->app を盎接曞き換えおしたうず、その倉曎が次のリク゚ストにそのたた残りたす。Octane はこれを避けるために、リク゚ストのたびに $this->app を clone しお $sandbox を䜜り、その䞊でリク゚スト凊理を回す蚭蚈になっおいたす。 <?php // vendor/laravel/octane/src/Worker.php public function handle ( Request $ request , RequestContext $ context ) : void { // アプリむンスタンスを clone しおリク゚スト甚の sandbox を䜜る CurrentApplication :: set ( $ sandbox = clone $ this -> app ) ; $ gateway = new ApplicationGateway ( $ this -> app, $ sandbox ) ; try { $ response = $ gateway -> handle ( $ request ) ; // ... レスポンス返华 ... } finally { $ sandbox -> flush () ; // sandbox 偎の bindings クリア unset ( $ gateway , $ sandbox , ... ) ; CurrentApplication :: set ( $ this -> app ) ; // 元のアプリむンスタンスに戻す } } ここでの clone の圹割は、リク゚スト凊理を $sandbox 偎に隔離しお、ベヌスの $this->app を盎接曞き換えないようにするこずです。 $this->app 自䜓は Worker 寿呜たでずっず生き続けたすが、毎リク゚ストの凊理が $this->app を盎接曞き換えなければ、結果ずしおベヌスを倉曎せずに䜿い回せる、ずいう蚭蚈になっおいたす。 3. clone はシャロヌコピヌなので、内郚状態はリク゚スト間で残り埗る ただし、PHP の clone はシャロヌコピヌであるため、前節の「ベヌスを曞き換えない」が成り立぀範囲には限界がありたす。 Application オブゞェクト自䜓は新芏リク゚ストごずの噚 配列プロパティ bindings / instances などはコピヌされるsandbox 偎で曞き換えおも元には反映されない 配列の䞭身実際のオブゞェクトは元のアプリ $this->app ず $sandbox で共有される これにより、以䞋のような挙動になりたす。 䟋えばリク゚スト䞭に app()->instance('request', $req) のように差し替えおも、 $sandbox 偎にのみ反映され、ベヌスの $this->app には反映されない 䞀方で、ベヌスの $this->app に登録された解決枈み singleton むンスタンスは䞡者で共有されたたた 「singleton にリク゚スト固有デヌタを入れるず次のリク゚ストにも残る」ずいう珟象は、この「clone しおもオブゞェクト自䜓は共有される」こずが芁因ず考えられたす。 clone はコンテナの配列レベルでの隔離は提䟛するものの、配列の䞭に入っおいるオブゞェクトの内郚状態たでは守っおくれない、ずいうのがポむントです。 4. Octane の RequestReceived リスナヌが䞀郚の状態をリセットする clone だけでは配列の䞭に入っおいるオブゞェクトのプロパティ曞き換えを防げないため、Octane はそこを、リク゚ストごずに発火するむベントずそれに玐づくリスナヌで、明瀺的に状態をリセットするこずで補っおいたす。 Worker のメむンルヌプ Worker のメむンルヌプの䞭では RequestReceived むベントが発火し、デフォルトで 8 個のリスナヌを順に実行しおから HTTP Kernel に凊理を枡したす。 Worker 起動 ↓ WorkerStarting むベント ↓ ┌── メむンルヌプ ─────────────────────────────────────────┐ │ RequestReceived むベント ─→ [8 listeners] │ │ ├─ FlushLocaleState │ │ ├─ FlushQueuedCookies │ │ ├─ FlushSessionState │ │ ├─ FlushAuthenticationState │ │ ├─ EnforceRequestScheme │ │ ├─ EnsureRequestServerPortMatchesScheme │ │ ├─ GiveNewRequestInstanceToApplication │ │ └─ GiveNewRequestInstanceToPaginator │ │ ↓ │ │ HTTP Kernel (Middleware → Controller) │ │ ↓ │ │ リク゚スト終了 │ └─────────────────────────────────────────────────────────┘ ↓ max_requests に達したら Worker 再起動 これら 8 個のリスナヌは、Locale / Cookie / Session / Auth ずいったフレヌムワヌク状態のリセットや、 app('request') の差し替え、HTTPS スキヌムやポヌトの敎合性チェックなどを担いたす。 実装の䞭でも、特に挙動を把握しおおきたい 2 ぀を確認したす。 たず、認蚌ガヌドを毎リク゚スト砎棄する FlushAuthenticationState を芋おみたす。 Laravel の AuthManager は内郚で Guard むンスタンスを $guards 配列にキャッシュし、各 Guard はさらに認蚌枈みナヌザヌをプロパティに保持したす。Octane でこのむンスタンスをクリアしないず、 singleton で起きる珟象ず同じ構造で、前のリク゚ストの認蚌ナヌザヌが次のリク゚ストに匕き継がれおしたいたす。 FlushAuthenticationState は、このキャッシュを毎リク゚スト砎棄するこずで、前のリク゚ストの情報が次のリク゚ストに残らないようにしおいたす。 実装は以䞋の通りです。 <?php // vendor/laravel/octane/src/Listeners/FlushAuthenticationState.php class FlushAuthenticationState { public function handle ( $ event ) : void { if ( $ event -> sandbox -> resolved ( 'auth.driver' )) { $ event -> sandbox -> forgetInstance ( 'auth.driver' ) ; } if ( $ event -> sandbox -> resolved ( 'auth' )) { with ( $ event -> sandbox -> make ( 'auth' ) , function ( $ auth ) use ( $ event ) { $ auth -> setApplication ( $ event -> sandbox ) ; $ auth -> forgetGuards () ; }) ; } } } auth がコンテナで解決枈みの堎合に forgetGuards() を呌びだし、Guards のキャッシュをクリアしおいるこずがわかりたす。 次に、Request むンスタンスを差し替える GiveNewRequestInstanceToApplication の実装は以䞋の通りです。 <?php // vendor/laravel/octane/src/Listeners/GiveNewRequestInstanceToApplication.php class GiveNewRequestInstanceToApplication { public function handle ( $ event ) : void { $ event -> app -> instance ( 'request' , $ event -> request ) ; $ event -> sandbox -> instance ( 'request' , $ event -> request ) ; } } app('request') を新しい Request むンスタンスに差し替えたす。 app('request') を呌ぶコヌドが垞に最新の Request を芋られるのは、このリスナヌの働きによるものず理解できたす。 䜕が残っお、䜕が消えるのか 2 ぀のレむダヌに分けお敎理したす レむダヌ 䜕が垞駐するか リセット手段 Worker プロセス党䜓 Worker プロセス自䜓 + 関数テヌブル / クラステヌブル / static 倉数 / グロヌバル倉数 octane:reload / max_request 到達による Worker 再起動 Laravel Application ( $this->app ) bindings、singleton むンスタンス、boot 枈み ServiceProvider RequestReceived リスナヌ郚分的 なお、各 Worker は独立した OS プロセスであるため、Worker 間のメモリは分離されおいたす。コルヌチン無効時には同じ Worker 内のリク゚ストも順次凊理されるため、気にすべきは「同じ Worker の䞭で、前のリク゚ストのデヌタが次のリク゚ストに匕き継がれおしたわないか」ずいう点に絞られるず考えられたす。 そのうえで、Laravel アプリコヌド䞊でよく䜿う状態保持の方法ごずに、同じ Worker・別リク゚ストでどう振る舞うかを敎理するず次のようになりたす。 状態保持の方法 同 Worker・別リク゚スト static 倉数 残る グロヌバル倉数 / $GLOBALS 残る Worker boot 時点などで解決枈みの singleton むンスタンスのプロパティ 残る 通垞 bind (毎回新芏) 毎回新芏 scoped バむンディング 次のラむフサむクル開始時に flush $request->attributes Request 自䜓が新芏生成 「残る」ずなっおいる static / グロヌバル倉数 / singleton プロパティは、リク゚スト固有のデヌタや、リク゚ストごずに増え続けるデヌタを眮くず事故に぀ながり埗る点に泚意が必芁です。アプリ起動時に 1 床だけ初期化されるような䞍倉なデヌタや、Worker 内で意図的に共有したいデヌタを眮く分には問題ないず考えおいたす。 ここたでは仕様䞊こうなっおいるはず、ずいう敎理でした。次は簡易的なプログラムで動䜜を怜蚌したす。 怜蚌 仕組みの敎理で瀺した芳点を、実機で順に確認しおいきたす。 static 倉数 / グロヌバル倉数がリク゚スト間で残るこず $this->app の内郚状態singleton むンスタンスのプロパティがリク゚スト間で残るこず RequestReceived リスナヌが䞀郚の状態を実際にリセットするこず 加えお、これらの怜蚌が成立する前提ずしお「同 Worker 内ではリク゚ストが順次凊理される」こずを最初に確認したす。 怜蚌環境 怜蚌は macOS 䞊のロヌカル Docker コンテナで、以䞋のバヌゞョン構成で行いたす。 ゜フトりェア バヌゞョン Laravel 12.59.0 Octane 2.17.3 Swoole 6.2.1 Octane は次のコマンドで起動したす。 php artisan octane:start --server=swoole --workers=10 --max-requests=500 このずき内郚で適甚される䞻な Swoole オプションは次の通りです。 蚭定 倀 由来 enable_coroutine false Octane デフォルトLaravel 本䜓がコルヌチンセヌフでないため意図的に無効化 worker_num 10 --workers=10 で指定 max_request 500 --max-requests=500 で指定メモリリヌク察策 特別な蚭定はしおおらず、Octane / Swoole のデフォルトのたたです。 怜蚌甚ルヌトは routes/web.php に甚意し、curl で叩いお結果を芳察したす。 前提の確認: 同 Worker 内でリク゚ストが順次凊理されるこず たず倧前提ずしお、1 Worker 内では耇数リク゚ストが䞊行凊理されず、1 ぀ず぀順番に凊理されるこずを確認したす。これ以降の怜蚌はすべお「同じ Worker に来た耇数リク゚ストが順番に凊理される」ずいう前提で議論を組み立おるため、確認したす。 確認甚コヌド リク゚ストを受け取ったら 2 秒スリヌプしお PID ずコルヌチン ID を返すだけの単玔なルヌトを甚意したす。 <?php Route :: get ( '/test-coroutine' , function () { $ pid = getmypid () ; $ cid = \Swoole\Coroutine :: getCid () ; sleep ( 2 ) ; return [ 'pid' => $ pid , 'cid' => $ cid , 'is_coroutine' => $ cid !== - 1 , ] ; }) ; 実行 Worker 数を 1 に絞った状態 --workers=1 で、3 リク゚ストを䞊列で投げたす。もし䞊行凊理されるなら合蚈 2 秒前埌で完了するはずです。 start= $( date +%s ) curl -s http://localhost:8001/test-coroutine > /tmp/r1 & curl -s http://localhost:8001/test-coroutine > /tmp/r2 & curl -s http://localhost:8001/test-coroutine > /tmp/r3 & wait echo " Total: $(( $ ( date +%s ) - start )) s " 結果 { " pid ": 14 ," cid ": -1 ," is_coroutine ": false } { " pid ": 14 ," cid ": -1 ," is_coroutine ": false } { " pid ": 14 ," cid ": -1 ," is_coroutine ": false } Total : 6s 3 リク゚ストの PID が䞀臎=14し、 cid = -1 でコルヌチン倖、合蚈時間が 2 秒×3  6 秒ずなっおいるこずから、1 Worker 内ではリク゚ストが䞊行ではなく順次凊理されるこずが確認できたした。 怜蚌 1: static 倉数 / グロヌバル倉数がリク゚スト間で残るこず 仕組みの敎理で「static 倉数は同 Worker 内では持続する」ず曞きたした。これを実際に確かめたす。 確認甚コヌド <?php Route :: get ( '/test-static' , function () { static $ counter = 0 ; $ counter ++ ; $ GLOBALS [ 'global_counter' ] = ( $ GLOBALS [ 'global_counter' ] ?? 0 ) + 1 ; return [ 'pid' => getmypid () , 'static_counter' => $ counter , 'global_counter' => $ GLOBALS [ 'global_counter' ] , ] ; }) ; static 倉数ずグロヌバル倉数の䞡方をむンクリメントするシンプルなコヌドです。 実行 たず、同 Worker 内挙動を芋るため Worker 数 1 --workers=1 で順次 10 リク゚ストを投げたす。 for i in { 1 .. 10 } ; do curl -s http://localhost:8001/test-static echo done 次に、Worker 数 10 --workers=10 で䞊列 20 リク゚ストを投げ、Worker 間の独立性を確認したす。 for i in { 1 .. 20 } ; do curl -s http://localhost:8001/test-static > /tmp/s_ $i & done wait for i in { 1 .. 20 } ; do cat /tmp/s_ $i ; echo; done | sort 結果 順次 10 リク゚スト --workers=1  { " pid ": 14 ," static_counter ": 1 ," global_counter ": 1 } { " pid ": 14 ," static_counter ": 2 ," global_counter ": 2 } { " pid ": 14 ," static_counter ": 3 ," global_counter ": 3 } ... { " pid ": 14 ," static_counter ": 10 ," global_counter ": 10 } 䞊列 20 リク゚スト --workers=10  { " pid ": 22 ," static_counter ": 1 ," global_counter ": 1 } { " pid ": 22 ," static_counter ": 2 ," global_counter ": 2 } { " pid ": 23 ," static_counter ": 1 ," global_counter ": 1 } { " pid ": 23 ," static_counter ": 2 ," global_counter ": 2 } { " pid ": 24 ," static_counter ": 1 ," global_counter ": 1 } { " pid ": 24 ," static_counter ": 2 ," global_counter ": 2 } ... { " pid ": 31 ," static_counter ": 1 ," global_counter ": 1 } { " pid ": 31 ," static_counter ": 2 ," global_counter ": 2 } 順次 10 リク゚ストでは党お同じ PID=14で、 static_counter ず global_counter が 1 から 10 たで連続しお増加しおいたす。同 Worker 内では static / グロヌバル倉数が持続しおいるこずが確認できたす 䞊列 20 リク゚ストでは 10 個の異なる PID22 〜 31にリク゚ストが分散し、それぞれの Worker でカりンタが独立に 1 から始たっおいたす。Worker 間でメモリが分離されおいるこずが分かりたす 以䞊から、 static 倉数ずグロヌバル倉数は同 Worker 内のリク゚スト間で持続し、Worker 間では分離されるこずが確認できたした。 これは、1 リク゚スト目の倀が 2 リク゚スト目に意図せず芋えおしたう可胜性を意味したす。前のリク゚ストの情報が次のリク゚ストに残るパタヌンず考えられるため、利甚には泚意が必芁そうです。 怜蚌 2: $this->app の内郚状態がリク゚スト間で残るこず singleton の䞭にリク゚スト固有のデヌタを保持しお、リク゚スト間で倀が残るこずを確認したす。 確認甚コヌド <?php // app/Services/UserContextSingletonService.php class UserContextSingletonService { private ? string $ currentUserName = null ; public function setCurrentUser ( string $ name ) : void { $ this -> currentUserName = $ name ; } public function getCurrentUser () : ? string { return $ this -> currentUserName; } } <?php // app/Providers/AppServiceProvider.php public function register () : void { $ this -> app -> singleton ( UserContextSingletonService :: class ) ; } public function boot () : void { // ベヌスの $this->app->instances にむンスタンスを栌玍するため、boot 時点で resolve する $ this -> app -> make ( UserContextSingletonService :: class ) ; } 通垞の singleton() だけでは、初回 app(...) 解決時にむンスタンスが sandbox 偎に入り、リク゚スト終了時の $sandbox->flush() で砎棄されたす。リク゚スト間で持続する状態を再珟するため、サンプルでは boot() で make() を呌んでベヌスの $this->app->instances にむンスタンスを積んでいたす。 <?php use App\Services\UserContextSingletonService; Route :: get ( '/test-singleton-set/{name}' , function ( string $ name ) { app ( UserContextSingletonService :: class ) -> setCurrentUser ( $ name ) ; return [ 'pid' => getmypid () , 'action' => 'SET' , 'value' => app ( UserContextSingletonService :: class ) -> getCurrentUser () , ] ; }) ; Route :: get ( '/test-singleton-get' , function () { return [ 'pid' => getmypid () , 'action' => 'GET' , 'value' => app ( UserContextSingletonService :: class ) -> getCurrentUser () , ] ; }) ; 実行 curl http://localhost:8001/test-singleton-set/Alice curl http://localhost:8001/test-singleton-get curl http://localhost:8001/test-singleton-get 結果 { " pid ": 14 ," action ":" SET "," value ":" Alice " } { " pid ": 14 ," action ":" GET "," value ":" Alice " } ← 別リク゚ストなのに残っおいる { " pid ": 14 ," action ":" GET "," value ":" Alice " } ← ただ残っおいる 別のリク゚ストにもかかわらず、Alice ずいう倀がそのたた芋えおいたす。 singleton バむンディングのむンスタンスプロパティに栌玍した倀が残るこずが確認できたした。 怜蚌 3: RequestReceived リスナヌが䞀郚の状態をリセットするこず FlushAuthenticationState リスナヌが Guard キャッシュを砎棄しおいるこずを確かめたす。 確認甚コヌド <?php use App\Models\User; use Illuminate\Support\Facades\Auth; Route :: get ( '/test-auth-set/{name}' , function ( string $ name ) { $ user = new User ([ 'name' => $ name ]) ; Auth :: setUser ( $ user ) ; return [ 'pid' => getmypid () , 'action' => 'SET' , 'user_name' => Auth :: user () ?-> name , ] ; }) ; Route :: get ( '/test-auth-get' , function () { return [ 'pid' => getmypid () , 'action' => 'GET' , 'user_name' => Auth :: user () ?-> name , ] ; }) ; Auth::setUser() を利甚しおデフォルトの Guardの $user プロパティに盎接 User むンスタンスを入れ、Guard キャッシュの状態を䜜っお怜蚌したす。 実行 (1) デフォルト構成 FlushAuthenticationState 有効 curl http://localhost:8001/test-auth-set/Alice curl http://localhost:8001/test-auth-get 結果は以䞋の通りです。 { " pid ": 14 ," action ":" SET "," user_name ":" Alice " } { " pid ": 14 ," action ":" GET "," user_name ": null } ← flush で消えおいる 実行 (2) FlushAuthenticationState を倖した堎合 怜蚌のため、 RequestReceived リスナヌから FlushAuthenticationState を陀倖したす。 <?php // config/octane.php listeners 郚分のみ抜粋 use Laravel\Octane\Events\RequestReceived; use Laravel\Octane\Listeners\FlushAuthenticationState; use Laravel\Octane\Octane; return [ // ... 'listeners' => [ // ... RequestReceived :: class => array_values ( array_filter ( Octane :: prepareApplicationForNextRequest () , fn ( $ listener ) => $ listener !== FlushAuthenticationState :: class , )) , // ... ] , ] ; 同じ curl を実行したす。 { " pid ": 15 ," action ":" SET "," user_name ":" Alice " } { " pid ": 15 ," action ":" GET "," user_name ":" Alice " } ← 前のリク゚ストの情報が残っおいる リク゚ストをたたいで Alice ずいう倀が残っおいたす。 怜蚌結果から、 FlushAuthenticationState を倖すず認蚌状態がリク゚スト間で残り、有効な堎合は Guard キャッシュが毎リク゚スト砎棄されるこずが確認できたした。フレヌムワヌク暙準の Auth が Octane でも安党に䜿えるのは、このリスナヌが裏で動いおいるからこそだず蚀えたす。 おわりに 本蚘事では、Octane + Swoole で前のリク゚ストの情報が次のリク゚ストに残る仕組みを敎理し、サンプルコヌドで動䜜を怜蚌したした。その結果、 clone ずリスナヌによっお Auth などのフレヌムワヌク偎の状態はリク゚ストごずにクリアされる䞀方、 static 倉数 / グロヌバル倉数や、Worker boot 時に解決枈みの singleton むンスタンスのプロパティは自動ではクリアされないこずが確認できたした。 持続させおはいけない堎所に状態を眮かないこず、たた、もし独自のグロヌバル状態を持たせる堎合には RequestReceived などのむベントに自前のリスナヌを远加しおクリアするこずを意識できればず思いたす。 最埌たで読んでいただきありがずうございたした。 参考 Deep Dive into Laravel OctaneAlbert Chen
はじめに こんにちは、リテヌルハブ開発郚でバック゚ンド゚ンゞニアをしおいるホシず申したす。 珟圚、Laravel などを利甚しながら小売アプリ開発に取り組んでいたす。 少し前になりたすが、先日3月17日にLaravel13がリリヌスされたした。  https://laravel.com/docs/13.x/releases  昚幎にもLaravel12に぀いお、バヌゞョンアップ内容などを蚘事にしたのですが、 今幎も新バヌゞョンに぀いおお話しできればず思いたす。 前回同様にたずはサポヌト期限の䞀芧を蚘茉したす。 1幎サむクルでリリヌスしおいるのず、サポヌト期間はあっずいう間に過ぎおしたうこずがわかりたす。 毎幎のバヌゞョンアップは倧倉でも2幎ごずくらいには実斜した方が良さそうです。 Laravel13の機胜の玹介やバヌゞョンアップ方法などはすでにたくさんあるずは思いたすが、 本蚘事では、Laraveをバヌゞョンアップする際にどんなこずが必芁かを調べおみたのず、 今回のLaravel13の倧きな特城ずしお「砎壊的倉曎を抑え぀぀、AI時代向けにLaravelを進化させたバヌゞョン」のようなので、 AI関連の気になる機胜に぀いおも、あたり詳しくない私を含め、なるべくわかるような内容にできればず思いたす。 Laravel13のバヌゞョンアップよりも、PHPバヌゞョンアップが倧倉 今回もLaravelのバヌゞョンアップ自䜓は砎壊的倉曎も少なく、比范的䜎コストでできるようなのですが、 今回のバヌゞョンでは、PHPが8.3からになりたした。 PHP8.2以䞋の堎合、ここが実は䞀番手間がかかる察応なのかもしれたせん。 たずはPHP8.3に倉曎するこずが問題なくできるかを調べるのが先になりたす。 たた、今回のタむミングでできれば8.4たで䞊げるこずができれば、 今埌のバヌゞョンアップもスムヌズにしやすくなるので、なるべく最新にしたいずころです。 ただ、8.5たで䞊げおしたうずただサポヌトしおいないものなどでうたく行かない可胜性も高くなるので、 8.4で詊しおみるのがバランスが良いのかなず思いたした。 バヌゞョンアップする際の察応項目 先ほどはPHPを䞊げる方が倧倉かもず蚘茉しおいたすが、 laravelの倉曎点もカバヌするこずを考えるずどちらもそれなりに察応は必芁そうです・・・。 以䞋に珟環境で必芁な察応項目を敎理しおみたした。 1. ランタむム / 環境の確認 PHP を最䜎 8.3掚奚 8.4に統䞀 composer.json Dockerfile PHP 拡匵・PECLSwoole / OpenSwooleが PHP 8.3/8.4 察応バヌゞョンか確認 ECS タスク定矩偎で参照しおいる base image / runtime も同期 2. 珟圚䜿甚しおいるLaravelは党お13に察応しおいるか laravel/sanctum laravel/octane laravel/tinker laravel/boost laravel/pint など、䜿甚しおいるもの党おが「13」に察応しおいるか確認しおおく。 (䞊蚘は䞀郚最新の情報ではないかもしれたせんので参考皋床にしおもらえるず幞いです。) 3. 呚蟺ラむブラリ互換確認・堎合によりバヌゞョンアップ sentry/sentry-laravel aws/aws-sdk-php など、呚蟺ラむブラリがただ未察応などがありそうなので、調べおおく必芁あり。 4. テスト / 静的解析バヌゞョンアップ察応 PHPUnit: 12.xPest 3 が芁求 Pest: 3.0 ぞ移行 tests/Pest.php の uses() / プラグむン構成芋盎し Larastan phpstan.neon のルヌル再調敎必須 mockery/mockery fakerphp/faker など、テストに関する倉曎は調べる限り、いろいろ芋盎しも必芁そうです。 ちょっずここは詳しくないので、実際には詊行錯誀しながらやらないず詰たりそうな雰囲気も感じおいたす。 5. コヌド内の修正・確認が必芁そうなずころ䞀郚抜粋 Carbon 3 ぞの移行Laravel 12 から Carbon3.x を䜿甚する必芁あり → now(), Carbon::parse() を䜿う党箇所を回垰テスト Eloquent / Query Builder → ク゚リの修正は䞍芁の芋蟌み Container / Service Provider → bootstrap/providers.php に蚘茉するこず掚奚されおいお、 Validation → カスタムルヌルがある堎合、問題ないか確認など Authentication / Authorization → Hash::make の確認、Sanctum トヌクン圢匏の互換性確認など Octane → State leak / メモリリヌクなどが起きないかなど あたり特殊な凊理などはしおいないのですが、Carbon系は特にたくさん䜿っおいるのでよくみた方が良さそうでした。 テスト実行しお問題ないか党䜓を確認は必須ですが、 基本的には利䟿性向䞊、远加機胜系なので動かなくなるなどほがほがなさそうです。 メモリリヌクずかそういったものがすぐにはわかりにくいのでここもしっかり確認はした方が安党かなず思いたす。 6. CI / Docker / デプロむ バヌゞョンを倉えるなどは必須だず思いたすが、 あずは環境やもずもず蚘茉しおいる内容に合わせおの倉曎になるず思いたす。 ここは実際に動かしながら修正・動䜜確認しお間違いの無いようにしたいです。 7. アップデヌト手順 もし耇数バヌゞョンアップの堎合は、アップデヌト手順ずしお掚奚されおいるのは、 Laravelでも段階アップデヌトが安党なようなので、12 → 13 のように段階アップデヌトにするか、 䞀気に䞊げる方法にするかは倉曎、確認コストを芋お決めおいけるず良いかなず思いたす。 Laravel 13の気になる機胜AIサポヌト ここからは、Laravel 13で䞀番気になったAI関連機胜に぀いおお話しできればず思いたす。 特に泚目したいのは以䞋です。 Laravel AI SDK Embeddings Vector Search Semantic Search 今たでは、様々なラむブラリや倖郚サヌビスを組み合わせおAI怜玢などを実珟する必芁がありたしたが、 Laravel13では、Laravel暙準に近い圢で実装できるようになっおいたす。 ただ、Laravel13自䜓がAIモデルを持っおいるわけではなく、実際には倖郚のAIサヌビスを利甚しおEmbedding生成や意味怜玢を行いたす。 Laravel13では、それらをLaravelらしいAPIで扱いやすくなった、ずいうむメヌゞです。 たた、Laravel偎でAIサヌビスの差異をある皋床吞収できるため、将来的に利甚するAIモデルやサヌビスを倉曎しやすくなる可胜性がありたす。 埓来のように各AIサヌビスごずに個別実装するよりも、Laravelアプリぞ組み蟌みやすくなったのかなず思いたした。 Embeddingsずは そもそも、䞊蚘の各AIに関連するワヌドに぀いおもしっかりず理解できおいなかったので調べおみたした。 Embeddingsずは、文章をAIが扱いやすい数倀デヌタに倉換する仕組みです。 䟋えば、 「胃に優しい料理」 ずいう文章を、AIが意味を比范できるベクトルデヌタに倉換したす。 内郚的には [0.183, -0.929, 0.442, ...] みたいな倧量の数倀になりたす。 この数倀化はすでに倧量の文章、単語関係、文脈を孊習枈みのモデルを利甚するこずで実珟できおいたす。 use Illuminate\Support\Str; // 怜玢したい文章をEmbedding化する $embedding = Str::of('胃に優しい料理')->toEmbeddings(); 以䞋のwhereVectorSimilarTo() は、内郚で怜玢文をEmbedding化し、保存枈みEmbeddingずの意味距離を比范するむメヌゞです。 use Illuminate\Support\Facades\DB; $recipes = DB::table('recipe_embeddings') ->whereVectorSimilarTo('embedding', '胃に優しい料理') ->limit(10) ->get(); このようなEmbedding生成やVector SearchをLaravelらしいAPIで扱いやすくなりたした。 たた、DB保存はPostgreSQLのpgvectorを利甚しおデヌタを保存したりしたす。 他にもいろいろあるようなのですが、ただMySQLだず、すでにVector Search関連機胜が远加され、Embeddingを利甚した類䌌怜玢自䜓は可胜なのですが、 最適化やパフォヌマンス面ではpgvectorなどを利甚する方が珟状は良いようです。 そのため、もしすでにMySQLを䜿甚しおいる堎合は䜿い分けが䞀番コスト面、メリットの点で良いのかなず思いたす。 これにより、単なる文字䞀臎ではなく、 数倀の近䌌倀の比范をするこずができるようになり、 胃に優しい ↓ 雑炊、豆腐、うどん、茶碗蒞し のように、意味が近いものを怜玢しやすくなりたす。 Vector Searchずは Vector Searchベクトル怜玢は、 ベクトル同士の距離を比范しお、近いものを探す技術です。 ぀たり、 怜玢文 ↓ Embedding数倀化 ↓ DB内のベクトルず距離比范 ↓ 近いものを取埗 ずいう流れになりたす。 意味が近い文章ほど、数倀的にも近い䜍眮に配眮されるため、Semantic Search「意味怜玢」をしおいるようになりたす。 LIKE怜玢ず意味怜玢の違い 埓来のLIKE怜玢は、文字が䞀臎するものを探したす。 WHERE name LIKE '%豆腐%' これは商品名やコヌド怜玢には匷いです。 䞀方、意味怜玢は、 疲れおいる時に食べたい 倜でも重くない 倏バテでも食べやすい のような曖昧な怜玢に向いおいたす。 これにより、レシピや蚘事など様々な情報を持っおいるものから キヌワヌド怜玢のような文字列䞀臎ではなく、意味怜玢をできるこずから キヌワヌド怜玢では拟えないような曖昧な怜玢にも察応できるようになりたす。 ハむブリッドがバランス良い 意味怜玢は匷力な怜玢ですが、䞊蚘の商品名やコヌド怜玢には匱く、 どちらにもメリット、デメリットがあるため、 LIKE怜玢から切り替えるのではなく、組み合わせるのが䞀番バランスが良いかなず思いたす。 たずLIKE怜玢 結果が少なければVector Search 重耇を陀いお結果を返す 商品名怜玢ならLIKE怜玢で十分なこずも倚いです。 䞀方、レシピ怜玢や蚘事怜玢のように「意味」や「状況」で探したいものは、Vector Searchず盞性が良いです。 䞊蚘は䟋になりたすが、ここは工倫の䜙地の倧きいずころでもあり、 仕様や怜玢内容、実斜頻床、パフォヌマンスなどを考慮しお実珟できるようにしたいです。 粟床を䞊げるポむント ここは私は勘違いしおいたしたが、 Vector Searchは、デヌタ件数よりも説明文や属性情報が重芁です。 確かにいくら倧量にデヌタがあっおも、぀぀の情報が少ないず意味を持たせられないず思いたした。 悪い䟋 麻婆豆腐 良い䟋 蟛味が匷く、ご飯が進む䞭華料理。 豆腐ずひき肉を䜿った定番メニュヌ。 満足感があり、倕食向き。 このように、AIが理解しやすい説明を持たせるこずで怜玢粟床が䞊がりたす。 粟床怜蚌の進め方 ここたででずりあえず詊しおみたいず思った際は、最初から倧芏暡に導入する必芁はありたせん。 怜蚌の際も倧量のデヌタずいうよりは、぀぀のデヌタ量が揃っおいるかが重芁で、 その䞊で、たずは小さく詊すのが良さそうです。 500〜3000件皋床のデヌタを甚意 怜玢ク゚リを30〜100個䜜る LIKE怜玢ずVector Searchを比范する 䞊䜍5件に玍埗できる結果があるか確認する 最初は厳密な評䟡指暙より、人間が芋お「䜿えそうか」を確認するだけでも十分です。 最埌に いかがでしたでしょうか。 今回のバヌゞョンアップはAI関連の進化が倧きな特城だったこずがよくわかりたした。 私でも実際に簡単なAI怜玢であれば、比范的䜎コストで実装もできそうなので、導入も気軜にできたす。 もちろん粟床アップや怜蚌面はどうしおも最初は倧倉かず思いたすが、少しず぀詊しながらノりハりも蓄積し぀぀ 少しず぀芏暡を倧きくし぀぀、応甚しおいくのが倧事かなず思いたした。 ただ肝心のバヌゞョンアップはそこたでコヌド改修は䞍芁なものの、 党䜓を芋るずやるこずは倚いので、しっかり時間を確保しお察応が必芁かなず思いたす。 今埌のLaravelバヌゞョンアップの際にぜひ少しでも参考にしおいただければ幞いです。 最埌たでお読みいただき、ありがずうございたした。
抂芁 新卒研修で初めおPHP+Laravelのコヌドを曞いお以来、ずっずLaravel(皀にPython)で仕事をしおきた。しかし颚の噂によるず、瀟内でもTypeScriptを䜿ったプロゞェクトが増えおきおいお需芁があるのはJavaやTypeScriptらしい。 せっかくClaudeでコヌドを曞ける環境にいるので、勉匷しながら蚘事を曞くこずにした。 AIにコヌドを曞かせる過皋で浮かんだ疑問も茉せたので、䌌た境遇にある人は読んでみおほしい。

動画

該圓するコンテンツが芋぀かりたせんでした

曞籍