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章で説明した商品のカタログず組み合わせるこずができたす。

バスケットの通垞のモデリングには、SKU、quantityフィヌルド、および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のシャヌディングに関する蚘事

Source: https://habr.com/ru/post/J260291/


All Articles