Статьи

Приложения для аукционов и торгов с использованием NoSQL на основе документов


В
моем предыдущем посте мы говорили о том, как моделировать аукционы и продукты, на этот раз мы рассмотрим, как моделировать ставки.

Прежде чем мы сможем это сделать, нам нужно выяснить, как мы собираемся их использовать. Как я уже упоминал, я собираюсь использовать Ebay в качестве источника для «макетов приложений». Поэтому я пошел на Ebay и сделал пару снимков экрана.

Вот актуальная страница аукциона:

образ

А вот актуальная страница ставок.

образ

Это говорит нам о нескольких вещах:

  • Ставки не доступны для главной страницы.
  • Существует большая вероятность того, что для большинства товаров количество заявок будет небольшим (менее тысячи).
  • Даже для товаров с большим количеством предложений мы по большей части заботимся только о самых последних.

Это документ аукциона, каким мы его видели в последний раз:

{
   "Quantity":15,
   "Product":{
      "Name":"Flying Monkey Doll",
      "Colors":[
         "Blue & Green"
      ],
      "Price":29,
      "Weight":0.23
   },
   "StartsAt":"2011-09-01",
   "EndsAt":"2011-09-15"
}

Вопрос в том, куда мы выставляем заявки? Одним из простых вариантов было бы поместить все заявки в документ аукциона, например так:

{
   "Quantity":15,
   "Product":{
      "Name":"Flying Monkey Doll",
      "Colors":[
         "Blue & Green"
      ],
      "Price":29,
      "Weight":0.23
   },
   "StartsAt":"2011-09-01",
   "EndsAt":"2011-09-15",
   "Bids": [
     {"Bidder": "bidders/123", "Amount": 0.1, "At": "2011-09-08T12:20" }
   ]
}

Проблема такого подхода заключается в том, что теперь мы вынуждены загружать заявки, когда хотим загрузить аукцион, но основной сценарий заключается в том, что нам нужны только детали аукциона, а не все детали заявок. На самом деле, нам нужно только количество заявок и выигрышная заявка, она также не сможет правильно обработать сценарий аукциона с высокой процентной ставкой, в котором много заявок.

Это оставляет нас с несколькими вариантами. Один из них указывает на то, что мы не заботимся о торгах и аукционах как о времени, требующем внимания. Пока мы принимаем заявки, нам не нужно давать вам немедленную обратную связь. Действительно, так работает большинство аукционных площадок. Они дают вам кэшированное представление данных, обновляя их каждые 30 секунд или около того. Идея состоит в том, чтобы снизить стоимость фактического принятия новых заявок до необходимого минимума. Как только аукцион будет закрыт, мы сможем выяснить, кто на самом деле победил, и уведомить их.

Хорошим дизайном для этого сценария будет отдельный документ предложения для каждой ставки и индекс карты / уменьшения для получения суммы выигрышной ставки и большого количества. Что-то вроде этого

     {"Bidder": "bidders/123", "Amount": 0.1, "At": "2011-09-08T12:20", "Auction": "auctions/1234"}
     {"Bidder": "bidders/234", "Amount": 0.15, "At": "2011-09-08T12:21", "Auction": "auctions/1234" }
     {"Bidder": "bidders/123", "Amount": 0.2, "At": "2011-09-08T12:22", "Auction": "auctions/1234" }

И индекс:

from bids in docs.Bids
select new { Count = 1, bid.Amount, big.Auction }

select result from results
group result by result.Auction into g
select new 
{
   Count = g.Sum(x=>x.Count),
   Amount = g.Max(x=>x.Amount),
   Auction = g.Key
}

Как вы можете себе представить, из-за природы индексов RavenDB, мы можем дешево вставить новые ставки, не дожидаясь, пока индексация заработает. И мы всегда можем отобразить последнее рассчитанное значение Аукциона, включая время, в течение которого оно стабильно.

Это одна модель для аукциона, но другой будет гораздо более строгий сценарий, когда вы не можете просто принять какую-либо ставку. Это может быть система, в которой с вас взимается плата за одну ставку, поэтому принятие известной недопустимой ставки не допускается (если вы тем временем перебили цену). Как бы мы создали такую ​​систему? Мы все еще можем использовать предыдущий дизайн и просто отложить фактическое выставление счетов на более поздний этап, но давайте предположим, что это является сильным ограничением для системы.

В этом случае мы не можем полагаться на индексы, потому что нам нужна немедленная согласованная информация и она должна быть дешевой. С RavenDB у нас есть хранилище документов, которое является ACIDly последовательным. Таким образом, мы можем сделать следующее, хранить все заявки на аукцион в одном документе:

{
   "Auction": "auctions/1234",
   "Bids": [
     {"Bidder": "bidders/123", "Amount": 0.1, "At": "2011-09-08T12:20", "Auction": "auctions/1234"}
     {"Bidder": "bidders/234", "Amount": 0.15, "At": "2011-09-08T12:21", "Auction": "auctions/1234" }
     {"Bidder": "bidders/123", "Amount": 0.2, "At": "2011-09-08T12:22", "Auction": "auctions/1234" }
    ]
}

И мы изменяем Аукционный документ так:

{
   "Quantity":15,
   "Product":{
      "Name":"Flying Monkey Doll",
      "Colors":[
         "Blue & Green"
      ],
      "Price":29,
      "Weight":0.23
   },
   "StartsAt":"2011-09-01",
   "EndsAt":"2011-09-15",
   "WinningBidAmount": 0.2,
   "BidsCount" 3
}

Добавление BidsCount и WinningBidAmount к аукциону означает, что мы можем очень дешево показать их пользователям. Поскольку RavenDB является транзакционным, мы можем сделать это следующим образом:

using(var session = store.OpenSession())
{
  session.Advanced.OptimisticConcurrency = true;
  
  var auction = session.Load<Auction>("auctions/1234")
  var bids = session.Load<Bids>("auctions/1234/bids");
  
  bids.AddNewBid(bidder, amount);
  
  auction.UpdateStatsFrom(bids);
  
  session.SaveChanges();
}

Теперь мы гарантируем, что это либо удастся полностью (и у нас будет новая выигрышная ставка), либо оно полностью провалится, не оставляя следов. Обратите внимание, что AddNewBid отклонит ставку, которая не выше (сгенерировать исключение), и если у нас есть две одновременные модификации, RavenDB скинет это. И Аукцион, и его заявки рассматриваются как единая транзакционная единица, как и должно быть.

Последний вопрос — как обращаться с аукционом с высокой процентной ставкой, который собирает много заявок. Мы не беспокоились об этом в предыдущей модели, потому что это было оставлено для обработки RavenDB. В этом случае, поскольку мы используем один документ для заявок, мы должны позаботиться об этом сами. Есть несколько вещей, которые мы должны рассмотреть здесь:

  • Проигравшие заявки обычно мало интересны.
  • Нам, вероятно, все же нужно держать их на всякий случай.

Для этого мы осуществим разбиение для документа заявок. Что это значит?

Всякий раз, когда число заявок в документе ставок достигает 500 заявок, мы разделяем документ. Мы берем самые старые 250 заявок и переносим их в документ исторических заявок, а затем сохраняем.

Таким образом, у нас есть набор исторических документов с 250 предложениями, каждый из которых вряд ли когда-либо будет прочитан, но мы должны сохранить его, и у нас есть основной документ предложений, который содержит самые последние (и соответствующие предложения. Высокий интерес). Аукцион может выглядеть так:

  • аукционы / 1234 <- Аукционный документ
  • аукционы / 1234 / торги <- тендерный документ
  • аукционы / 1234 / заявки / 1 <- исторические заявки № 1
  • аукционы / 1234 / заявки / 2 <- исторические заявки № 2

И этого на данный момент достаточно, я думаю, этот пост продлился немного дольше, чем я предполагал, но, надеюсь, мне удалось объяснить вам как окончательные дизайнерские решения, так и процесс, который использовался для их достижения.

Мысли?