eコマースでのMongoDBの使用例(パート2)





[ 前編 ]



この投稿では、最初の部分に収まらないものがあります。 これらは、 aggregation framework



にある演算子の一部であり、Webサイトのエコシステムセクションの3つの記事をMongoDB



助けを借りてかなり自由に翻訳し、eコマースの使用事例を説明しています。



使用事例は8つの記事に分かれており、条件に応じて3つのグループに分けることができます。 翻訳に最も興味深いのは、 e-commerce



関連する3つの資料です。



  1. 集約フレームワークの演算子
  2. 製品カタログ
  3. カートおよび在庫管理
  4. カテゴリー階層






集約フレームワークの演算子



原則として、 aggregation framework



には、 $project



$group



などの基本的な演算子があり、そこからpipeline



チェーンが形成されます。

しかし、 $cond



ような演算子もあり、これらは常にメイン演算子の中にあります。

$ condは、次の 場合の通常の近似アナログです。

バージョン2.6



から登場し、2つの記録形式があります。

 { $cond: { if: <boolean-expression>, then: <true-case>, else: <false-case-> } }  { $cond: [ <boolean-expression>, <true-case>, <false-case> ] }
      
      





たとえば、次のドキュメントがあります。

 { "_id" : 1, "item" : "abc1", qty: 300 } { "_id" : 2, "item" : "abc2", qty: 200 } { "_id" : 3, "item" : "xyz1", qty: 250 }
      
      





qty



250



以上の場合、 discount



30



設定しdiscount



。それ以外の場合、 discount



20



なります

 db.test.aggregate( [ { $project:{ item: 1, discount: { $cond: { if: { $gte: [ "$qty", 250 ] }, then: 30, else: 20 } } } } ] )
      
      





得られた結果:

 { "_id" : 1, "item" : "abc1", "discount" : 30 } { "_id" : 2, "item" : "abc2", "discount" : 20 } { "_id" : 3, "item" : "xyz1", "discount" : 30 }
      
      







$ ifNull は、フィールドが存在し、nullでないことを確認します



フィールドがない場合は、目的の値に置き換えます。

 { "_id" : 1, "item" : "abc1", description: "product 1", qty: 300 } { "_id" : 2, "item" : "abc2", description: null, qty: 200 } { "_id" : 3, "item" : "xyz1", qty: 250 }
      
      



description



フィールドが存在しないかnull



場合、 Unspecified



返しnull





 db.test.aggregate( [ { $project: { item: 1, description: { $ifNull: [ "$description", "Unspecified" ]}} } ] )
      
      



結果:

 { "_id" : 1, "item" : "abc1", "description" : "product 1" } { "_id" : 2, "item" : "abc2", "description" : "Unspecified" } { "_id" : 3, "item" : "xyz1", "description" : "Unspecified" }
      
      





$ let 変数を作成します



$let



演算子は変数を作成し、それらを使用していくつかの操作を実行できます。

この例では、2つの変数、 total



を作成し、2つのフィールドprice, tax



合計の値total



割り当てます。 論理演算子cond



を返す変数discounted



結果。

その後、変数total



discounted



を掛けて、それらの製品を返します。



 { _id: 1, price: 10, tax: 0.50, applyDiscount: true } { _id: 2, price: 10, tax: 0.25, applyDiscount: false } db.test.aggregate( [ { $project: { finalTotal: { $let: { vars: { total: { $add: [ '$price', '$tax' ] }, discounted: { $cond: { if: '$applyDiscount', then: 0.9, else: 1 } } }, in: { $multiply: [ "$$total", "$$discounted" ] } } } } }] ) { "_id" : 1, "finalTotal" : 9.450000000000001 } { "_id" : 2, "finalTotal" : 10.25 }
      
      





$ map は各要素に特定の式を適用します

式を各要素に適用します。この例では、配列の各要素に+2



追加されます。

 { _id: 1, quizzes: [ 5, 6, 7 ] } { _id: 2, quizzes: [ ] } db.grades.aggregate([{ $project:{ adjustedGrades: {$map: {input: "$quizzes", as: "grade", in: { $add: [ "$$grade", 2 ] } } } } }]) { "_id" : 1, "adjustedGrades" : [ 7, 8, 9 ] } { "_id" : 2, "adjustedGrades" : [ ] }
      
      







$ setEqualsは 配列を比較します



2つ以上の配列を比較し、類似した要素がtrue



場合はtrue



を返しtrue





結果
{$ setEquals:[["a"、 "b"、 "a"]、["b"、 "a"]]} 本当
{$ setEquals:[["a"、 "b"]、[["a"、 "b"]]]}


$ setIntersection は一致する要素を返します

2つ以上の配列を受け取り、各入力配列に存在する要素を含む配列を返します。 配列の1つが空であるか、ネストされた配列を含む場合、空の配列を返します。

結果
{$ setIntersection:[["a"、 "b"、 "a"]、["b"、 "a"]]} ["B"、 "a"]
{$ setIntersection:[["a"、 "b"]、[["a"、 "b"]]]} []


$ setUnion は、任意の入力パラメーターに存在する要素を返します。



2つ以上の配列を取り、任意の入力配列に現れる要素を含む配列を返します。

結果
{$ setUnion:[["a"、 "b"、 "a"]、["b"、 "a"]]} ["B"、 "a"]
{$ setUnion:[["a"、 "b"]、[["a"、 "b"]]]} [["A"、 "b"]、 "b"、 "a"]


$ setDifference もsetUnionですが、その逆も同様です

結果
{$ setDifference:[["a"、 "b"、 "a"]、["b"、 "a"]]} []
{$ setDifference:[["a"、 "b"]、[["a"、 "b"]]]} [「A」、「b」]


$ setIsSubset はサブセットをチェックします

2つの配列を取り、最初の配列が2番目の配列のサブセットである場合(最初の配列が2番目の配列と等しい場合を含む)、Trueを返します。

結果
{$ setIsSubset:[["a"、 "b"、 "a"]、["b"、 "a"]]} 本当
{$ setIsSubset:[["a"、 "b"]、[["a"、 "b"]]]}


$ anyElementTrue

配列を要素のセットとして評価し、いずれかの要素がTrueの場合Trueを返し、その逆の場合はFalseを返します。 空の配列はfalseを返します。 1つの引数を取ります。

結果
{$ anyElementTrue:[[true、false]]}
{$ anyElementTrue:[[[false]]]}
{$ anyElementTrue:[[null、false、0]]}
{$ anyElementTrue:[[]]}


$ allElementsTrue

配列をチェックし、配列の1つの要素がfalseでない場合はTrueを返します。 それ以外の場合は、falseを返します。 空の配列はTrueを返します。

結果
{$ allElementsTrue:[[true、1、 "someString"]]}
{$ allElementsTrue:[[[false]]]}
{$ allElementsTrue:[[]]}
{$ allElementsTrue:[[null、false、0]]}


$ cmp -2つの要素を比較します

2つの要素を比較して返します。



 { "_id" : 1, "item" : "abc1", description: "product 1", qty: 300 } { "_id" : 2, "item" : "abc2", description: "product 2", qty: 200 } { "_id" : 3, "item" : "xyz1", description: "product 3", qty: 250 }
      
      





$cmp



を使用して、 qty



フィールドの値を250



と比較します。



 db.test.aggregate( [ { $project:{ _id: 0, item: 1,qty: 1, cmpTo250: { $cmp: [ "$qty", 250 ] } } } ] )
      
      





結果:

 { "item" : "abc1", "qty" : 300, "cmpTo250" : 1 } { "item" : "abc2", "qty" : 200, "cmpTo250" : -1 } { "item" : "xyz1", "qty" : 250, "cmpTo250" : 0 }
      
      







$ add add

 { "_id" : 1, "item" : "abc", "price" : 10, "fee" : 2 } { "_id" : 2, "item" : "jkl", "price" : 20, "fee" : 1 } db.test.aggregate([ { $project: { item: 1, total: { $add: [ "$price", "$fee" ] } } } ]) { "_id" : 1, "item" : "abc", "total" : 12 } { "_id" : 2, "item" : "jkl", "total" : 21 }
      
      







$減算 減算

日付の差をミリ秒単位で返す場合があります。

 { "_id" : 1, "item" : "abc", "price" : 10, "fee" : 2, "discount" : 5 } { "_id" : 2, "item" : "jkl", "price" : 20, "fee" : 1, "discount" : 2 } db.test.aggregate( [ { $project: { item: 1, total: { $subtract: [ { $add: [ "$price", "$fee" ] }, "$discount" ] } } } ] ) { "_id" : 1, "item" : "abc", "total" : 7 } { "_id" : 2, "item" : "jkl", "total" : 19 }
      
      





$乗算 乗算



 { "_id" : 1, "item" : "abc", "price" : 10, "quantity": 2 } { "_id" : 2, "item" : "jkl", "price" : 20, "quantity": 1 } db.test.aggregate([ { $project: { item: 1, total: { $multiply: [ "$price", "$quantity" ] } } } ]) { "_id" : 1, "item" : "abc", "total" : 20 } { "_id" : 2, "item" : "jkl", "total" : 20 }
      
      





$除算 演算子

 { "_id" : 1, "name" : "A", "hours" : 80, "resources" : 7 }, { "_id" : 2, "name" : "B", "hours" : 40, "resources" : 4 } db.test.aggregate([ { $project: { name: 1, workdays: { $divide: [ "$hours", 8 ] } } } ]) { "_id" : 1, "name" : "A", "workdays" : 10 } { "_id" : 2, "name" : "B", "workdays" : 5 }
      
      







$ concat- ストリング連結

 { "_id" : 1, "item" : "ABC1", quarter: "13Q1", "description" : "product 1" } db.test.aggregate([ { $project: { itemDescription: { $concat: [ "$item", " - ", "$description" ] } } } ]) { "_id" : 1, "itemDescription" : "ABC1 - product 1" }
      
      





$ substr は部分文字列を返します



開始インデックスと先頭からの文字数を取得します。 インデックスはゼロから始まります。



 { "_id" : 1, "item" : "ABC1", quarter: "13Q1", "description" : "product 1" } { "_id" : 2, "item" : "ABC2", quarter: "13Q4", "description" : "product 2" } db.inventory.aggregate([{ $project:{ item: 1, yearSubstring: { $substr: [ "$quarter", 0, 2 ] }, quarterSubtring: { $substr: [ "$quarter", 2, -1 ] } } }]) { "_id" : 1, "item" : "ABC1", "yearSubstring" : "13", "quarterSubtring" : "Q1" } { "_id" : 2, "item" : "ABC2", "yearSubstring" : "13", "quarterSubtring" : "Q4" }
      
      







$ toLower- 小文字に変換します

 { "_id" : 1, "item" : "ABC1", quarter: "13Q1", "description" : "PRODUCT 1" } db.test.aggregate([{ $project:{ item: { $toLower: "$item" }, description: { $toLower: "$description" }} }]) { "_id" : 1, "item" : "abc1", "description" : "product 1" }
      
      





$ toUpper は大文字に変換します



 { "_id" : 2, "item" : "abc2", quarter: "13Q4", "description" : "Product 2" } db.inventory.aggregate( [ { $project:{ item: { $toUpper: "$item" }, description: { $toUpper: "$description" } } } ] ) { "_id" : 2, "item" : "ABC2", "description" : "PRODUCT 2" }
      
      







$ strcasecmpは 文字列を比較します

2つの文字列を比較して返します。



演算子は大文字と小文字を区別しません。

 { "_id" : 1, "item" : "ABC1", quarter: "13Q1", "description" : "product 1" } { "_id" : 2, "item" : "ABC2", quarter: "13Q4", "description" : "product 2" } { "_id" : 3, "item" : "XYZ1", quarter: "14Q2", "description" : null } db.inventory.aggregate([{ $project:{ item: 1, comparisonResult: { $strcasecmp: [ "$quarter", "13q4" ] } } }]) { "_id" : 1, "item" : "ABC1", "comparisonResult" : -1 } { "_id" : 2, "item" : "ABC2", "comparisonResult" : 0 } { "_id" : 3, "item" : "XYZ1", "comparisonResult" : 1 }
      
      







日付を操作する


アカウントの $ dayOfYear

請求書の月の $ dayOfMonth

$ dayOfWeek は、アカウントの曜日(1(土曜日)-7(日曜日))を返します。

$年 年を 返します

$ month は、アカウントの月を返します

$ week は、年の 番号を 0〜53の 数値として返します

$ hour は、0〜23の数値として 時間を 返します

$ minutes は、0〜59の数値として 分を 返します

$ second は、0〜23の数値として秒を返します

$ millisecond は、0〜999の数値として ミリ秒を 返します



日付の年間通算日を1〜366の数値として返します。

たとえば、1月1日に1を返します。

 { "_id": 1, "item": "abc", "date" : ISODate("2014-01-01T08:15:39.736Z") } db.sales.aggregate( [ { $project: { year: { $year: "$date" }, month: { $month: "$date" }, day: { $dayOfMonth: "$date" }, hour: { $hour: "$date" }, minutes: { $minute: "$date" }, seconds: { $second: "$date" }, milliseconds: { $millisecond: "$date" }, dayOfYear: { $dayOfYear: "$date" }, dayOfWeek: { $dayOfWeek: "$date" }, week: { $week: "$date" } } } ] ) { "_id" : 1, "year" : 2014, "month" : 1, "day" : 1, "hour" : 8, "minutes" : 15, "seconds" : 39, "milliseconds" : 736, "dayOfYear" : 1, "dayOfWeek" : 4, "week" : 0 }
      
      







$ dateToString は文字列に変換します



指定された形式に従って、日付オブジェクトを文字列に変換します。

 { "_id" : 1, "item" : "abc", "price" : 10, "quantity" : 2, "date" : ISODate("2014-01-01T08:15:39.736Z") } db.test.aggregate( [ { $project: { yearMonthDay: { $dateToString: { format: "%Y-%m-%d", date: "$date" } }, time: { $dateToString: { format: "%H:%M:%S:%L", date: "$date" } } } } ] ) { "_id" : 1, "yearMonthDay" : "2014-01-01", "time" : "08:15:39:736" }
      
      







製品カタログ



この章では、 MongoDB



を使用して電子商取引システムで製品カタログを設計する基本的な原則について説明します。



商品のカタログには、さまざまなタイプのオブジェクトを格納でき、各オブジェクトには独自の特性リストが必要です。

リレーショナルデータベースの場合、パフォーマンスの違いなど、同様の問題に対するいくつかの解決策があります。 これらのオプションのいくつかを見てから、これがMongoDB



どのように処理されるかを確認します。



SQLおよびリレーショナルデータモデル



具体的なテーブルの継承(有限クラスのテーブルによる継承)

リレーショナルモデルの最初のオプションは、商品のカテゴリごとに独自のテーブルを作成することです。



 CREATE TABLE `product_audio_album` ( `sku` char(8) NOT NULL, ... `artist` varchar(255) DEFAULT NULL, `genre_0` varchar(255) DEFAULT NULL, `genre_1` varchar(255) DEFAULT NULL, ..., PRIMARY KEY(`sku`)) ... CREATE TABLE `product_film` ( `sku` char(8) NOT NULL, ... `title` varchar(255) DEFAULT NULL, `rating` char(8) DEFAULT NULL, ..., PRIMARY KEY(`sku`)) ...
      
      





このアプローチには、柔軟性に関連する2つの主な制限があります。





単一テーブルの継承



リレーショナルモデルの2番目のオプションは、すべての製品カテゴリに単一のテーブルを使用することです。 また、商品の種類に関するデータを保存する必要がある場合はいつでも新しい列を追加します。



 CREATE TABLE `product` ( `sku` char(8) NOT NULL, ... `artist` varchar(255) DEFAULT NULL, `genre_0` varchar(255) DEFAULT NULL, `genre_1` varchar(255) DEFAULT NULL, ... `title` varchar(255) DEFAULT NULL, `rating` char(8) DEFAULT NULL, ..., PRIMARY KEY(`sku`))
      
      





このアプローチはより柔軟性があり、さまざまなタイプの製品に基づいて単一のクエリを作成できます。



複数のテーブルの継承



また、リレーショナルモデルでは、「複数のテーブルの継承」を使用できます。これは、製品のすべてのカテゴリに共通の属性がメインproduct



テーブルにあり、個別の属性(それぞれ独自のカテゴリ)に異なる属性が含まれるパターンです。



次のSQL



例を検討してください。

 CREATE TABLE `product` ( `sku` char(8) NOT NULL, `title` varchar(255) DEFAULT NULL, `description` varchar(255) DEFAULT NULL, `price`, ... PRIMARY KEY(`sku`)) CREATE TABLE `product_audio_album` ( `sku` char(8) NOT NULL, ... `artist` varchar(255) DEFAULT NULL, `genre_0` varchar(255) DEFAULT NULL, `genre_1` varchar(255) DEFAULT NULL, ..., PRIMARY KEY(`sku`), FOREIGN KEY(`sku`) REFERENCES `product`(`sku`)) ... CREATE TABLE `product_film` ( `sku` char(8) NOT NULL, ... `title` varchar(255) DEFAULT NULL, `rating` char(8) DEFAULT NULL, ..., PRIMARY KEY(`sku`), FOREIGN KEY(`sku`) REFERENCES `product`(`sku`)) ...
      
      





このオプションは、単一のテーブル継承よりも効率的で、各カテゴリのテーブルを作成するよりもわずかに柔軟性があります。 このオプションでは、特定の製品に関連するすべての属性を取得するために「高価な」 JOIN



を使用する必要があります。



各エンティティの属性値



そして、リレーショナルモデルの最後のパターンは、 entity-attribute-value



スキーマです。 このスキームに従って、たとえばentity_id



attribute_id



、および各製品の説明に使用されるvalue



3つの列を持つテーブルが作成されます。



オーディオ録音を保存する例を考えてみましょう。

エンティティ 属性 価値
sku_00e8da9b タイプ オーディオアルバム
sku_00e8da9b タイトル 最高の愛
sku_00e8da9b ... ...
sku_00e8da9b アーティスト ジョン・コルトレーン
sku_00e8da9b ジャンル ジャズ
sku_00e8da9b ジャンル 全般
... ... ...






このスキームは非常に柔軟です:



どの製品にも、任意の属性セットを設定できます。 新しい製品カテゴリでは、データベースを変更する必要はありません。



このオプションの短所は、 JOIN



を含む多くのクエリが必要になることであり、これはパフォーマンスにあまり適していません。



さらに、リレーショナルデータベースシステムを使用する一部のe-commerce



ソリューションでは、このデータをBLOB列にシリアル化します。 そして、商品の属性を見つけて分類することが難しくなります。



非リレーショナルデータモデル



mongodb



リレーショナルデータベースでmongodb



ないため、製品カタログを作成する柔軟性の観点から追加の機会があります。



最善のオプションは、1つのコレクションを使用してすべての種類のドキュメントを保存することです。 また、ドキュメントごとに任意のスキームを使用できるため、製品のすべての特性(属性)を1つのドキュメントに保存できます。



文書のルートには、カタログ全体の検索を容易にするための製品に関する一般的な情報が必要です。 そして、すでにサブドキュメントには、各ドキュメントに固有のフィールドがあるはずです。 例を考えてみましょう:



 { sku: "00e8da9b", type: "Audio Album", title: "A Love Supreme", description: "by John Coltrane", asin: "B0000A118M", shipping: { weight: 6, dimensions: { width: 10, height: 10, depth: 1 }, }, pricing: { list: 1200, retail: 1100, savings: 100, pct_savings: 8 }, details: { title: "A Love Supreme [Original Recording Reissued]", artist: "John Coltrane", genre: [ "Jazz", "General" ], ... tracks: [ "A Love Supreme Part I: Acknowledgement", "A Love Supreme Part II - Resolution", "A Love Supreme, Part III: Pursuance", "A Love Supreme, Part IV-Psalm" ], }, }
      
      







映画に関する情報が保存されているドキュメントの場合{ type: "Film" }



主なフィールドは、価格、配達などです。 同じままです。 ただし、サブ文書の内容は異なります。 例:



 { sku: "00e8da9d", type: "Film", ..., asin: "B000P0J0AQ", shipping: { ... }, pricing: { ... }, details: { title: "The Matrix", director: [ "Andy Wachowski", "Larry Wachowski" ], writer: [ "Andy Wachowski", "Larry Wachowski" ], ..., aspect_ratio: "1.66:1" }, }
      
      







ほとんどの場合、製品カタログの主な操作は検索です。 以下に便利なさまざまなタイプのクエリを示します。 すべての例はPython/PyMongo



ます。



ジャンル別にアルバムを検索し、リリース年で並べ替えます

このクエリは、特定のジャンルに対応し、新しい順に並べ替えられた商品を含むドキュメントを返します。

 query = db.products.find({'type':'Audio Album', 'details.genre': 'jazz'}).sort([('details.issue_date', -1)])
      
      





このクエリでは、クエリおよび並べ替えで使用されるフィールドにインデックスが必要です。

 db.products.ensure_index([ ('type', 1), ('details.genre', 1), ('details.issue_date', -1)])
      
      







割引率で降順で並べ替えられた製品を検索します。

ほとんどのリクエストは特定のタイプの製品(アルバム、映画など)に対するものですが、多くの場合、特定の価格帯ですべての商品を返品する必要があります。



すべてのドキュメントにある価格フィールドを使用して検索するために、割引が有効な製品を検索します。



 query = db.products.find( { 'pricing.pct_savings': {'$gt': 25 }).sort([('pricing.pct_savings', -1)])
      
      





このクエリでは、 pricing.pct_savings



フィールドにインデックスを作成します。



 db.products.ensure_index('pricing.pct_savings')
      
      





MongoDB



は、インデックスを昇順または降順で読み取ることができます。



有名な俳優が演じたすべての映画を見つける

ドキュメントタイプが"Film"



で、ドキュメント属性の値が{ 'actor': 'Keanu Reeves' }



ドキュメントを見つけます。 結果を日付で降順に並べ替えます。



 query = db.products.find({'type': 'Film', 'details.actor': 'Keanu Reeves'}).sort([('details.issue_date', -1)])
      
      





このクエリでは、次のインデックスを作成します。



 db.products.ensure_index([ ('type', 1), ('details.actor', 1), ('details.issue_date', -1)])
      
      





インデックスはすべてのドキュメントにあるtype



フィールドで始まり、 details.actor



フィールドに沿って進むため、検索領域を絞り込みます。 したがって、インデックスは最も効果的です。



タイトルに特定の単語を含むすべての映画を見つける



データベースの種類に関係なく、単語による検索クエリを実行するには、データベースが結果を取得するためにドキュメントの一部をスキャンする必要があります。



Mongodb



は、クエリで正規表現をサポートしています。 Pythonでは、 re



モジュールを使用して正規表現を作成できます。

 import re re_hacker = re.compile(r'.*hacker.*', re.IGNORECASE) query = db.products.find({'type': 'Film', 'title': re_hacker}).sort([('details.issue_date', -1)])
      
      







Mongodbは正規表現用の特別な構文を提供するため、クエリではre



モジュールなしで実行できます。 前の例に代わる次の例を検討してください。



 query = db.products.find({ 'type': 'Film', 'title': {'$regex': '.*hacker.*', '$options':'i'}}).sort([('details.issue_date', -1)])
      
      





$options



特別な演算子です。この場合、検索語で大文字と小文字が区別されないことを示します。



インデックスを作成します。



 db.products.ensure_index([ ('type', 1), ('details.issue_date', -1), ('title', 1) ])
      
      





このインデックスを使用すると、ドキュメント全体のスキャンを回避できます。インデックスのおかげで、 title



フィールドのみがスキャンされます。



シャーディング





スケーリングのデータベースパフォーマンスはインデックスに依存します。 シャーディングを使用してパフォーマンスを向上させると、大きなインデックスがRAMに収まります。

シャード設定で、 shard key



選択しshard key



。これにより、 mongos



要求をシャードまたはシャードの小さなグループに直接ルーティングできます。



type



フィールドはほとんどのリクエストに存在するため、 shard key



含めることができshard key



shard key



残りについては、以下を考慮してください:





次の例では、 details.genre



フィールドがtype



次いで2番目に重要なフィールドでtype



ます。 Python/PyMongo



シャーディングを初期化する

 >>> db.command('shardCollection', 'product', { key : { 'type': 1, 'details.genre' : 1, 'sku':1 } } ) { "collectionsharded" : "details.genre", "ok" : 1 }
      
      





失敗したshard key



を作成したとしても、シャーディングに役立ちます:

  1. シャーディングは、インデックスの保存に使用できる大量のRAMを提供します。
  2. MongoDB



    は、シャード間でクエリを並列化し、遅延を削減します。




読書


シャーディングはスケーリングに適した方法ですが、一部のデータセットは、 mongos



クエリを目的のシャードに誘導するような方法で分割することはできません。

この場合、 mongos



はすべてのシャードにすぐにリクエストを送信し、その結果、すでにクライアントに返されます。

どのシャードを読むことが望ましいかを明確に示しているため、生産性をわずかに高めることができます。



次の例のSECONDARY



プロパティを使用すると、接続全体のセカンダリノード(およびプライマリノード)からの読み取りが可能になります。

 conn = pymongo.MongoClient(read_preference=pymongo.SECONDARY)
      
      





SECONDARY_ONLY



は、クライアントがセカンダリメンバからのみ読み取ることを意味します。

 conn = pymongo.MongoClient(read_preference=pymongo.SECONDARY_ONLY)
      
      





特定のリクエストにread_preference



を指定することもできます。たとえば、次のとおりです。

 results = db.product.find(..., read_preference=pymongo.SECONDARY)
      
      





または

 results = db.product.find(..., read_preference=pymongo.SECONDARY_ONLY)
      
      







カートおよび在庫管理





オンラインストアのユーザーは定期的に「バスケット」にアイテムを追加および削除するため、倉庫内の商品の量は購入中に何度も絶えず変化する可能性があります。さらに、ストアユーザーはいつでも購入を拒否し、他の予期しない問題が発生することがあります解決するには、注文をキャンセルする必要があります。



これらすべてにより、倉庫で入手可能な商品を追跡するのが少し難しくなる可能性があり、ユーザーが既に予約されている商品を「購入」できないようにする必要があります。



したがって、バスケットには時間があります。その間、バスケットが非アクティブだった場合、予約された商品は他の全員が再び利用できるようになり、バスケットは空になります。







各倉庫在庫単位( SKU ;またはアイテム)の現在の在庫量を含むドキュメント、およびそれぞれの商品の数量を含むバスケットのリストは、 Inventory



コレクションに保存する必要があります。 その後、利用可能な残高がバスケットに入っていれば返却できますが、クライアントはしばらくバスケットを使用しませんでした。



次の例では、 SKUを格納する_id



フィールド



 #     _id:SKU,   ,      16 , 1 .  #   id 42,  2 .   c id 43.      19    . { _id: '00e8da9b', qty: 16, carted: [ { qty: 1, cart_id: 42, timestamp: ISODate("2012-03-09T20:55:36Z") }, { qty: 2, cart_id: 43, timestamp: ISODate("2012-03-09T21:55:36Z") }, ] }
      
      





この例では、シンプルなスキームが使用されています。生産では、このスキームを第2章で説明した商品のカタログと組み合わせることができます。



バスケットの通常のモデリングには、SKUquantity



フィールド、およびitem_details





 #  item_details          # ,           . { _id: 42, last_modified: ISODate("2012-03-09T20:55:36Z"), status: 'active', items: [ { sku: '00e8da9b', qty: 1, item_details: {...} }, { sku: '0ab42f88', qty: 4, item_details: {...} } ] }
      
      







運営

バスケットへのアイテムの追加バスケットを



使用する際の主なポイントは、倉庫内の既存の在庫からバスケットへの商品の移動です。

既に予約した商品をバスケットに入れないことが重要です。関数を使用してこれを実装しますadd_item_to_cart







 def add_item_to_cart(cart_id, sku, qty, details): now = datetime.utcnow() # ,         result = db.cart.update( {'_id': cart_id, 'status': 'active' }, {'$set': {'last_modified':now}, '$push': {'items': {'sku':sku, 'qty':qty, 'details':details}}}, w=1 ) if not result['updatedExisting']: raise CartInactive() #   result = db.inventory.update( {'_id':sku, 'qty': {'$gte': qty}}, {'$inc': {'qty':-qty}, '$push': {'carted': {'qty':qty, 'cart_id':cart_id, 'timestamp':now}}}, w=1 ) if not result['updatedExisting']: #     ,    ,    . db.cart.update( {'_id': cart_id }, { '$pull': { 'items': {'sku': sku } } }) raise InadequateInventory()
      
      





, _id



.


— , . , .

, .







, , .

 def update_quantity(cart_id, sku, old_qty, new_qty): #        _id   . now = datetime.utcnow() delta_qty = new_qty - old_qty # ,           . result = db.cart.update( {'_id': cart_id, 'status': 'active', 'items.sku': sku }, {'$set': { 'last_modified': now, 'items.$.qty': new_qty } }, w=1 ) if not result['updatedExisting']: raise CartInactive() #   result = db.inventory.update( {'_id':sku, 'carted.cart_id': cart_id, 'qty': {'$gte': delta_qty} }, {'$inc': {'qty': -delta_qty },'$set': { 'carted.$.qty': new_qty, 'timestamp': now } }, w=1 ) if not result['updatedExisting']: #           db.cart.update( {'_id': cart_id, 'items.sku': sku }, {'$set': { 'items.$.qty': old_qty } } ) raise InadequateInventory()
      
      











, , .

 def checkout(cart_id): now = datetime.utcnow() #     <code>active</code>    <code>pending</code>. #           cart = db.cart.find_and_modify( {'_id': cart_id, 'status': 'active' }, update={'$set': { 'status': 'pending','last_modified': now } } ) if cart is None: raise CartInactive() #   ;  payment try: collect_payment(cart) db.cart.update( {'_id': cart_id }, {'$set': { 'status': 'complete' } } ) db.inventory.update( {'carted.cart_id': cart_id}, {'$pull': {'cart_id': cart_id} }, multi=True) except: db.cart.update( {'_id': cart_id }, {'$set': { 'status': 'active' } } ) raise
      
      





, pending



. , .

  • , cart_id



    inventory



    . complete



    .
  • , , .




.

.

timeout



.

 def expire_carts(timeout): now = datetime.utcnow() threshold = now - timedelta(seconds=timeout) #       db.cart.update( {'status': 'active', 'last_modified': { '$lt': threshold } }, {'$set': { 'status': 'expiring' } }, multi=True ) #     for cart in db.cart.find({'status': 'expiring'}): #      for item in cart['items']: db.inventory.update( { '_id': item['sku'], 'carted.cart_id': cart['id'], 'carted.qty': item['qty'] }, {'$inc': { 'qty': item['qty'] }, '$pull': { 'carted': { 'cart_id': cart['id'] } } } ) db.cart.update( {'_id': cart['id'] }, {'$set': { 'status': 'expired' })
      
      







:

  • , .
  • , .
  • , expired



    .




status



last_modified



.

 db.cart.ensure_index([('status', 1), ('last_modified', 1)])
      
      







.

: , inventory



, , , .



, inventory



carted



.



 def cleanup_inventory(timeout): now = datetime.utcnow() threshold = now - timedelta(seconds=timeout) #       for item in db.inventory.find( {'carted.timestamp': {'$lt': threshold }}): carted = dict( (carted_item['cart_id'], carted_item) for carted_item in item['carted'] if carted_item['timestamp'] < threshold ) #  :       carted,     inventory for cart in db.cart.find( { '_id': {'$in': carted.keys() }, 'status':'active'}): cart = carted[cart['_id']] db.inventory.update( {'_id': item['_id'], 'carted.cart_id':cart['_id'] }, { '$set': {'carted.$.timestamp':now }} ) del carted[cart['_id']] #  :  carted        . for cart_id, carted_item in carted.items(): db.inventory.update( { '_id': item['_id'], 'carted.cart_id': cart_id, 'carted.qty': carted_item['qty'] }, { '$inc': { 'qty': carted_item['qty'] }, '$pull': { 'carted': { 'cart_id': cart_id } } } )
      
      







«carted» , , timeout



. :

  • timeout



    , - , .
  • , , .






, shard key



_id



, _id



.

mongos



_id



mongod



.



_id



shard key



.



_id



cart



, .



, , ( MD5 SHA-1) ObjectID, _id.

:



 import hashlib import bson cart_id = bson.ObjectId() cart_id_hash = hashlib.md5(str(cart_id)).hexdigest() cart = { "_id": cart_id, "cart_hash": cart_id_hash } db.cart.insert(cart)
      
      





, , _id



shard key



.



, , ( Sleep ()



), .



Python/PyMongo :

 >>> db.command('shardCollection', 'inventory', 'key': { '_id': 1 } ) { "collectionsharded" : "inventory", "ok" : 1 } >>> db.command('shardCollection', 'cart', 'key': { '_id': 1 } ) { "collectionsharded" : "cart", "ok" : 1 }
      
      











カテゴリツリーを商品でモデル化します。各カテゴリは、祖先のリストまたは親のリストを持つドキュメントに保存されます。

たとえば、ジャンルのリストを使用します。





これらのカテゴリのタイプは頻繁に変更されないため、モデリングでは、更新操作のパフォーマンスではなく、階層の維持に必要な操作に焦点を当てます。



この回路には次の特性があります。





次のプロトタイプを検討してください。

 { "_id" : ObjectId("4f5ec858eb03303a11000002"), "name" : "Modal Jazz", "parent" : ObjectId("4f5ec858eb03303a11000001"), "slug" : "modal-jazz", "ancestors" : [ { "_id" : ObjectId("4f5ec858eb03303a11000001"), "slug" : "bop", "name" : "Bop" }, { "_id" : ObjectId("4f5ec858eb03303a11000000"), "slug" : "ragtime", "name" : "Ragtime" } ] }
      
      







このセクションでは、Eコマースソリューションで必要になる可能性のあるカテゴリツリー操作操作について説明します。すべての例では、Python/PyMongo







Reading and Displaying

Request カテゴリを使用します

次のオプションを使用して、カテゴリツリーを読み取って表示します。このリクエストでは、フィールドを使用してslug



、カテゴリに関する情報を返します“bread crumb”







 category = db.categories.find({'slug':slug}, {'_id':0, 'name':1, 'ancestors.slug':1, 'ancestors.name':1 })
      
      





フィールドに一意のインデックスを作成しますslug







 >>> db.categories.ensure_index('slug', unique=True)
      
      





カテゴリーの追加カテゴリー

を追加するには、最初にその祖先を定義する必要があります。新しいカテゴリーを追加します-図に示すようにSwing



、カテゴリーの子孫Ragtime



として:





祖先を除いて、カテゴリーを追加する操作は非常に簡単です。祖先を配列に追加するには、次の関数を検討してください。



 def build_ancestors(_id, parent_id): parent = db.categories.find_one({'_id': parent_id}, {'name': 1, 'slug': 1, 'ancestors':1}) parent_ancestors = parent.pop('ancestors') ancestors = [ parent ] + parent_ancestors db.categories.update({'_id': _id}, {'$set': { 'ancestors': ancestors } })
      
      





ツリーを1レベル上に移動し、祖先のリストを取得するRagtime



必要があります。これを使用して、祖先のリストを作成できますSwing





次に、ドキュメントを作成し、次の値を設定します。



 doc = dict(name='Swing', slug='swing', parent=ragtime_id) swing_id = db.categories.insert(doc) build_ancestors(swing_id, ragtime_id)
      
      





新しいカテゴリを追加する操作では、デフォルトのインデックスで_id







カテゴリの祖先 変更できます

このセクションでは、ツリーを変更し、カテゴリのbop



上にカテゴリを配置するプロセスを検討しますswing









ドキュメントbop



更新して、親の値を変更します。

 db.categories.update({'_id':bop_id}, {'$set': { 'parent': swing_id } } )
      
      





次の関数は、先祖が保存されているフィールドを再構築します。



 def build_ancestors_full(_id, parent_id): ancestors = [] while parent_id is not None: parent = db.categories.find_one({'_id': parent_id}, {'parent': 1, 'name': 1, 'slug': 1, 'ancestors':1}) parent_id = parent.pop('parent') ancestors.append(parent) db.categories.update({'_id': _id}, {'$set': { 'ancestors': ancestors } })
      
      





次のループを使用して、すべての子孫を再構築できます bop







 for cat in db.categories.find({'ancestors._id': bop_id}, {'parent': 1}): build_ancestors_full(cat['_id'], cat['parent'])
      
      





ancestors._id



更新操作に必要なフィールドのインデックスを作成します



 db.categories.ensure_index('ancestors._id')
      
      







カテゴリの名前を変更するカテゴリの名前

を変更するには、カテゴリ自体とすべての子孫の両方を更新する必要があります。

次の図に示すように、カテゴリ「Bop」の名前を「BeBop」に変更することを検討してください



。最初にすることは、カテゴリの名前を変更することです。



 db.categories.update({'_id':bop_id}, {'$set': { 'name': 'BeBop' } })
      
      





次に、各子孫は祖先のリストを更新する必要があります。



 db.categories.update({'ancestors._id': bop_id}, {'$set': { 'ancestors.$.name': 'BeBop' } }, multi=True)
      
      





この操作では以下を使用します。



この更新用のインデックスは既にありancestors._id



ます。



シャーディング



このコレクションは非常に小さいため、このコレクションをシャーディングほとんどの場合は、制限された値です。シャードが必要な場合、shard key



フィールドは適切_id



です。



 >>> db.command('shardCollection', 'categories', { 'key': {'_id': 1} }) { "collectionsharded" : "categories", "ok" : 1 }
      
      







PS PMに書き込むための文法エラーと翻訳エラーのリクエスト。



使用材料:



エコシステムMongoDB

集約フレームワーク

オペレーターに関するヘルプハーバーリファレンス「デザインパターン」(ru)のシャーディングに関する記事




All Articles