結果がメモリに収まり、数秒で生成できる間は、データエクスポートは簡単に見えます。しかし、データ量や機密性、同時リクエスト数が増えると、レスポンスを直接返す方式ではリソースを使い果たしたり、実行時間制限を超えたりして、何が起きたのかユーザーに分からないままになることがあります。PHPでデータエクスポートを設計するとは、CSVの書き方だけでなく、生成方法、保護方法、状態の伝え方を決めることです。
リクエスト内でのエクスポート生成をやめるタイミング

同期エクスポートは、小規模で範囲が限定され、短時間で処理できるデータセットに適しています。アプリケーションがリクエストを検証し、データを取得して、同じレスポンスでファイルを返します。理解しやすく、後続のジョブやファイルを管理する必要もありませんが、レスポンス時間が全レコードの取得とシリアライズにかかるコストに左右されます。
処理時間にばらつきがある、または長い場合、データ量の増加が見込まれる場合、実行時間やメモリに重要な制限がある場合、あるいは同時エクスポートが対話的なリクエストと競合する場合は、非同期処理に移行するのが適切です。ユーザーが処理を開始し、後で戻ってくる必要がある場合にも、非同期処理が望ましいでしょう。普遍的な閾値はありません。実環境で処理時間、最大メモリ使用量、生成データ量、同時実行時の影響を測定してください。
明確な上限を設定でき、レスポンス時間も許容できるなら、同期方式を続けても問題ありません。両方を提供する方法もあります。小規模なデータセットは即時ダウンロード、大規模なリクエストはバックグラウンド生成にします。上限は明示し、処理開始前に伝えてください。完了間際に予期しないエラーとして現れるべきではありません。
用途に応じた形式と配信方法の選択
形式は利用者に応じて選びます。CSVは表計算ソフトや単純な連携に実用的で、JSONはネストした構造を必要とする利用者に適しています。複数ファイル、データ型、メタデータが必要なら、パッケージ化が適切な場合があります。サイズ、互換性、文字コード、区切り文字、日付、タイムゾーン、null値などの表現規則も考慮してください。
エクスポートの契約を定義します。列とその順序、適用されるフィルター、日付形式、文字の扱い、空の値の意味を明確にしてください。ファイルを表計算ソフトで開く場合は、ユーザーが指定した値が数式として解釈されるリスクも評価します。対策は形式と利用者によって異なります。動作を文書化せずにデータを暗黙に変更しないでください。
直接ダウンロードでは、クエリと形式が許せばPHPで内容を段階的に送信できます。大規模なジョブでは、一時ファイルを生成し、完成後に提供するほうが通常は制御しやすくなります。生成とダウンロードを分離すると進捗を表示でき、途中で切れるレスポンスを避けられますが、ストレージ、有効期限、権限の管理が必要です。
監視可能な非同期フローを設計する
一般的なフローは次のとおりです。
- リクエスト:フィルター、形式、対象範囲を検証し、ジョブIDを作成して、依頼者を記録する。
- 認可:フィルターや機密フィールドを含め、依頼されたデータセットをそのユーザーがエクスポートできることを確認する。
- 生成:バックグラウンドでジョブを実行し、エラーを記録して、公開されていない場所に書き込む。
- 利用可能化:書き込みが完了し、検証されてからファイルを準備完了にする。
- ダウンロードと期限切れ:アクセス権を再確認し、ファイルを提供して、定めたポリシーに従って削除する。
状態は分かりやすく、照会可能にします。たとえば、保留中、処理中、準備完了、失敗、期限切れです。内部情報を漏らさず、次の行動が分かるメッセージを含めてください。有用であれば、根拠のない精密なパーセンテージではなく、処理済みブロック数で進捗を記録します。画面では処理中のジョブと失敗したジョブを区別し、製品ポリシーに応じて再生成を依頼できるようにします。
認可されたユーザーの識別情報と対象範囲をジョブに紐付けてください。推測しにくいID自体を認可の代わりにしてはいけません。状態照会時やダウンロード時には、ジョブの所有者と現在有効な権限を確認します。ファイル生成中に権限が変更された場合の動作も定めてください。機密データでは、ダウンロード前の再検証や、権限が取り消されたジョブのキャンセルが必要になることがあります。
メモリを使い果たさずにブロック単位で処理する
シリアライズする前に結果全体を配列へ読み込むのは避けてください。順序を指定してレコードをブロック単位で取得し、各ブロックをストリームに書き込み、次へ進む前に参照を解放します。PHPではページネーションやイテレーターが役立ちますが、挙動はエンジンとドライバーによって異なります。一見イテレーターを使っていても、クライアント側で結果が蓄積される場合があります。想定データ量でメモリ使用量を検証してください。
大規模なデータセットでは、オフセットによるページネーションは処理コストが高くなることがあります。適切な場合は、安定した並び順と一意に特定できる継続用カラムを使ったキーページネーションを採用します。エクスポート中にデータが変化した場合の扱いを定めてください。一貫したスナップショットにはトランザクションや専用の戦略が必要なことがあり、ロックと処理時間のコストを評価する必要があります。変化するビューを許容する場合は、その仕様を文書化してください。
予測困難な名前と制限的な権限を設定した一時ファイルを、公開ルートの外に書き込みます。空き容量に加え、ファイルのオープン、書き込み、クローズ時のエラーを確認してください。書き込み失敗によって部分ファイルがダウンロード可能になってはいけません。まず一時名で生成し、書き込みの完了後、選択したストレージが提供する保証の範囲内で安全な操作を行って正式なファイルとして確定できます。
ダウンロード、有効期限、復旧を保護する
ダウンロードは、状態、認可、有効期限を確認する認証済みルートを経由させます。ユーザー入力のパラメーターからファイルパスを組み立てないでください。サーバーが管理するメタデータを通じてジョブIDを解決します。オブジェクトストレージを使用する場合は、一時アクセスを限定的に管理し、そのURLをフロー内の認可チェックの代わりにしないでください。
データの機密性、サイズ、ユーザーのニーズに応じて保持ポリシーを設定します。クリーンアップ処理では、期限切れファイルだけでなく孤立した一時ファイルも削除し、関連する状態を更新してください。追跡が必要な場合は、エクスポートを依頼またはダウンロードしたユーザーを記録します。ただし、エクスポートされたデータや秘密情報をログに保存しないでください。
障害が発生したら、運用上の原因を記録し、ジョブを整合性のある状態にしてください。再試行はコストの重複やファイルの重複生成につながることがあります。ジョブIDと冪等性のルールを使い、安全に生成を再開するか、新たに開始するかを判断します。部分ファイルへ無条件に追記してはいけません。削除または隔離し、完成した出力だけを公開してください。再試行回数に上限を設け、放置されたジョブの復旧方法も定めます。
チェックポイントからの再開には、単なるジョブの再試行以上の仕組みが必要です。最後に確定したブロックと、安定した継続キー(たとえば決定的な順序で最後に処理したキー)を、フィルターおよびジョブの識別情報とともに永続的に保存します。再起動時にはパラメーターが変わっていないことを検証し、次のキーから続行します。一貫性のない出力の公開を防ぐには、確定済みのブロックをジョブIDで識別できる一時パーツに書き込み、すべてのパーツが完成してから最終ファイルを組み立てます。形式やストレージがパーツの安全な確定と検証に対応していない場合、または一貫したデータビューを保証できない場合は、部分ファイルを破棄し、最初から再生成してください。誤った再開を試みるより、制御された再生成のほうが通常は簡単で安全です。
本番環境に向けたテストとチェックリスト

内容とライフサイクルの両方をテストしてください。フィルター、権限、エクスポート対象のフィールドが正しいこと、他のユーザーが自分以外のジョブを照会またはダウンロードできないこと、有効期限後にアクセスできないことを確認します。空のデータセット、特殊文字、大きな値、機密データを含むレコードも対象にします。可能であれば、実際の利用者で形式を検証してください。
データベースエラー、ディスク容量不足、書き込み中断、プロセス消失、リクエストの重複をシミュレートします。不完全なファイルが公開されないこと、再試行によって不要な重複処理が起きないこと、クリーンアップによって残骸が削除されることを確認してください。チェックポイントを実装する場合は、各ブロック境界での再起動、互換性のないパラメーターの検出、最終ファイルの組み立てをテストします。代表的な同時実行条件でメモリ、処理時間、負荷を測定し、ジョブキュー、未処理のストレージ、処理中エクスポートの経過時間も監視してください。
- サイズ、処理時間、同時実行数の上限を定める。
- フィルター、フィールド、状態照会、ダウンロードを認可する。
- ブロック単位で処理・書き込みを行い、実際のメモリ使用量を測定する。
- 完成したファイルだけを公開し、保存場所を保護する。
- 状態、復旧可能なエラー、有効期限を通知する。
- 冪等な再試行と自動クリーンアップを計画する。
- ブロックを確定し、一貫したパラメーターとデータで続行できる場合に限り、チェックポイントを使用する。
- 権限、部分的な障害、整合性、負荷をテストする。
重要なのは、単に同期か非同期かを選ぶことではありません。待ち時間、整合性、プライバシー、復旧について、製品がどのような保証を提供できるかを決めることです。その保証を明確にすれば、現在のデータ量に見合ったPHPの実装を選べます。また、エクスポートがアプリケーションの他の部分を劣化させる前に、拡張に向けた上限と兆候を設定できます。



