Redisビットマップに基づくオンラインストアの高速カタログフィルター





すべてのオンラインストアがユーザーが必要なものを見つけるのを助けるべきであることは秘密ではありません。 特に大量の商品がある場合(> 10)。 製品のカタログ化が助けになりますが、製品をカテゴリに分割することは戦いの半分です。 カテゴリ内の製品は、そのプロパティでフィルタリングできる必要があります。 特に、製品が雑多な場合、たとえば、衣類、電子機器、宝石など そして、ここで、彼のeコマース製品を書く開発者は、人生の不愉快な現実に直面しています:商品は全く異なる特性を持っているかもしれません、いくつかの製品は存在しないかもしれません、いくつかの製品は1つの特性について異なる値を持っているかもしれません(ドレスの色は、または青、それぞれ、青と青の両方で表示するとよいでしょう)。 簡単に言えば、 EAVがあります。 また、開発の終わりに向かってEAVが顧客によって診断されたり、リリース後に動的プロパティによるフィルターの追加を求められることもあります。



あなたは耳の後ろでcombき始め、あなたのリレーショナルモデルには何も適合しないことに気づき、あなたが優れたWeb開発者であればすでにDBMSとしてMySQLを選択している、またはあなたがそれについて読んでいるならPostgreSQL、そして異なる企業の支持者であれば多分PostgreSQLを選択している一般的に製品を選択できますが、誰も禁止していません。 ただし、ほとんどの場合、これはすべてRDBMSであり、動的プロパティは簡単にねじ込まれません(読みにくい:難しい)。誰もができるわけではありません。

ここでは、たとえば、人気のあるeコマースプラットフォームMagentoのデータベースダイアグラムの一部を示します。



(フルサイズをクリックします)



それで、私たちは宝石店のカタログのためにそのようなフィルタを作る仕事をしました。 そして、私たちが一般的に持っていた商品の特性は、MySQLのjsonにありました。 製品ページ自体にのみ必要であり、他には必要ありませんでした。 オンラインジュエリーストアであり、リングサイズ、金属の種類、金属の色、インサートなどのプロパティを最初に設定できるという状況が少し改善されました。 それにもかかわらず、結果として得られるソリューションは普遍的であり、商品のプロパティの完全に動的なセットのコードを簡単に変更できます。

データベースの半分とコードの半分以上を変更してフィルターを追加することは適切ではないことが決定されたため、特に、文字列、操作でビットごとに動作するクールな機能、SETBIT、GETBIT、 BITOP、BITCOUNT。 コマンドの意味は、ドキュメントを調べなくても簡単に推測できます。



Redisのフィルターストレージスキームは次のとおりです。

  1. 1つのキーは、製品のプロパティの1つの値です。たとえば、 size-18:またはcolor-red:
  2. 各キーのデータは長さNビットのビットマップでした。Nはストア内の商品の数です。 したがって、ビットマップ内のビット位置は製品IDであり、ビット自体は、このIDがこの値を持つフィルターに属するかどうかを示します。


より良い理解のための例:

製品ID(ビット位置) ID:1 ID:2 ID:3 ID:4 ID:5
リディスキー redis-value
サイズ-17: 0 0 1 0 1
サイズ-18: 1 1 0 0 0
サイズ-19: 0 0 0 1 0
カラーレッド: 1 1 1 0 1
カラーグリーン: 0 0 1 1 1


したがって、大根には5つのキーと値のペアがあります。 色(2オプション)とサイズ(3オプション)の2つのフィルターがあります。 ストアには5つの製品しかないため、ビットマップは5ビットで構成されます。 表は、ID 2の製品はサイズ18の赤で、ID 3の製品はサイズ17ですが、赤と緑の両方の色を持っていることを示しています。

商品のカタログにフィルターを適用するには、ユーザーが選択したフィルター値に対してビット単位のAND演算を実行するだけで十分です。 たとえば、ある人がサイズ18の緑色の製品を望んでいる場合、フィルターで2つのドウを突くと、次のようになります。

BITOP AND result-key size-18 color-green







結果キーには、ビットマップがあります。これは、これら2つのビットマップのビットごとの乗算です。 ユニットがある場所、ユニットの位置のみを計算でき、指定されたフィルターを持つ製品IDがあります。

 $bytes = str_split($result_key); $ids = []; // resulting product IDs for ($i = 0; $i < count($bytes); $i++) // iterate in bytes (8 products at once) for ($j = 7; $j >= 0; $j--) // iterate over bits in current 8-products chunk if ($bytes[$i] & (1 << $j)) // >0 if bit was 1, otherwise 0 $ids[] = 8*$i + (8-$j); // calculate product ID and append it
      
      







ビットマップは、管理パネルで商品を追加/変更するときに生成されます。商品の使用可能なプロパティに応じて、目的のフィルターでBITSETを実行します。



そのようなソリューションの利点:

1)ほとんどメモリを食べません。 50,000個を超える製品、約100個のフィルター値、つまり50,000 * 100 = 5,000,000ビット= 625キロバイトのメモリのみがあります。

2)非常に高速。 ビット単位の操作の複雑さはO(N)ですが、文字列は数百万バイト単位では測定されず、2、3のビットマップに50,000ビットを掛けることは、プロセッサの数マイクロ秒のタスクです。 全体として、最悪の場合(すべてのフィルターを掛ける)、コマンドをREDISに送信する前と結果を受信した後のPHPでの時間差の測定は40ミリ秒です(これは、以下の条項 3の追加機能によるものです)。 Web用の非常にリアルタイムのページ生成が可能です。 それがたくさんあるように思える場合-結果をキャッシュしてください、しかしそれは私たちを完全に満足させました。

3) 各フィルターおよびカテゴリーの商品数をカウントする機能 。 これは便利な副作用になりました。 現在使用可能な各フィルター値の製品数を計算できるようになりました。 はい、これには、現在の結果キー(製品の現在の選択)と各フィルター値、およびBITCOUNTのビット単位の乗算が必要です。 これを実現し、空の商品セットを持つフィルターを動的に非表示にできるようになりました(ダイヤモンド付きのプラチナリングを選択した人は、「最大3,000ルーブル」の価格でフィルターを表示しません)。



そのような決定の欠点:

1)たとえば、ユーザーがルーブルまで価格を手動でフィルタリングできる場合、範囲フィルターをエンコードできない。 さて、OTとTOのスライダーを持つものは、あなたが知っています。 まだ携帯電話では機能しません。 私たちの店では、価格フィルターはそれぞれ5つのオプション(最大3000、3000-10000など)であり、5つのビットマップprice-0-3000:、price-3000-10000:などとしてエンコードしました。 。

2)選択したIDのリストをMySQLに転送してデータを取得する必要。 もちろん、サンプルの大根からIDのリストを投げるのは良くありません

 SELECT * FROM products WHERE id IN (....)
      
      





しかし、判明したように、非常に高速に動作します。 最悪の場合、私が間違えなければ、すべてのカテゴリに対して選択されたすべてのフィルターを含むカタログページ全体が600ミリ秒で生成されました。 いくつかのフィルターの証明:





その結果、このビジネスを非常に迅速に定着させることが判明しました。PHPのRedisバインダーが利用可能で、Redis自体は非常に原始的であり、1日で簡単に習得できます。



All Articles