web-dev-qa-db-ja.com

RESTページ分割されたデータを返すサービスでデータをキャッシュすることは可能ですか?

RESTアーキテクチャの原則の下では、RESTfulアプリケーションはステートレスである必要があるため、ASP.NET 4 RESTサービス(GET動詞を使用)を呼び出すたびに、数万レコードの場合、RESTサービスはそれらを10のチャンクでページ分割します(OData v4を使用)。これにより、ユーザーが毎回10レコードしかロードしないため、UIが軽量になりますASP.NETコントローラーは、データアクセスレイヤー(Dapper micro ORM)の読み取りメソッドを呼び出して、次の10レコードのチャンクを呼び出します。コントローラーは、それぞれ10レコードしか返さなくても、同じ数千のレコードを繰り返しプルします。 ODataページネーションエンジンのおかげで、データアクセスレイヤー(Dapper)は毎回同じ数千のレコードをクエリするため、時間がかかり、コストもかかります。Dapperが使用するクエリを変更して、ページネーションフィルターをクエリレベルまで下げることができます。ですが、ODataが送信するフィルターは非常に複雑で、 WHERE句でフィルターを生成するためのセマンティックツリーを生成する余裕はありません。さらに、そもそもODataは機能しませんか?同じファイラーが何度も何度もリクエストされている場合に、毎回データベースを呼び出さないようにするために、何千ものレコードをどこかに単純にキャッシュすることはできませんか?

ああ、そうです。EntityFrameworkは完全にノーゴーです。代わりにDapperが必須です。

3
Rikai no hōhō

何千ものレコードをどこかに単純にキャッシュして、毎回データベースを呼び出さないようにすることはできませんか?

はい、しかしそれはstatelessであるのとは正反対であり、RESTはそうです。あなたは反対しようとしていますRESTfulサービスの主なイデオロギー。

できますか?技術レベルでは、本当に必要な場合は確認してください。しかし、これは XY問題 の場合です。提案する解決策(Y-データのキャッシュ)は、実際の問題の回避策です(X-クエリにページ番号を付けて、返されるデータの量を減らす)。

完全なデータセットを保存しても、問題の半分しか解決されません。ユーザーが平均して2ページしか表示しない場合でも、20有用な行と9980役に立たない行を取得したことになります(合計10,000行を想定しています)。つまり、取得されたデータの99.8%は決して使用されません。それはまだ大きなスペースの無駄です。

さらに、フィルターの一意の組み合わせごとに個別のデータセットをキャッシュする必要があります。つまり、特定のレコードが複数の異なるフィルター処理されたデータセットに表示される可能性があるため、メモリに大量の重複データが存在する可能性があります。
同時ユーザーの数が非常に多く、そのすべてのリクエストをキャッシュしていると仮定すると、取得するために、テーブル全体を一度メモリに格納するだけの方が効率的であると考え始めています重複を取り除く(これを行う必要があると言っているのではありません、すべてをキャッシュすると、より多くの問題が発生することを指摘しているだけです解決する可能性があります)。

ページ分割の問題の解決策として、ページ分割されていないデータをキャッシュしてはならない理由はたくさんあります。

Dapperが使用するクエリを変更して、ページネーションフィルターをクエリレベルまで下げることができることはわかっていますが、それは負担が大きすぎることがわかりました

データセットのページ分割を拒否した場合、ページ分割されたデータセットを期待することはできません。しかし、その後は、正しい解を除外して、より簡単な非RESTfulな解を優先します。これは将来のための技術的負債を作成する可能性があります。

Dapperの例を使用してこのページ分割を確認してください。 サブクエリを独自のクエリに変更する必要があります。

SELECT  *
FROM    ( SELECT    ROW_NUMBER() OVER ( ORDER BY InsertDate) AS RowNum, *
          FROM      Posts
          WHERE     InsertDate >= '1900-01-01'
        ) AS result
WHERE   RowNum >= 1 // *your pagination parameters
    AND RowNum =< 10  //*
ORDER BY RowNum

これを自分で実装するために必要なのは、行の制限を理解することだけです(例では1および10)。これらは簡単な計算を使用して見つけることができます。

注:pageNumberには1インデックスが付けられていると想定しています。これは、一般的にUIがそれを表現する方法だからです。あなたの場合、pageSizeは10です。

var row_limit_lower = ((pageNumber - 1) * pageSize) + 1;
var row_limit_upper = (pageNumber * pageSize) - 1;

要求されているページ番号を把握します。これに基づいて(およびアプリケーションで常に10と推測されるページサイズ)、クエリ自体に必要なページネーションを計算して実装できるため、多くの不要な行の取得を防ぐことができます。

3
Flater

私はASP.Netを使用したことがありませんが、まず第一に、データアクセス層に必要なレコードのみを取得させないのはなぜですか?ほとんどのリレーショナルデータベースでは、LIMITやOFFSETなどのページ番号を付けることができます。Dapperでこれらの機能にアクセスできると思います。

それが整ったら、ページ番号をキャッシュキーに組み込むことができます。

ただし、その後のページにアクセスするユーザーは多くないため、ページがキャッシュされないカットオフポイントを設けることは価値がありますが、そのコンテンツを保存しようとすると、より頻繁に使用されるデータがキャッシュから削除される可能性があります。

1
bigblind

1つのオプション(既に実際に言及されています)は、追加コンポーネントとしてシステムにキャッシングレイヤーを導入することです。その後、すべてを同じままにすることができますが、データベースにクエリを実行する必要がある場合は、最初にキャッシュを経由します。キャッシュを実行できない場合は、クエリがデータベースに渡されます。キャッシュキーは、基本的にはストアドプロシージャに渡したパラメーターです(文字列化)。

このタイプの透過キャッシングは、システムを実際にステートフルにしたり、RESTプリンシパルに違反したりしません。データのSLAが何であるかを自由に定義できます。キャッシュの寿命に合わせてください。

データをいくつかのストレージ、BLOB、Redis、File、Mongoにシリアル化し(ユースケースに最適なものを試す必要があります)、小さなサービスを上に追加することをお勧めします。

クエリのページ分割が常に最良の選択肢であるとは限りません。クエリの実行に30秒かかる場合はどうなりますか?すべてのページで実行しますか?また、あなたのケースでは、フィルタリングが複雑であり、ストアドプロシージャと通信するのが難しい場合があります。

特定の要件を満たすために可能な解決策をどのように適用するのが最善かを決定する必要があるので、ここで最良の答えが1つだけではありません。

また、「キャッシュが大きくなりすぎる」問題を解決する方法はたくさんあります。繰り返しますが、自分に合ったものを見つける必要があります。ユーザーごとにキャッシュ内のエントリは1つだけです。

1
sylvanaar

純粋にREST UIを少し変更したい場合は、この種の問題に対する解決策があります。ユーザーに最初にGETを実行させる代わりに、 POST。クエリの実行を開始し、どこかで結果をキャプチャします。実行中に、結果を指すURIを返します。

その後、UIは結果のプルバックを開始し、必要に応じてページ分割できます。必要に応じて、ある時点で結果を期限切れにして、410の応答を返すことができます。

1
JimmyJames