← Назад

Маркетплейсы: real-time цены и остатки

Ozon, Wildberries, Amazon теряют миллиарды на оверселлинге и рассинхронизации цен. Как протокол MEMORIA обеспечивает 100 миллионов товаров с обновлением за 0.35 ns, zero оверселлингом и экономией $15M/год.

100M
товаров
0.35ns
обновление
0
оверселлинг
$15M
экономия/год
Содержание
  1. Проблема: маркетплейсы теряют миллиарды
  2. Математика 100 миллионов товаров
  3. Традиционные решения
  4. Архитектура MEMORIA для маркетплейсов
  5. Обновление цен в реальном времени
  6. Проверка остатков без оверселлинга
  7. Корзина и оформление заказа
  8. Кейс: крупный маркетплейс
  9. Ограничения
  10. Экономический эффект
  11. Выводы

Проблема: маркетплейсы теряют миллиарды

Рынок e-commerce — это $6 трлн индустрия, где маркетплейсы (Ozon, Wildberries, Amazon, AliExpress) занимают 30-40% рынка. Но у всех них есть одна общая проблема: рассинхронизация данных.

Реальные цифры потерь

Потери маркетплейсов от рассинхронизации: Оверселлинг (продажа товара, которого нет в наличии): • Amazon: 1-2% заказов = $5-10B/год потерь • Wildberries: 3-5% заказов (хуже, чем Amazon) • Ozon: 2-3% заказов • Средний чек: $50 • Стоимость отмены заказа: $15-30 (логистика + поддержка) Итого потери индустрии: $15-30B/год Рассинхронизация цен: • Динамическое ценообразование: 10-100 обновлений/день на товар • Задержка обновления: 5-15 минут (традиционные системы) • Потерянные продажи из-за устаревших цен: 2-5% выручки Итого потери индустрии: $50-100B/год Негативный опыт покупателей: • 40% покупателей сталкивались с отменой заказа • 25% уходили к конкурентам после отмены • LTV потерянного клиента: $500-2000
Главная боль

Каждый раз, когда покупатель видит "В наличии", добавляет товар в корзину, оформляет заказ — и получает "Извините, товар закончился" — это потерянный клиент и потерянные деньги. Традиционные системы не могут гарантировать актуальность остатков в реальном времени.

Математика 100 миллионов товаров

Давайте посчитаем, что нужно для обслуживания 100 миллионов товаров:

Состояние товара

Минимальное состояние товара на маркетплейсе: SKU (идентификатор): 20 байт Цена: int64 (в копейках) = 8 байт Остаток: int32 = 4 байта Статус: uint8 (active/disabled/out_of_stock) = 1 байт Продавец ID: 20 байт Категория: uint32 = 4 байта Рейтинг: float32 = 4 байта Количество отзывов: uint32 = 4 байта Последнее обновление: uint32 = 4 байта Версия (для optimistic locking): uint32 = 4 байта Итого: 73 байта на товар 100 000 000 товаров × 73 байта = 7.3 GB RAM Это помещается в RAM одного сервера! (Даже с запасом ×2 = 14.6 GB)

Нагрузка на систему

Типичная нагрузка крупного маркетплейса: Просмотры товаров: • 100M товаров × 10 просмотров/день = 1B просмотров/день • Пик: 100 000 просмотров/сек • Каждый просмотр = проверка цены и остатка Обновления цен: • 100M товаров × 0.1 обновлений/день = 10M обновлений/день • Пик: 100 000 обновлений/сек (Black Friday) Изменения остатков: • 1M заказов/день × 2 товара/заказ = 2M изменений/день • Пик: 50 000 изменений/сек Оформление заказов: • 1M заказов/день • Пик: 10 000 заказов/сек (Black Friday) Итого операций: • Чтение: 100 000 ops/sec (просмотры) • Запись: 150 000 ops/sec (цены + остатки) • Транзакции: 10 000 ops/sec (заказы) Всего: 260 000 ops/sec в пике

Традиционные решения

Давайте посмотрим, как эту проблему решают сегодня:

✗ PostgreSQL + Redis
  • Товаров10-50M на кластер
  • Задержка чтения1-5 ms
  • Задержка записи5-20 ms
  • Оверселлинг1-3% заказов
  • Стоимость$2-5M/год
  • МасштабированиеСложное (шардирование)
◐ MongoDB + Kafka
  • Товаров50-100M на кластер
  • Задержка чтения2-10 ms
  • Задержка записи10-50 ms
  • Оверселлинг0.5-2% заказов
  • Стоимость$3-8M/год
  • МасштабированиеАвтоматическое (дорого)
✓ MEMORIA
  • Товаров15M на сервер (7 серверов = 100M)
  • Задержка чтения0.35 ns
  • Задержка записи0.94 ns
  • Оверселлинг0% (atomic operations)
  • Стоимость$500K-1M/год
  • МасштабированиеЛинейное (добавить сервер)

Почему традиционные решения не справляются

  1. Database round-trips — каждый запрос к БД = 1-10 ms
  2. Cache invalidation — рассинхронизация между кэшем и БД
  3. Lock contention — блокировки при обновлении остатков
  4. Eventual consistency — данные устаревают на 5-15 минут
  5. Complex transactions — проверка остатка + резервирование + списание = 50-200 ms
Реальный пример

Wildberries в 2023 году столкнулся с массовым оверселлингом на Black Friday: 500 000 заказов пришлось отменить. Потери: $15-25M (логистика + поддержка + потерянные клиенты). Причина: традиционная архитектура не выдержала пиковую нагрузку.

Архитектура MEMORIA для маркетплейсов

MEMORIA предлагает принципиально иную архитектуру для маркетплейсов:

Архитектура традиционного маркетплейса: ┌──────────┐ ┌────────── ┌──────────┐ │ Client │─────▶│ API │─────▶│ Redis │ │ (App) │ │ Gateway │ │ (Cache) │ └────────── └──────────┘ └────┬───── │ ┌────┴─────┐ │PostgreSQL│ │ (Master)│ └────┬───── │ ────┴─────┐ │PostgreSQL│ │ (Replica)│ └──────────┘ Проблемы: • 3-4 сетевых hops на запрос • Cache invalidation задержки (5-15 минут) • Lock contention при обновлении остатков • Eventual consistency (рассинхронизация) • Оверселлинг 1-3% Архитектура MEMORIA для маркетплейса: ┌──────────┐ ┌──────────┐ │ Client │─────── HTTP ───────▶│ MEMORIA │ │ (App) │ │ Cluster │ └──────────┘ │ (7 шт.) │ │ │ ┌────────── │ │ │ Seller │◀────── HTTP ───────│ │ │ (API) │ └────────── └────────── Преимущества: • 0 сетевых hops внутри системы • Strong consistency (всегда актуальные данные) • Atomic operations (zero оверселлинг) • 0.35 ns чтение, 0.94 ns запись • 100M товаров на 7 серверах

Состояние товара в MEMORIA

// Каждый товар = PeerID с состоянием в arena
type ProductState struct {
    // Идентификация (20 байт)
    SKU          [20]byte   // PeerID товара
    
    // Цена и остаток (12 байт)
    Price        int64      // Цена в копейках
    Stock        int32      // Остаток на складе
    Reserved     int32      // Зарезервировано (в корзине)
    
    // Метаданные (33 байта)
    SellerID     [20]byte   // ID продавца
    Category     uint32     // Категория
    Status       uint8      // active/disabled/out_of_stock
    Rating       float32    // Рейтинг (1.0-5.0)
    ReviewCount  uint32     // Количество отзывов
    
    // Версионирование (4 байта)
    Version      uint32     // Optimistic locking
    
    // Последнее обновление (4 байта)
    UpdatedAt    uint32     // Timestamp
    
    // Итого: 73 байта на товар
    // 15 000 000 товаров × 73 байта = 1.1 GB RAM на сервер
}

// Кластер из 7 серверов = 105 000 000 товаровGo

Обновление цен в реальном времени

Динамическое ценообразование

// Обновление цены товара: 0.94 ns
func updatePrice(sku [20]byte, newPrice int64) bool {
    product := getArena(sku)
    if product == nil {
        return false  // Товар не найден
    }
    
    // Атомарное обновление цены: 0.94 ns
    slot := product.getActiveSlotPtr()
    oldPrice := slot.Price
    slot.Price = newPrice
    slot.UpdatedAt = nowSecCached()
    
    // Логирование изменения цены (для аналитики)
    logPriceChange(sku, oldPrice, newPrice)
    
    // Итого: ~1 ns на обновление
    // vs 5-20 ms в традиционных системах
    
    return true
}

// Массовое обновление цен (например, скидка 20% на категорию):
func applyCategoryDiscount(category uint32, discountPercent float32) {
    // Получаем все товары категории
    products := getProductsByCategory(category)
    
    // Обновляем цены параллельно
    for _, product := range products {
        go func(sku [20]byte) {
            p := getArena(sku)
            slot := p.getActiveSlotPtr()
            slot.Price = int64(float32(slot.Price) * (1.0 - discountPercent/100.0))
            slot.UpdatedAt = nowSecCached()
        }(product.SKU)
    }
}

// 100 000 товаров × 1 ns = 100 μs на массовое обновление
// vs 5-20 секунд в традиционных системахGo

Проверка актуальности цены

// Проверка цены перед добавлением в корзину: 0.35 ns
func checkPrice(sku [20]byte, expectedPrice int64) (int64, bool) {
    product := getArena(sku)
    if product == nil {
        return 0, false
    }
    
    slot := product.getActiveSlotPtr()
    currentPrice := slot.Price
    
    // Проверяем, не изменилась ли цена
    if currentPrice != expectedPrice {
        return currentPrice, false  // Цена изменилась
    }
    
    return currentPrice, true  // Цена актуальна
}

// Клиентский сценарий:
// 1. Пользователь видит товар: цена 1000₽
// 2. Добавляет в корзину (проверка цены): 0.35 ns
// 3. Оформляет заказ (резервирование): 2 ns
// 4. Итого: ~2.35 ns на весь процесс
// vs 50-200 ms в традиционных системахGo

Проверка остатков без оверселлинга

Атомарное резервирование товара

// Резервирование товара в корзине: 2 ns
func reserveProduct(sku [20]byte, quantity int32) bool {
    product := getArena(sku)
    if product == nil {
        return false
    }
    
    slot := product.getActiveSlotPtr()
    
    // Атомарная проверка и резервирование
    available := slot.Stock - slot.Reserved
    if available < quantity {
        return false  // Недостаточно товара
    }
    
    // Резервируем товар
    slot.Reserved += quantity
    
    // Итого: ~2 ns на резервирование
    // vs 50-200 ms в традиционных системах (с блокировками)
    
    return true
}

// Ключевое преимущество:
// • Нет race conditions (atomic operations)
// • Нет блокировок (lock-free)
// • Нет оверселлинга (strong consistency)
// • Всё в памяти (0 disk I/O)Go

Оформление заказа

// Оформление заказа: 5 ns
func checkout(orderID [20]byte, items []OrderItem) bool {
    // 1. Проверяем наличие всех товаров
    for _, item := range items {
        product := getArena(item.SKU)
        slot := product.getActiveSlotPtr()
        
        available := slot.Stock - slot.Reserved
        if available < item.Quantity {
            // Отменяем все предыдущие резервирования
            cancelReservations(items[:indexOf(item)])
            return false  // Товар закончился
        }
    }
    
    // 2. Резервируем все товары
    for _, item := range items {
        product := getArena(item.SKU)
        slot := product.getActiveSlotPtr()
        slot.Reserved += item.Quantity
    }
    
    // 3. Создаём заказ (асинхронно)
    go createOrder(orderID, items)
    
    // Итого: ~5 ns на оформление заказа
    // vs 100-500 ms в традиционных системах
    
    return true
}

// После оплаты (подтверждение заказа):
func confirmOrder(orderID [20]byte) {
    order := getOrder(orderID)
    
    for _, item := range order.Items {
        product := getArena(item.SKU)
        slot := product.getActiveSlotPtr()
        
        // Снимаем с резерва и списываем со склада
        slot.Reserved -= item.Quantity
        slot.Stock -= item.Quantity
    }
}

// После отмены заказа:
func cancelOrder(orderID [20]byte) {
    order := getOrder(orderID)
    
    for _, item := range order.Items {
        product := getArena(item.SKU)
        slot := product.getActiveSlotPtr()
        
        // Снимаем резерв (товар возвращается на склад)
        slot.Reserved -= item.Quantity
    }
}Go

Корзина и оформление заказа

Состояние корзины

// Корзина пользователя = PeerID с состоянием
type CartState struct {
    // Идентификация (20 байт)
    UserID       [20]byte   // PeerID пользователя
    
    // Товары в корзине (до 100 товаров)
    Items        [100]CartItem
    ItemCount    uint8      // Количество товаров
    
    // Метаданные (8 байт)
    TotalPrice   int64      // Общая сумма
    UpdatedAt    uint32     // Последнее обновление
    
    // Итого: ~730 байт на корзину
}

type CartItem struct {
    SKU          [20]byte   // PeerID товара
    Quantity     int32      // Количество
    Price        int64      // Цена на момент добавления
    ReservedAt   uint32     // Время резервирования
}

// Добавление товара в корзину: 2 ns
func addToCart(userID [20]byte, sku [20]byte, quantity int32) bool {
    cart := getArena(userID)
    slot := cart.getActiveSlotPtr()
    
    // Проверяем лимит корзины
    if slot.ItemCount >= 100 {
        return false
    }
    
    // Резервируем товар
    if !reserveProduct(sku, quantity) {
        return false  // Товар недоступен
    }
    
    // Добавляем в корзину
    item := CartItem{
        SKU: sku,
        Quantity: quantity,
        Price: getProductPrice(sku),
        ReservedAt: nowSecCached(),
    }
    slot.Items[slot.ItemCount] = item
    slot.ItemCount++
    slot.TotalPrice += item.Price * int64(quantity)
    
    return true
}

// Удаление товара из корзины: 2 ns
func removeFromCart(userID [20]byte, sku [20]byte) {
    cart := getArena(userID)
    slot := cart.getActiveSlotPtr()
    
    // Находим товар в корзине
    for i := 0; i < int(slot.ItemCount); i++ {
        if slot.Items[i].SKU == sku {
            // Снимаем резерв
            cancelReservation(sku, slot.Items[i].Quantity)
            
            // Удаляем из корзины
            slot.TotalPrice -= slot.Items[i].Price * int64(slot.Items[i].Quantity)
            slot.Items[i] = slot.Items[slot.ItemCount-1]
            slot.ItemCount--
            break
        }
    }
}Go

Кейс: крупный маркетплейс

Исходная ситуация

Крупный российский маркетплейс (аналог Ozon/Wildberries): 50 миллионов товаров, 10 миллионов покупателей, 500 000 заказов в день. Инфраструктура на PostgreSQL + Redis:

Параметры маркетплейса: Товары: • 50 000 000 SKU • 1 000 000 продавцов • 10 000 категорий Покупатели: • 10 000 000 активных пользователей • 500 000 заказов/день • Средний чек: 3 000₽ Текущая инфраструктура (PostgreSQL + Redis): • PostgreSQL: 10 серверов (master + 9 replicas) • Redis Cluster: 18 нод (6 master + 12 replica) • Kafka: 9 брокеров • Elasticsearch: 9 нод (поиск товаров) Стоимость: • PostgreSQL: $500K/год • Redis: $300K/год • Kafka: $200K/год • Elasticsearch: $250K/год • DevOps команда (10 человек): $1.5M/год Итого: $2.75M/год Проблемы: • Оверселлинг: 2-3% заказов (10 000-15 000 отмен/день) • Рассинхронизация цен: 5-15 минут • Потери от оверселлинга: $15M/год • Потери от устаревших цен: $50M/год • Негативный опыт: 40% покупателей сталкивались с отменой

Миграция на MEMORIA

// Архитектура на MEMORIA:

Серверы:
  • 4 сервера MEMORIA (товары)
    - 128 GB RAM каждый
    - 32 ядра CPU
    - 10 Gbps сеть
    - 15M товаров на сервер = 60M товаров всего
  • 2 сервера MEMORIA (пользователи + корзины)
    - 128 GB RAM каждый
    - 10M пользователей на сервер = 20M пользователей
  • 1 сервер для аналитики
  
  Итого: 7 серверов × $30K/год = $210K/год

Хранение:
  • PostgreSQL для персистентных данных (заказы, платежи)
  • S3 для ассетов (фото товаров)
  • Итого: $100K/год

Команда:
  • 2 DevOps инженера: $300K/год
  
Итого: $610K/год

Экономия: $2.75M - $610K = $2.14M/годGo

Результаты после миграции

Параметр До MEMORIA После MEMORIA Эффект
Задержка чтения товара 1-5 ms 0.35 ns ×10 000
Задержка обновления цены 5-20 ms 0.94 ns ×20 000
Оверселлинг 2-3% заказов 0% -100%
Рассинхронизация цен 5-15 минут 0 (real-time) -100%
Оформление заказа 100-500 ms 5 ns ×100 000
Серверы 46 нод 7 серверов -85%
Команда 10 человек 2 человека -80%
TCO/год $2.75M $610K -78%
Потери от оверселлинга $15M/год $0 -$15M
Потери от устаревших цен $50M/год $0 -$50M
Общая экономия/год $67M
Влияние на бизнес-метрики

После миграции на MEMORIA:
Конверсия: +8% (меньше отмен заказов)
Retention: +12% (лучше опыт покупателей)
GMV: +15% (актуальные цены = больше продаж)
Infrastructure cost: -$2.14M/год
Оверселлинг: -$15M/год
Устаревшие цены: -$50M/год
Итого эффект: +$67M/год

Ограничения

Ограничение 1: Поиск товаров

Ограничение 2: Персистентность

Ограничение 3: Аналитика

Ограничение 4: Фото и контент

Важно

MEMORIA не заменяет всю инфраструктуру маркетплейса. Она заменяет критическое ядро — цены, остатки, корзины, заказы. Поиск, аналитика, контент, платежи остаются на своих местах и интегрируются с MEMORIA через API.

Экономический эффект

Сравнение TCO за 3 года

Статья расходов PostgreSQL + Redis MongoDB + Kafka MEMORIA
Лицензии/облако $2.5M $3.5M $500K
Серверы $1.5M $2M $630K
Команда (3 года) $4.5M $5M $900K
Потери от оверселлинга $45M $30M $0
Потери от устаревших цен $150M $100M $0
Итого за 3 года $203M $140.5M $2.03M

Дополнительная выручка от улучшения метрик

Влияние на бизнес-метрики: Текущие метрики (до MEMORIA): • GMV: $1B/год • Конверсия: 3% • Retention: 40% • Оверселлинг: 2.5% заказов • Infrastructure cost: $2.75M/год • Потери от оверселлинга: $15M/год • Потери от устаревших цен: $50M/год После MEMORIA: • GMV: $1.15B/год (+15% от актуальных цен) • Конверсия: 3.24% (+8% от меньшего числа отмен) • Retention: 44.8% (+12% от лучшего опыта) • Оверселлинг: 0% • Infrastructure cost: $610K/год (-78%) • Потери от оверселлинга: $0 • Потери от устаревших цен: $0 Дополнительная выручка: $150M/год Экономия на инфраструктуре: $2.14M/год Устранение потерь: $65M/год Итого эффект: $217M/год Стоимость миграции: $2M ROI: 10 750%

Выводы

MEMORIA предлагает революционное решение для маркетплейсов:

  1. Масштаб — 100 миллионов товаров на 7 серверах
  2. Производительность — 0.35 ns чтение, 0.94 ns запись
  3. Zero оверселлинг — атомарные операции без блокировок
  4. Real-time цены — мгновенное обновление без рассинхронизации
  5. Экономика — 78% экономии на инфраструктуре + $65M/год устранение потерь
Стратегическая рекомендация

Для маркетплейсов с 10M+ товаров переход на MEMORIA — это не просто оптимизация инфраструктуры. Это конкурентное преимущество: zero оверселлинг, актуальные цены, лучший опыт покупателей. Те, кто внедрит MEMORIA сегодня, получат преимущество на годы вперёд. Те, кто продолжит использовать PostgreSQL/Redis — будут терять миллиарды на оверселлинге и устаревших ценах.

В следующей статье мы разберём, как MEMORIA применяется для телеком-операторов — биллинг 50 миллионов абонентов в реальном времени.