Laravel 8でセッションドライバーをDynamoDBに設定

# 前提（Laravelのセッション保存先）
Laravel 8.xでは.envファイルの設定でセッションの保存先を指定することができます。
+ file - サーバ内にテキストファイルで保存（storage/framework/sessions配下）※デフォルト
+ cookie - ユーザのアクセス元ブラウザのクッキーで保存
+ database - DBに保存
+ memcached／redis - キャッシュDBに保存
+ dynamodb - AWS DynamoDBに保存
+ array - （永続しない）

「file」がデフォルトで、サーバ内に保存される形になっています。
EC2を冗長化する際などには、保存先のディレクトリをEFSやディスク共有、lsyncdなどでファイル同期をかけないと、片系インスタンスのセッションをもう一方のインスタンスで処理するときにデータが使えない状態になります。
実稼働環境ではDBやキャッシュDBを利用するケースが多いかもしれません。
簡単なアプリケーションであれば、手っ取り早くもっとも簡単に設定できる「cookie」を選択することもあるかもしれません。

#### 参考：Laravel ドキュメント
https://readouble.com/laravel/8.x/ja/session.html


# 経緯（消えたセッションの謎）
ある日、ローカル環境ではバリデーションエラーが表示されるのに、同じアクションをAWS上の環境で行うと、エラーメッセージや入力値が一切表示されない事象が発生しました。

サーバログにもエラーが吐かれないため、環境依存ではないかと疑ってしまっていましたが、最終的に判明した、原因はこちら。
+ Laravelのセッションドライバーとして「cookie」を指定されていた
+ 当該アクションで生成されるセッションデータのcookieが、4KB以上になってしまった
+ 多くのブラウザではcookieが4KB程度までの制約があり、セッションデータが保存できていなかった

実際に発行されるcookieは、ブラウザの開発者ツールなどでレスポンスヘッダを見れば取得でき、バイト数をカウントすればすぐに判明します。

#### 参考：主なブラウザのCookie制約(2021/07現在)
https://docs.devexpress.com/AspNet/11912/common-concepts/cookies-support

# 対応（DynamoDB設定してみる）
今後の弾力性や、既に他のAWSのサービスを利用する設定が入った状況ということも考慮し、今回はDynamoDBをセッションドライバーとして採用することにしました。

### 1. IAM設定
今回はLaravelの.envファイルでAWS_ACCESS_KEY_IDを指定済みであるIAMユーザは既存のものを使いました。
DynamoDBにアクセスできるポリシーを、テーブル単位でアクセスを制限して新たに作成し、対象ユーザにアタッチします。

#### 参考：IAMのポリシー設定例（テーブル名の指定以外はコピペで可）
https://docs.aws.amazon.com/ja_jp/IAM/latest/UserGuide/reference_policies_examples_dynamodb_specific-table.html

### 2. DynamoDBテーブル作成
AWS CLIが実行できる環境から、コマンドで作成します。
+ テーブルの主キーとなるAttributeNameは「key」としてハッシュが登録できるように指定
+ キャパシティの設定は難しいので、従量課金のオンデマンド（PAY_PER_REQUEST）で指定
+ ポリシー「AmazonDynamoDBFullAccess」などがついたCreateTableができるユーザプロファイルでコマンドを実行

```
aws dynamodb create-table --table-name=【**テーブル名**】 --attribute-definitions AttributeName=key,AttributeType=S --key-schema AttributeName=key,KeyType=HASH --billing-mode PAY_PER_REQUEST
```

※テーブルのAttributeNameは他のものでも指定できますが、Laravelのデフォルトでは「key」でアクセスされます。（変える方法はある模様）

### 3. ENVファイルの設定

```
AWS_ACCESS_KEY_ID=【***********】　（既に指定済みだったIAMユーザを利用）
AWS_SECRET_ACCESS_KEY=【***********************************】
AWS_DEFAULT_REGION=【***********】　（東京ならap-northeast-1）
SESSION_DRIVER=dynamodb　（←変更）
DYNAMODB_CACHE_TABLE=【**テーブル名**】
```

※「400 Bad Request：The provided key element does not match the schema」が出るときは、AttributeNameをデフォルト以外で指定していて一致しないことが疑われます。
※「400 Bad Request：Requested resource not found」が出る場合は、きっとエンドポイントかテーブル名の誤りが疑われます。
※今回は「DYNAMODB_ENDPOINT」を指定せず、「AWS_DEFAULT_REGION」に「https」で接続させています。
　（ 指定する場合は `https://dynamodb.ap-northeast-1.amazonaws.com` など ）
※その他の項目指定は、 `config/cache.php`や`config/session.php`の設定項目が参考になります。

当然ではあるものの…
・サーバ上でconfigをキャッシュさせている場合は `php artisan config:cache` でリフレッシュが必要です。
・設定切替時にセッションは切れちゃうので、稼働環境によっては考慮して反映が必要です。

### 4. VPCエンドポイントの設定
このままの設定だとDynamoDBには、EC2インスタンスからインターネットゲートウェイを経由してネットワークの外に一旦出てからアクセスされます。
これだと帯域も消費してしまうので、EC2を立てているVPCには、DynamoDBのVPCエンドポイントを設定してAWS内部アクセスで処理できるように設定しておきます。
マニュアルではCLIで書かれていますが、`AWSコンソール ＞ VPC ＞ エンドポイント` から、右上の「エンドポイントを作成」ボタンでポチポチ選択する方が手っ取り早いです。

#### 参考：VPCエンドポイントの仕組み
https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/vpc-endpoints-dynamodb.html

※サービス単位/VPC単位でVPCエンドポイントを設定すると、VPC内のすべてのDynamoDBへのアクセスがインターネットを経由しなくなるので、影響範囲には注意が必要です。


反映できれば設定完了です。
実際のアクセス時の表示速度は、それほど違和感ありませんでした。


# 懸念（従量課金が怖い）

DynamoDBをオンデマンドキャパシティモードで指定しているため、どれくらい従量の料金がかかってくるのかが心配。
課金も単純なリクエスト数ではなくて、消費ユニットでのカウントなので計算が面倒です。

#### 東京リージョン／Standardの料金（主な部分）
+ 書き込みリクエスト：100万単位あたり 1.4269USD
  + 通常書込 (1KBまで)：1単位
  + トランザクション書込： 2単位
+ 読み出しリクエスト：100万単位あたり 0.285USD
  + 強力な整合性のある読込 (4KBまで)： 1単位
  + トランザクション読込： 2単位
  + 結果整合性のある読込： 0.5単位
+ ストレージ：25GBまで無料（無料枠を超えることはなさそう）
+ バックアップ：毎月 0.228USD/GB（セッションなので今回はバックアップ不要と判断）

東京リージョンだと結構お値段もかかるものの、セッションとなるとレイテンシの都合遠くのものを使うのも微妙です。
大量のアクセスがある場合はプロビジョニング済みキャパシティーモードと天秤にかける必要がありそうです。

テーブルを見ていると、ALBからのものかヘルスチェックのようなアクセスでもデータが生成されているのが見えるので、結構無駄にカウントされてそうですね。

RDSを使っている場合は「database」にしておいた方が、クエリは増えてしまうものの、安いといえばお安いでしょうか…
ElastiCache for Redisなどをつかって「redis」にすると、t3.microの稼働時間課金で月2500円ぐらいかかるので、アクセスが少ないサイトではDynamoDBの方が安いのではないかと考えています。

#### 参考：DynamoDBの料金表
https://aws.amazon.com/jp/dynamodb/pricing/