FHIR 検索を使用したページ分けと検索の合計の実装

Cloud Healthcare API の FHIR 検索の実装は、REST と FHIR の仕様のガイドラインと制限事項を遵守しながら、スケーラビリティとパフォーマンスに優れています。これを達成するために、FHIR 検索には次のプロパティがあります。

  • 検索の合計は、最後のページが返されるまでの見積もりです。 fhir.search メソッドから返される検索結果には、検索の合計(Bundle.total プロパティ)が含まれます。これは、検索の一致の合計数です。検索の合計は、検索結果の最後のページが返されるまでの見積もりです。結果の最後のページで返される検索の合計は、検索のすべての一致の正確な合計です。

  • 検索結果は順次ページ分けを行います。 返される検索結果が増えると、レスポンスには、次の結果ページを取得するためのページ分け URL(Bundle.link.url)が含まれます。

基本的な使用例

FHIR 検索は、次のユースケースに対するソリューションを提供します。

これらのユースケースで使用できるソリューションについては、次のセクションをご覧ください。

順番にブラウジングする

ユーザーが目的の結果を見つけるまで、結果のページ全体を順番に閲覧する低レイテンシ アプリケーションを構築できます。このソリューションは結果をスキップしないでページごとに閲覧するため、一致の数が小さい場合に有効です。ユースケースで、ユーザーが一度に複数のページに移動したり、逆方向に移動したりする必要がある場合は、近くのページに移動をご覧ください。

このソリューションは、結果の最後のページが返されるまで正確な検索合計を提供できません。ただし、結果の各ページについて、おおよその検索の合計を返すことができます。バックグラウンド プロセスでは正確な合計検索数が必要になる場合がありますが、人間のユーザーにとっては通常、おおよその合計検索数で十分です。

ワークフロー

このソリューションのワークフローの例を次に示します。

  1. アプリが fhir.search メソッドを呼び出すと、検索結果の最初のページが返されます。返される結果が多い場合は、レスポンスにページ分け URL(Bundle.link.url)が含まれます。レスポンスには、検索の合計(Bundle.total)も含まれます。返される結果が最初のレスポンスよりも多い場合、検索の合計は概算になります。詳細については、ページ分けと並べ替えをご覧ください。

  2. アプリには、検索結果のページ、結果の次のページへのリンク(存在する場合)、検索の合計が表示されます。

  3. ユーザーが結果の次のページを表示したい場合は、リンクをクリックして、ページ分け URL を呼び出します。さらに多くの結果を返す場合は、新しいページ分け URL がレスポンスに含まれます。レスポンスには検索の合計も含まれます。これは、返される結果が他にもある場合に、更新された見積もりです。

  4. アプリには、検索結果の新しいページ、結果の次のページへのリンク(存在する場合)、検索の合計が表示されます。

  5. 前の 2 つの手順は、ユーザーが検索を停止するか、結果の最後のページが返されるまで繰り返されます。

ベスト プラクティス

人間が読んで理解しやすいページサイズを選択します。ユースケースに応じて、1 ページあたり 10 ~ 20 件の一致があります。ページサイズが小さいほど読み込みは速くなります。また、ページ上のリンクが多すぎると、ユーザーが管理するのが難しくなります。ページサイズは _count パラメータで制御します。

一連の検索結果を処理する

検索結果のセットを取得するには、ページ分け URL を使用して fhir.search メソッドを連続して呼び出します。検索結果の数が十分に少ない場合、返されるページがなくなるまで続行することで、完全な結果セットを取得できます。正確な検索結果は、結果の最後のページに含まれます。検索結果が取得されたら、アプリはそれらの検索結果を読み取って、必要な処理、分析、集計を行うことができます。

検索結果の数が非常に多い場合、検索結果の最後のページ(および正確な検索結果の合計)を現実的な時間内に取得できないことがあります。

ワークフロー

このソリューションのワークフローの例を次に示します。

  1. アプリは、fhir.search メソッドを呼び出します。このメソッドは、検索結果の最初のページを返します。返される結果が多い場合は、レスポンスにページ分け URL(Bundle.link.url)が含まれます。

  2. さらに多くの結果を返す場合、アプリは前のステップのページ分け URL を呼び出して、検索結果の次のページを取得します。

  3. 返される結果がなくなるか、その他の事前定義された上限に到達するまで、アプリは前の手順を繰り返します。検索結果の最後のページに到達すると、検索の合計が正確になります。

  4. アプリによって検索結果が処理されます。

ユースケースに応じて、アプリで次のいずれかを行います。

  • すべての検索結果が受信されるのを待ってから、データを処理します。
  • fhir.search への連続した呼び出しごとに受信するデータを処理します。
  • 返される一致数や経過時間など、なんらかの制限を設定します。上限に達した場合は、データを処理したり、データを処理したり、その他のなんらかの操作を行ったりできます。

設計オプション

検索レイテンシを低減できる可能性がある設計オプションをいくつか紹介します。

  • 大きなページサイズを設定します。 _count パラメータを使用して、大きなページサイズ(500 ~ 1,000 など)をユースケースに応じて設定します。大きなページサイズを使用すると、各ページ取得のレイテンシが増加しますが、検索結果全体を取得するのに必要なページ数が少なくなるため、プロセス全体が高速化される可能性があります。

  • 検索結果を制限する。 正確な検索の合計(リソース コンテンツを返す必要がない)だけが必要な場合、fhir.search メソッドの _elements パラメータを identifier に設定します。これにより、完全な FHIR リソースを返す場合と比較して、検索クエリのレイテンシが短縮される可能性があります。詳細については、検索結果で返されるフィールドの制限をご覧ください。

プリフェッチとキャッシュ保存が必要なユースケース

ページ分け URL を使用して fhir.search メソッドを連続して呼び出すという単純なメカニズムより進んだ機能が必要になる場合があります。1 つの可能性としては、アプリと FHIR ストアの間にキャッシュ レイヤを構築し、検索結果をプリフェッチしてキャッシュに保存しながらセッション状態を維持する方法があります。アプリは、検索結果を 10 ~ 20 件の小さな「アプリページ」にグループ化できます。アプリは、ユーザーが選択した内容に基づいて、これらの小さなアプリページを順次、直接的にユーザーに提供できます。このタイプのワークフローの例については、近くのページに移動をご覧ください。

ユーザーが探しているものが見つかるまで大量の検索結果を走査できる、低レイテンシのソリューションを構築できます。レイテンシを低く保ちつつリソース消費の増加を比較的低く抑えながら、実質的に無制限の一致にまでスケーリングできます。検索結果のページに直接移動できます。現在のページから前または逆方向に、事前に設定したページ数だけ移動できます。結果の各ページで、推定合計検索数を指定できます。参考として、この設計は Google 検索の設計と類似しています。

ワークフロー

図 1 は、このソリューションのワークフローの例を示しています。このワークフローでは、表示する結果のページを選択するたびに、近くのページへのリンクがアプリに表示されます。この場合、アプリは選択したページから最大 5 ページ前に、選択したページから最大 4 ページ後にリンクします。4 ページ後のページを表示するため、アプリはユーザーが結果のページを選択すると 40 個の追加一致をプリフェッチします。

プリフェッチとキャッシュ

図 1.アプリは、検索結果を「アプリページ」にグループ化し、これをキャッシュに保存してユーザーが利用できるようにします。

図 1 はこれらのステップを図示したものです。

  1. ユーザーがアプリのフロントエンドに検索クエリを入力し、次のアクションを開始します。

    1. アプリは、fhir.search メソッドを呼び出して、検索結果の最初のページをプリフェッチします。

      _count パラメータを 100 に設定してレスポンスのページサイズを比較的小さく維持すると、レスポンス時間が比較的短くなります。返される結果が多い場合は、レスポンスにページ分け URL(Bundle.link.url)が含まれます。レスポンスには、検索の合計(Bundle.total)も含まれます。検索の結果は、返す結果が多い場合の推定です。詳細については、ページ分けと並べ替えをご覧ください。

    2. アプリは、キャッシュ レイヤにレスポンスを送信します。

  2. キャッシュ レイヤでアプリは、100 件の一致レスポンスを、ページあたり 10 件の一致を示す 10 ページのアプリページにグループ化します。アプリページは、アプリがユーザーに表示できる一致の小さなグループです。

  3. アプリによってアプリページ 1 がユーザーに表示されます。アプリページ 1 には、アプリページ 2 ~ 10 へのリンクと、検索の合計回数が表示されます。

  4. ユーザーが別のアプリページ(この例ではアプリページ 10)へのリンクをクリックすると、次のアクションが開始されます。

    1. アプリは、前のプリフェッチで返されたページ分け URL を使用して fhir.search メソッドを呼び出し、検索結果の次のページをプリフェッチします。

      _count パラメータを 40 に設定すると、ユーザーの検索クエリから次の 40 件の一致がプリフェッチされます。この 40 件の一致数は、アプリでユーザーが使用できるようになる次の 4 つのアプリページに相当します。

    2. アプリは、キャッシュ レイヤにレスポンスを送信します。

  5. キャッシュ レイヤでは、40 件の一致レスポンスを、ページあたり 10 件の一致を示す 4 ページのアプリページにグループ化します。

  6. アプリによってユーザーには、アプリページ 10 が表示されます。アプリページ 10 には、アプリページ 5 ~ 9(アプリページ 10 から 5 アプリページ前)とアプリページ 11 ~ 14(アプリページ 10 から 4 アプリページ後)へのリンクがあります。アプリページ 10 には、推定検索総数も含まれています。

ユーザーがアプリページへのリンクをクリックし続ける限り、この状態が続きます。ユーザーが現在のアプリページから戻るときに、近くのアプリページがすべてキャッシュされている場合は、ユースケースに応じて新しいプリフェッチを行わない選択もできます。

このソリューションは、次の理由により高速かつ効率的に使用できます。

  • 小さなプリフェッチ、さらには小さなアプリページでも、迅速に処理されます。
  • キャッシュに保存された検索結果により、同じ結果に対して複数の呼び出しを行う必要がなくなります。
  • このメカニズムは、検索結果の数が増えても、常に高速です。

設計オプション

ユースケースに応じて、次の設計オプションを検討してください。

  • アプリのページサイズ。 ユースケースに適している場合は、アプリページに 10 を超える一致を含めることができます。小規模なページは読み込みが速く、ページ上のリンクが多すぎると、ユーザーの管理が難しくなることがあります。

  • アプリページへのリンクの数。 ここで推奨しているワークフローでは、各アプリページが 9 つのアプリページへのリンクを返します。つまり、現在のアプリページから直接 5 アプリページ前のアプリページに 5 つのリンク、また現在のアプリページから直接 5 アプリページ後のアプリページに 4 つのリンクを返します。数値はユースケースに応じて調整できます。

ベスト プラクティス

  • キャッシュ レイヤは必要な場合にのみ使用してください。 キャッシュ レイヤを設定する場合は、ユースケースで必要な場合にのみ使用します。キャッシュ レイヤを必要としない検索では使用しないようにします。

  • キャッシュ サイズを減らす。 リソースを節約するため、検索結果の検索結果の表示に使用していたページの URL を保持しながら、古い検索結果を消去することでキャッシュ サイズを小さくできます。その後、ページの URL を呼び出すことで、必要に応じてキャッシュを再構築できます。FHIR ストアのリソースはバックグラウンドで作成、更新、削除されるため、同じページ分け URL を複数回呼び出すと結果が変わる可能性があります。キャッシュを削除するかどうか、削除する方法、キャッシュを削除する頻度は、ユースケースによって異なる設計上の決定です。

  • 特定の検索のキャッシュを削除します。 リソースを節約するため、非アクティブな検索の結果をキャッシュから完全に削除できます。非アクティブな最長検索を最初に削除することを検討してください。削除された検索が再びアクティブになると、エラー状態となり、キャッシュ レイヤが検索を再開することに留意してください。

現在のページの近くのページだけでなく、検索結果の任意のページにユーザー移動できるようにするには、近くのページに移動で説明したものと同様のキャッシュ レイヤを使用します。ただし、ユーザーが検索結果の任意のアプリページに移動できるようにするには、すべての検索結果をプリフェッチしてキャッシュに保存する必要があります。検索結果が比較的少ない場合はこれが可能です。検索結果が非常に多い場合は、すべてをプリフェッチすることは非現実的または不可能です。検索結果が非常に少ない場合でも、プリフェッチにかかる時間は、ユーザーが待つと予想される時間よりも長くなる可能性があります。

ワークフロー

近くのページに移動と同様のワークフローを設定しますが、次の重要な違いがあります。この場合アプリは、すべての一致が返されるか定義済みの上限に到達するまで、バックグラウンドで検索結果のプリフェッチを継続します。

このソリューションのワークフローの例を次に示します。

  1. アプリは fhir.search メソッドを呼び出して、ユーザーの検索クエリから検索結果の最初のページをプリフェッチします。返される結果が多い場合は、レスポンスにページ分け URL(Bundle.link.url)が含まれます。レスポンスには、検索の合計(Bundle.total)も含まれます。これは、返す結果が多い場合の推定値です。

  2. アプリは、レスポンスからの一致を 20 件の一致アプリページにまとめて、キャッシュに保存します。アプリページは、アプリがユーザーに表示できる一致の小さなグループです。

  3. アプリによって最初のアプリページが表示されます。アプリページには、キャッシュされたアプリページと推定検索総数へのリンクが含まれます。

  4. 返される結果が他にもある場合は、アプリで次の処理が行われます。

    • 前のプリフェッチから返されたページ分け URL を呼び出して、検索結果の次のページを取得します。
    • レスポンスに含まれる一致を 20 のアプリページにまとめ、キャッシュに保存します。
    • ユーザーが現在表示しているアプリページを更新して、新しく取得してキャッシュされたアプリページへの新しいリンクを追加します。
  5. 返される結果がなくなるか、その他の事前定義された上限に到達するまで、アプリは前の手順を繰り返します。検索結果の最後のページで、正確な検索の合計が返されます。

アプリがバックグラウンドでプリフェッチしてキャッシュ保存している間は、ユーザーはキャッシュされたページへのリンクを引き続き使用できます。

設計オプション

ユースケースに応じて、次の設計オプションを検討してください。

  • アプリのページサイズ。 ユースケースに適している場合、アプリページには 20 を超えたり、20 未満の一致を含めることができます。小規模なページは読み込みが速く、ページ上のリンクが多すぎると、ユーザーの管理が難しくなることがあります。

  • 検索の合計を更新します。 アプリが検索結果をプリフェッチしてバックグラウンドでキャッシュに保存している間は、より正確な検索結果を段階的に表示できます。これを行うには、以下を行うようアプリを構成します。

    • 設定した間隔で、キャッシュ レイヤの最新のプリフェッチから検索の合計(Bundle.total プロパティ)を取得します。これは、検索の合計の最新の現在の見積もりです。検索の合計が表示され、それが見積もりであることを示します。この更新の頻度は、ユースケースに応じて決定します。

    • キャッシュ レイヤからの検索の合計が正確なタイミングを認識します。つまり、検索結果の合計は、検索結果の最後のページからのものです。検索結果の最後のページに到達すると、アプリは検索の合計を表示し、検索の合計が正確であることを示します。その後、アプリはキャッシュ レイヤから合計検索数を取得します。

    一致が多数ある場合、バックグラウンドのプリフェッチとキャッシュは、ユーザーが検索セッションを完了する前に、検索結果の最後のページ(および正確な検索合計)に到達しない可能性があります。

ベスト プラクティス

  • 含まれているリソースの重複を排除します。 検索結果のプリフェッチとキャッシュ保存の際に _include パラメータと _revinclude パラメータを使用する場合は、プリフェッチごとにキャッシュに含まれるリソースの重複を排除することをおすすめします。これにより、キャッシュ サイズを減らすことでメモリを節約できます。 一致をアプリページにグループ化する場合は、各アプリページに適切なリソースを追加します。詳細については、検索結果に追加リソースを含めるをご覧ください。

  • プリフェッチとキャッシュ保存の制限を設定します。 検索結果が非常に多い場合は、すべてをプリフェッチすることは非現実的または不可能です。プリフェッチする検索結果の数に上限を設定することをおすすめします。これにより、キャッシュを管理可能なサイズに維持し、メモリを節約できます。たとえば、キャッシュのサイズを 10,000 または 20,000 一致に制限できます。または、プリフェッチするページ数を制限するか、プリフェッチが停止するまでの時間制限を設定することもできます。適用する制限のタイプとその適用方法は、ユースケースによって異なります。すべての検索結果が返される前に上限に達した場合、検索の合計がまだ見積もり値であるなど、そのことをユーザーに知らせることを検討してください。

フロントエンド キャッシュ

ウェブブラウザやモバイルアプリなどのアプリケーション フロントエンドでは、アーキテクチャにキャッシュ レイヤを導入する代わりに、検索結果をキャッシュに保存できます。このアプローチでは、AJAX 呼び出しを利用し、検索結果やページ分け URL を保存することで、前のページまたはナビゲーション履歴の任意のページへのナビゲーションを提供します。このアプローチの利点は次のとおりです。

  • キャッシュ レイヤよりもリソース消費量が少なくなる可能性があります。
  • キャッシュ処理を多くのクライアントに分散させるため、スケーラビリティが高くなります。
  • キャッシュされたリソースが不要になるタイミング(ユーザーがタブを閉じたときや検索インターフェースから離れたときなど)を簡単に判断できます。

一般的なおすすめの方法

このドキュメントのすべてのソリューションに適用されるベスト プラクティスは次のとおりです。

  • _count 値よりも小さいページを計画します。 状況によっては、指定した _count の値よりも一致が少ないページが返されることがあります。たとえば、ページサイズが特に大きい場合に、このエラーが発生することがあります。検索で _count 値より小さいページが返され、アプリでキャッシュ レイヤが使用されている場合は、次のことを行う必要があるかもしれません。(1)アプリページには想定される数よりも少ない結果を表示するか、(2)完全なアプリページ表示するためにさらにいくつかの結果を取得します。詳細については、ページ分けと並べ替えをご覧ください。

  • 再試行可能な HTTP リクエスト エラーを再試行する。 アプリは、再試行可能な HTTP リクエスト エラー(429500 など)を受け取り、受信後に再試行する必要があります。

ユースケースを評価する

任意のページへの移動、正確な検索合計の取得、推定合計の更新などの機能を実装すると、アプリの複雑さと開発コストが増加します。これらの機能により、レイテンシが増加し、Google Cloud リソースの使用にかかる費用も増加する可能性があります。これらの機能の価値がコストに正当化されるように、ユースケースを慎重に評価することをおすすめします。以下の点に注意してください。

  • 任意のページへの移動。 通常、ユーザーは現在のページから特定のページや多くのページに移動する必要はありません。ほとんどの場合、近くのページに移動するだけで十分です。

  • 正確な検索合計数。 検索合計数は、FHIR ストアのリソースが作成、更新、削除されると変更される可能性があります。そのため、正確な検索合計は、検索結果が表示された時点(検索結果の最後のページ)で正確ですが、時間の経過とともに正確性を維持しない場合があります。そのため、ユースケースによっては、正確な検索合計数に関するアプリの価値が制限される可能性があります。