Octa Roboticsインターン体験記 Part1:ログ永続化サーバー開発編
ログ永続化サーバー開発編 ~設計思想と試行錯誤の記録~
1. はじめに:ロボットログ、その重要性とクラウド永続化への挑戦
私がOcta Robotics様のインターン(バックエンド)として活動させていただいたレポートを書かせて頂きます。IoTシステム、特にログデータと通信の仕組みについて記します。システムは、不具合の原因究明や性能改善のための貴重な情報源です。しかし、これらのデータをいかに効率的に収集し、保存し、活用するかという課題があります。
この記事では、要件定義から設計、実装、そしてDocker化に至るまでの開発プロセスと、その中で直面した技術的課題、試行錯誤の記録をお伝えします。
2. プロトタイピング:Tornadoと非同期処理による基盤構築
技術選定:なぜPython Tornadoなのか?
- 非同期I/Oによる高効率なリクエスト処理:ログアップロードのような待ち時間の長い処理には非同期処理が効果的です
- 軽量なWebフレームワーク:必要最小限の機能を備えており、オーバーヘッドが少ない
Tornadoはシングルスレッドで動作しながら多数の接続を非同期に処理できるため、I/O待ちの多いログ処理に最適です。内部的にはepoll
/kqueue
などのイベント駆動型I/Oを活用し、ブロッキングなI/O待ちをしないことで効率的にリクエストを捌けます。
初期実装:最初のコードの設計と構造
最初に実装した際の主要クラスと設計意図は以下の通りです:
-
LogCache
: インメモリでログファイルの情報をキャッシュする -
LogStorageManager
: ファイルシステムへのログの読み書きを管理 -
LogInfoHandler
: GETリクエスト(最終ログ情報取得)を処理 -
LogUploadHandler
: POSTリクエスト(ログアップロード)を処理
特に重要な設計ポイントは以下の2点でした:
-
インメモリキャッシュの導入:
頻繁にアクセスされるログ情報をメモリに保持し、ディスクI/Oを削減。これにより応答速度を向上させつつ、サーバー負荷を軽減します。 -
非同期処理によるリクエスト分離:
アップロード処理を非同期キュー(asyncio.PriorityQueue
)に格納し、Web APIリクエスト処理と実際のアップロード処理を分離。これにより、重いアップロード処理中でもAPIリクエストの応答性が低下しないようにしました。
この段階では、キャッシュの有効期限管理やログローテーションなど、未実装の課題もありましたが、基本的な機能は確立できました。Tornadoの非同期ハンドラを使用することで、処理の途中でブロックすることなくリクエストを受け付けられるようになっています。
内部的な処理の流れは以下のとおりです:
- クライアントからPOSTリクエストを受信
- リクエストボディとパラメータを検証
- 一時ファイルとしてローカルに保存
- 応答をすぐにクライアントに返す(非同期処理のおかげで可能)
- 背後でアップロードキューに追加し、非同期ワーカーが処理
この設計により、クライアントは長時間待たされることなく、サーバーは効率的にアップロード処理を行えるようになりました。
動作確認
実装後、curl
コマンドを使って基本的なAPI動作テストを行いました。これにより、APIエンドポイントの疎通確認ができ、実際のリクエストフローを検証できました。重要なのは、非同期処理が適切に機能しているかを確認するために、複数のリクエストを同時に送信し、応答時間に大きな遅延がないことを確認しました。
この段階で、ローカル環境での挙動は良好でしたが、実際のネットワーク環境でのパフォーマンスはまだ未検証でした。次のステップでコンテナ化を進めることで、より実環境に近い条件でのテストが可能になります。
学びポイント
この段階で学んだのは:
- 非同期プログラミングの基礎(イベントループ、コールバック、タスク)
- Tornadoの基本的な使い方(ハンドラ、アプリケーション構成、ルーティング)
- クラス設計による関心の分離(責務の明確化で保守性向上)
特に、非同期プログラミングの概念は、これまでの同期的なコーディングスタイルとは大きく異なり、イベント駆動型の考え方を身につける良い機会となりました。
3. Docker化:開発環境の整備と運用効率化
Docker化の動機
次のステップとして、開発環境をDocker化しました。その理由は:
- 環境差異の吸収:開発環境と本番環境の差異を最小化し、「自分の環境では動くのに…」という問題を防止
- 依存関係の管理:Pythonパッケージや外部ライブラリの依存関係を簡単に管理
- デプロイの容易性:コンテナ化することで、どの環境でも同じ手順でデプロイ可能
- マイクロサービス化への布石:MQTTブローカーなどの分離を視野に入れる
Dockerfileとdocker-compose.yml
上長から共有された構成ファイルを参考に、ログサーバー用にカスタマイズしました。特に重要だったのは、複数のサービスを連携させるためのdocker-compose.yml
の設定です。
主要なコンポーネントは以下の通りです:
- loggerサービス: 開発したPythonアプリケーション
- mqtt-broker: Eclipse Mosquitto(将来の機能拡張用)
- nginx: リバースプロキシとして前段に配置
この構成の工夫ポイントは以下の通りです:
-
ボリューム設定 (
volumes
):- ホストとコンテナ間でコード・設定・データを共有することで、開発効率を上げつつ、コンテナ再起動時もデータを保持
-
/app/logs
ディレクトリを永続化し、サーバー再起動時もログデータが失われないよう配慮
-
依存関係管理 (
depends_on
+healthcheck
):- サービス間の起動順序を制御し、依存サービスが完全に起動するまで待機
- 特にMQTTブローカーが正常に起動しているか確認するためのヘルスチェック実装
- 単純な
depends_on
だけでなくヘルスチェックを組み合わせることで、サービスが「稼働中」ではなく「準備完了」の状態を確認
-
ネットワーク設定 (
networks
):-
robot-network
ブリッジを作成し、コンテナ間の通信を分離 - セキュリティとパフォーマンスの観点から、必要な通信だけを許可
-
これらの設定により、開発者は複雑なインフラを意識することなく、アプリケーションの開発に集中できるようになりました。
学びポイント
この段階で学んだのは:
- Dockerの基本概念(イメージ、コンテナ、ボリューム、ネットワーク)
- Composeによる複数コンテナの連携(サービス間の依存関係、起動順序制御)
- コンテナオーケストレーションの初歩(ヘルスチェック、リスタートポリシー)
- シェルスクリプトによる運用効率化(繰り返し作業の自動化)
特に、Docker Composeによるマルチコンテナ構成の管理は、マイクロサービスアーキテクチャの基本を学ぶ良い機会となりました。サービス間の通信や依存関係を適切に設計することの重要性を実感しました。
4. クラウドストレージ連携:認証、アップロード、そしてAPIの壁
クラウドAPI認証
クラウドストレージAPIを使うためには、OAuth 2.0認証を設定する必要があります。この認証フローの理解と実装は、外部APIと連携する上で重要なスキルです。
認証プロセスは以下のステップで行いました:
- クラウドサービスのAPIコンソールでプロジェクト作成
- 必要なAPIを有効化
- OAuth 2.0クライアントIDを作成し、認証情報を保存
- 初期認証スクリプトを実行してトークンを取得
この認証フローの面白い点は、最初の認証時にはブラウザを開いてユーザー承認が必要ですが、一度トークンを取得すれば、リフレッシュトークンを使ってサーバーが自動的に認証を更新できることです。これにより、長期稼働するサービスでも認証切れを気にせず運用できます。
OAuth 2.0の仕組みを理解することで、「なぜトークンが有効期限を持つのか」「なぜリフレッシュトークンが必要なのか」といったセキュリティモデルの設計思想も学ぶことができました。
アップロードロジックの実装
本番用のサーバーコードでは、UploadManager
クラスを実装し、効率的なアップロード処理を実現しました。
バッファリング戦略
「60秒 or 4KB」というバッファリング戦略を採用した理由は:
-
APIコール回数削減:クラウドサービスのAPIには一日あたりの呼び出し回数制限(クォータ)があります。小さなファイルを頻繁にアップロードすると、このクォータにすぐに達してしまいます。バッファリングによりAPI呼び出し回数を削減できます。
-
リアルタイム性とのバランス:60秒という時間制限は、データのリアルタイム性と効率のバランスから決定しました。長すぎると最新データが反映されず、短すぎるとAPI呼び出しが頻発します。
-
ネットワーク効率:4KBというサイズ制限は、TCPパケットサイズとの関係から決めました。小さすぎるとTCPヘッダのオーバーヘッドが相対的に大きくなり非効率です。大きすぎるとアップロード失敗時のリスクが高まります。
この戦略を実装するにあたり、以下のようなデータ構造を設計しました:
buffer = {
"filename1": (bytearray(), timestamp),
"filename2": (bytearray(), timestamp),
...
}
バッファは定期的にチェックされ、サイズ制限または時間制限を超えたエントリはアップロードキューに移されます。この方式により、メモリ使用量を抑えつつ効率的な処理が可能になりました。
非同期アップロード
アップロード処理をメインのリクエスト処理から分離するために、非同期ワーカーを実装しました。これにより、Webリクエスト処理をブロックすることなく、バックグラウンドでアップロードが行われます。
内部的な仕組みは以下の通りです:
-
asyncio.PriorityQueue
を使用して優先度付きのアップロードキューを実装 - 複数のワーカータスクがキューからアップロード作業を取得
- アップロード中もWebサーバーは新しいリクエストを受け付け続ける
これは、イベントループベースの非同期プログラミングモデルを活用した典型的なプロデューサー・コンシューマーパターンです。Tornadoの非同期機能とPythonのasyncioを組み合わせることで、効率的かつ安定したシステムを構築できました。
再開可能アップロード
大きなログファイルやネットワーク不安定時の対策として、再開可能アップロード機能も実装しました。APIクライアントを設定する際にresumable=True
を指定することで、アップロード中に接続が切れても、続きから再開できるようになっています。
この仕組みは、クラウドストレージ側でチャンク単位でのアップロード管理が行われており、クライアントが一定のチャンクサイズでファイルを分割して送信します。各チャンクには識別子が付与され、サーバー側で再構成されます。アップロードが中断した場合、次回接続時にどのチャンクまで成功したかを確認し、そこから再開できます。
この機能のおかげで、不安定なネットワーク環境でも確実にデータをアップロードできるようになりました。
一時ファイルへの書き出し
アップロード前にローカルファイルに書き出す理由は:
-
メモリ効率:大きなファイルをメモリに保持し続けると、サーバーのメモリ使用量が増大します。一時ファイルに書き出すことで、メモリ効率が向上します。
-
耐障害性:サーバークラッシュ時でもファイルが残り、復旧後に処理可能になります。メモリ内のデータは障害時に消失します。
-
デバッグ容易性:問題発生時に一時ファイルを確認することで、データの内容や形式を検証できます。
一時ファイル名にはタイムスタンプを含めることで、重複を避けつつ、処理順序も把握できるようにしました。さらに、正常にアップロードされた一時ファイルは自動的に削除され、ディスク容量の圧迫を防止しています。
APIクォータとの戦い
クラウドAPIには利用上限(クォータ)があり、これを超えるとサービスが一時的に利用できなくなります。上長からの指摘「APIクォータを考慮せよ」を受け、以下の対策を講じました:
- バッファリング:小さなログを結合してAPIコール回数を削減
-
リトライ制限:
max_retries
でリトライ回数を制限し、無限ループを防止 - 指数バックオフ:リトライ間隔を徐々に長くし、サーバー負荷を軽減
これらの対策により、APIクォータを効率的に使用しつつ、必要なデータを確実にアップロードできるようになりました。
特に指数バックオフは、サーバー側の一時的な過負荷状態からの回復をスムーズにする効果があります。リトライごとに待機時間を増やすことで、サーバーに「呼吸の余裕」を与え、回復を促進します。
リトライ処理:指数バックオフの実装
単純なリトライではなく指数バックオフを実装した理由は:
-
サーバー負荷軽減:一時的な障害時に連続リクエストによるサーバー負荷を回避できます。特に大量のクライアントが同時に再試行する場合、この効果は顕著です。
-
連続エラー回避:短時間での再試行は同じエラーを繰り返す可能性が高いです。例えば、APIクォータ超過や一時的なネットワーク障害は、数秒待っても解消されないことが多いため、時間をおくことが効果的です。
-
回復の可能性向上:時間をおくことでネットワーク状況やサーバー状態の改善を期待できます。特にクラウドサービスは負荷分散を動的に行うため、時間経過とともに状況が改善することが多いです。
実装では、リトライ回数に比例して待機時間を増やす方式を採用しました。具体的には:
# retry_countは0から始まるリトライ回数
wait_time = (2 ** retry_count) * base_delay # 指数関数的増加
await asyncio.sleep(wait_time)
このアルゴリズムにより、1回目のリトライは短い間隔で行われますが、問題が続くにつれて間隔が徐々に長くなります。これは、「問題が長引くほど、解決に時間がかかる可能性が高い」という経験則に基づいています。
学びポイント
この段階で学んだのは:
- 外部API連携の実装方法(認証フロー、エラーハンドリング、リトライ戦略)
- 非同期処理による効率化(イベントループ、タスク管理、キュー処理)
- API利用制限への対策(バッファリング、バッチ処理、レート制限)
- OAuth 2.0の仕組みと設計思想
特に、実運用を想定したエラーハンドリングの重要性を実感しました。理想的な環境ではなく、制約のある実環境でいかに安定したシステムを構築するかという視点は、今後のエンジニアリングに欠かせない要素です。
5. 高度なアップロード戦略の探求と反省
上長のさらなる要求
ログアップロードの基本的な機能が実装された後、上長から「効率的なアルゴリズムを検討し、資料で説明せよ」という要求がありました。これを受けて、より高度なバッチングアルゴリズムの検討に入りました。
動的バッチングアルゴリズムの提案
提案したアルゴリズムの核心は、「単純な時間やサイズだけでなく、ファイルの重要度や緊急度も考慮したアップロード優先順位決定」です。具体的には以下の3つの要素を組み合わせました:
1. 価値計算
ファイルの価値を計算する関数では、以下の要素を考慮しました:
- 経過時間: 古いファイルほど価値が減少する(指数関数的減衰)
- ファイルタイプ: エラーログは警告ログより重要、警告ログは情報ログより重要
- サイズ効率: 小さいファイルは結合したほうが効率的なため、サイズに応じた調整
この関数の設計思想は、「現在のシステム状態を最もよく反映している新しいログほど価値が高い」「問題検出に直結するエラーログは優先度が高い」「効率的なアップロードのために小さなファイルは結合すべき」という3つの原則に基づいています。
2. アップロード時間推定
単純に「サイズ÷帯域」で計算するのではなく、以下の要素を考慮しました:
- 過去のアップロード実績: 実際のアップロード時間から学習する移動平均
- オーバーヘッド係数: 小さいファイルほどAPIコールの固定オーバーヘッドの割合が大きい
- 現在のネットワーク状況: 直近の転送速度から帯域を推定
これにより、理論値ではなく実際の環境に適応した時間推定が可能になります。特にクラウドサービスのレスポンス時間は変動するため、静的な計算式よりもこのような適応型の推定が効果的です。
3. 優先度計算
最終的な優先度は、価値、緊急度、リトライ状況を組み合わせて決定します:
- 価値と緊急度のバランス: 高価値×高緊急度のファイルを優先
- リトライペナルティ: リトライ回数が多いファイルは優先度を下げる
- システム状態への対応: サーバー負荷に応じた動的調整
この優先度計算により、「重要かつ緊急」なファイルを優先しつつ、特定のファイルがリトライで詰まることを防ぎます。
大きな反省点
このアルゴリズムを考案した後、大きな反省点がありました:
-
実装コストと複雑性: このアルゴリズムは本当に必要だったのか? シンプルな時間/サイズベースのバッファリングで十分ではなかったか?
-
時間配分の誤り: アルゴリズムの検討に時間をかけすぎ、実装が遅れてしまいました。計画段階での時間見積もりが甘かったといえます。
-
要求の解釈ミス: 上長の真意は「効率化のアイデアを出してほしい」というレベルだった可能性が高く、詳細な数式やコードまで求めていなかったかもしれません。
この経験から学んだのは、複雑なアルゴリズムを考案する前に、「本当にその複雑さが必要か」を検討することの重要性です。エンジニアとして技術的に優れたソリューションを追求する気持ちは大切ですが、時には「十分に良い」シンプルな解決策を選ぶことも重要です。
教訓:MVPの考え方
この経験から、MVP(Minimum Viable Product)の考え方の重要性を学びました。まずは動くものを作り、必要に応じて改良するアプローチが効果的です。要求分析とコミュニケーションを深めることで、このような過剰な設計を避けることができたでしょう。
特に重要なのは、「完璧を目指すよりも、まずは動くものを」という考え方です。実際のユーザーフィードバックを得てから改良するほうが、リソースを効率的に使用できます。
学びポイント
この段階で学んだのは:
- アルゴリズム設計の面白さと難しさ(理論と実践のバランス)
- トレードオフの考慮方法(複雑性vs効果、開発時間vs機能)
- 要求定義の重要性(曖昧な要求の解釈と明確化)
- 自己評価と改善の必要性(反省から学ぶプロセス)
特に「技術的に正しい」ことと「ビジネス的に正しい」ことのバランスを取ることの重要性を実感しました。エンジニアとしての成長には、この両面からの視点が欠かせません。
6. サーバーダウン対策と代替ストレージの模索
課題認識
ログアップロード中のサーバーダウン時にデータがロストするリスクがあることに気づきました。一時的なネットワーク障害や再起動は実運用では避けられないため、これはシステムとして解決すべき重要な課題です。
内部的には、以下のようなシナリオに対応する必要がありました:
- アップロード中のサーバーダウン
- クラウドAPI側の一時的な障害
- ネットワーク切断
- ディスク容量不足による書き込み失敗
これらの問題は単なる「エラー処理」では解決できず、システム設計レベルでの対策が必要です。
検討した対策
キューイングシステム (RabbitMQ, SQS)
タスクの永続化のために、メッセージキューイングシステムの導入を検討しました:
- 基本原理: アップロードタスクをキューに永続化し、サーバー再起動後も処理を継続
- 利点: 耐障害性向上、スケーラビリティ向上、負荷分散
- 欠点: システム複雑化、新たな依存関係追加、運用コスト増
RabbitMQは、パブリッシャー・サブスクライバーモデルを採用したオープンソースのメッセージブローカーです。メッセージを永続化し、ACK(確認応答)ベースの配信保証を提供します。これにより、サーバーダウン後も未処理のメッセージを再度処理できます。
一方、AWS SQSは、クラウドベースのフルマネージドキューイングサービスで、スケーラビリティが高く、運用負荷が低いという特徴があります。分散システムの信頼性を向上させる優れた選択肢ですが、AWS依存になるという制約もあります。
実装を検討する中で、メッセージブローカーの仕組みを深く理解することができました。特に「At-least-once delivery」(少なくとも1回の配信)と「Exactly-once processing」(正確に1回の処理)の違いや、分散システムにおける一貫性の課題は非常に興味深いトピックでした。
状態管理DBの強化
アップロード状態の詳細な記録と管理のために:
- 基本原理: SQLiteなどの軽量DBを使い、アップロード状態を詳細に記録
- 実装方法: ファイルごとに「未処理」「処理中」「完了」「エラー」などの状態を管理
- 利点: 細粒度の状態管理、リジューム処理の容易化、整合性確保
この設計では、各ファイルに対して以下のような情報を記録します:
{
"file_id": "unique_identifier",
"file_path": "/path/to/file.log",
"upload_status": "IN_PROGRESS", // PENDING, IN_PROGRESS, COMPLETED, ERROR
"chunks_total": 10,
"chunks_uploaded": 3,
"retry_count": 2,
"last_attempt": "2023-07-15T14:25:32Z",
"error_message": null
}
これにより、サーバー再起動後もどこまで処理が進んでいたかを正確に把握でき、中断した処理を再開できます。特に大きなファイルのアップロードでは、チャンク単位での進捗管理が効果的です。
データベース設計においては、トランザクション処理を適切に実装することで、状態の整合性を確保します。これにより、例えばサーバーダウン直前に「処理中」に更新した直後でも、再起動後に適切に処理を再開できます。
サーバー冗長化
単一障害点を排除するために:
- 構成: Dockerコンテナを複数起動し、ロードバランサーで分散
- 同期方法: 共有ボリュームまたはデータベースを使用して状態を同期
- フェイルオーバー: ヘルスチェックによる障害検知と自動切り替え
この設計のポイントは、各サーバーインスタンスを「ステートレス」に近づけることです。状態情報(どのファイルがアップロード中か、どこまで進んだかなど)を共有ストレージやデータベースに保存することで、どのサーバーからでも処理を引き継げるようになります。
冗長化の設計で特に注意したのは、「Split Brain」問題(複数のサーバーが同時に同じファイルをアップロードしようとする状態)の回避です。これには、分散ロック機構やリーダー選出アルゴリズムなどが必要になりますが、今回のプロジェクトの規模では過剰な複雑さになると判断しました。
代替ストレージ:分散型クラウドストレージ
主要クラウドストレージの代替/補完ストレージとして分散型クラウドストレージを検討しました。特に注目したのは、データの冗長性と耐障害性に優れた分散型アーキテクチャです。
分散型ストレージの仕組みを調査する中で、以下のような興味深い技術に触れることができました:
- コンテンツアドレス可能ストレージ: データのハッシュ値をアドレスとして使用する方式
- イレイジャーコーディング: データを複数のフラグメントに分割し、一部が失われても再構築可能にする技術
- 分散ハッシュテーブル(DHT): ピア間でのデータ検索を効率化する仕組み
分散型ストレージと従来のクラウドストレージを比較検討した結果、以下のような知見を得ました:
評価観点 | 従来のクラウドストレージ | 分散型ストレージ |
---|---|---|
可用性 | 単一事業者に依存 | 分散ノードで高可用性 |
コスト | 帯域やアクセス頻度で変動 | 通常固定料金、安価 |
速度 | 一般的に高速、地域依存 | 地域分散のため変動あり |
セキュリティ | 中央管理、強固な暗号化 | エンドツーエンド暗号化 |
運用複雑性 | シンプル、ツール充実 | 複雑、ツール発展途上 |
プライマリ + セカンダリ構成の検討:
- メリット: 冗長性向上、ストレージプロバイダロックインの回避、障害時の継続運用
- デメリット: 実装の複雑化、管理コスト増、二重のAPI呼び出しによるパフォーマンス低下
最終的に、プロジェクトの納期と要件を考慮し、代替ストレージの本格導入は見送りましたが、将来的な拡張オプションとして検討材料を残しました。
学びポイント
この検討プロセスで学んだのは:
- システムの可用性・耐障害性を高めるための設計パターン(冗長化、状態管理、キューイング)
- 分散型ストレージの特性と利点(耐障害性、コスト効率、プライバシー保護)
- システムアーキテクチャ比較検討能力(トレードオフの分析、要件との整合性評価)
- 分散システムの理論(CAP定理、結果整合性、分散ロック)
特に、理論的な「ベストプラクティス」と実際のプロジェクト制約(時間、予算、技術スタック)のバランスを取ることの重要性を実感しました。完璧なシステムを目指すよりも、リスクを認識した上で適切な妥協点を見つけることが実務では重要です。
7. まとめと今後の課題
開発成果
このプロジェクトでは以下を実現しました:
- Python Tornadoベースの非同期ログサーバー
- Docker化による環境統一と運用効率化
- クラウドストレージ連携機能(バッファリング、リトライ機能を実装)
- アップロード管理とエラーハンドリング
特に重要だったのは、単なる機能実装だけでなく、実運用を見据えた設計を行ったことです。バッファリング戦略、エラーリトライ、非同期処理などは、理想的な環境ではなく、現実の不安定な環境でも動作することを想定しています。
開発プロセスで得たもの
インターンを通じて以下のスキルを習得・向上させました:
- 非同期プログラミングの深い理解(イベントループ、コールバック、タスク管理)
- 外部API連携の実装方法(認証、エラー処理、リトライ戦略)
- Dockerによるコンテナ化と運用(マルチコンテナ構成、ネットワーキング)
- アルゴリズム設計と実装のバランス感覚(理論と実践の融合)
- 要件定義とコミュニケーションの重要性(曖昧さの解消、期待値の明確化)
この経験は、単なる技術習得にとどまらず、実際のプロジェクトにおける意思決定プロセスや、問題解決アプローチについても学ぶ貴重な機会となりました。
残された課題
今後取り組むべき課題としては:
- より高度なサーバーダウン対策の実装(分散キューイング、状態管理DB)
- 代替ストレージの本格導入検討(拡張性と冗長性の向上)
- パフォーマンスチューニングとスケーラビリティ向上(負荷テスト、ボトルネック特定)
- セキュリティ強化(認証・認可、暗号化、脆弱性対策)
これらの課題は、本番環境への導入を見据えたときに重要になるものです。特にセキュリティ面では、実証実験段階では簡略化していた部分も、本格運用に向けては強化が必要です。
次回予告
次回の記事「ネットワーク検証編」では、このサーバーに対してネットワーク経由での検証を行った記録を詳述します。VPN接続の問題、ローカルネットワークシミュレーション、クラウド環境での負荷テストなど、多くの「失敗と学び」をお伝えする予定です。お楽しみに!
Discussion