Разработка системы хранения order book snapshots

Проектируем и разрабатываем блокчейн-решения полного цикла: от архитектуры смарт-контрактов до запуска DeFi-протоколов, NFT-маркетплейсов и криптобирж. Аудит безопасности, токеномика, интеграция с существующей инфраструктурой.
Показано 1 из 1Все 1305 услуг
Разработка системы хранения order book snapshots
Сложный
~5 дней
Часто задаваемые вопросы

Направления блокчейн-разработки

Этапы блокчейн-разработки

Последние работы

  • image_website-b2b-advance_0.webp
    Разработка сайта компании B2B ADVANCE
    1361
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1251
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    957
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1189
  • image_logo-advance_0.webp
    Разработка логотипа компании B2B Advance
    646
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    930

Мы — команда Web3-инженеров с 10+ летним опытом разработки криптоинфраструктуры. Нами реализовано более 20 проектов для бирж и трейдинговых фирм, включая системы хранения order book с нагрузкой до 5000 обновлений в секунду. В криптобиржах order book — один из самых нагруженных источников данных. Трейдеры требуют низкую задержку, а аналитики — полную историю. Неправильное хранение приводит к огромным затратам на инфраструктуру.

Order book — самые информативные и самые сложные в хранении биржевые данные. Полный стакан BTC/USDT на Binance содержит 5000 уровней с обеих сторон, обновляется 5–10 раз в секунду и генерирует сотни мегабайт данных в час. При наивном подходе (хранение каждого среза) объём достигает 100 ГБ в сутки только для одного символа. Правильная система балансирует полноту данных с практическими ограничениями. Наше решение использует комбинацию полных снапшотов и дельт (diff), что даёт сжатие в 50 раз без потери разрешения.

Какой формат хранения order book выбрать?

Прежде чем проектировать хранилище, важно понять, какие данные реально нужны. В таблице ниже — сравнение основных форматов.

Тип данных Размер (на одно обновление) Частота записи Использование
Full snapshot 8–15 KB 1 раз в минуту Восстановление состояния, бэкапы
Depth snapshot (20 уровней) 200–500 байт 1–5 раз в секунду Торговые стратегии, визуализация
Order book diff 150–300 байт каждое обновление Секундное разрешение между снапшотами
Mid-price + spread 40 байт каждое обновление Долгосрочный анализ, мониторинг

На практике системы хранят комбинацию: полные снапшоты для восстановления и дельты для исторической точности.

Формат хранения: Delta Encoding

Delta encoding — критичный элемент для уменьшения объёмов. Вместо полного стакана мы сохраняем только изменения относительно предыдущего состояния.

Snapshot @ T=0:
  bids: [(43250.0, 1.5), (43249.5, 2.0), (43249.0, 0.8)]
  asks: [(43251.0, 1.2), (43251.5, 3.0), (43252.0, 0.5)]

Diff @ T=1 (только изменения):
  bids_updated: [(43250.0, 2.1)]    # объём изменился
  bids_removed: [(43249.5, 0)]      # уровень исчез
  bids_added:   [(43248.5, 1.0)]    # новый уровень
  asks_updated: []
  asks_removed: []
  asks_added:   [(43251.75, 0.3)]

Полный снапшот: ~8 KB. Diff: ~200 байт. При 5 обновлениях в секунду и снапшоте раз в 60 секунд — 300 diffs + 1 snapshot = ~60 KB/мин вместо 3 MB/мин. Выигрыш — 50 раз.

Почему ClickHouse — оптимальный выбор?

Используем ClickHouse с custom serialization. Колоночное хранение и поддержка массивов кортежей идеально подходят для структуры стакана. ZSTD-сжатие даёт дополнительное сокращение объёма. Согласно документации ClickHouse, колоночное хранение и ZSTD могут сжимать числовые данные в 2-3 раза эффективнее LZ4.

CREATE TABLE orderbook_snapshots (
    exchange     LowCardinality(String),
    symbol       LowCardinality(String),
    snapshot_time DateTime64(3, 'UTC'),
    depth        UInt16,
    bids         Array(Tuple(Decimal(24,8), Decimal(24,8))),
    asks         Array(Tuple(Decimal(24,8), Decimal(24,8)))
)
ENGINE = MergeTree()
PARTITION BY (exchange, toYYYYMM(snapshot_time))
ORDER BY (exchange, symbol, snapshot_time);

CREATE TABLE orderbook_diffs (
    exchange      LowCardinality(String),
    symbol        LowCardinality(String),
    diff_time     DateTime64(3, 'UTC'),
    first_update_id UInt64,
    last_update_id  UInt64,
    bids_changes  Array(Tuple(Decimal(24,8), Decimal(24,8))),
    asks_changes  Array(Tuple(Decimal(24,8), Decimal(24,8)))
)
ENGINE = MergeTree()
PARTITION BY (exchange, toYYYYMM(diff_time))
ORDER BY (exchange, symbol, diff_time);

CREATE TABLE orderbook_metrics (
    exchange   LowCardinality(String),
    symbol     LowCardinality(String),
    ts         DateTime64(3, 'UTC'),
    mid_price  Decimal(24,8),
    spread     Decimal(24,8),
    spread_bps Decimal(10,4),
    bid_1      Decimal(24,8),
    ask_1      Decimal(24,8),
    bid_vol_10 Decimal(24,8),
    ask_vol_10 Decimal(24,8),
    imbalance  Decimal(10,6)
)
ENGINE = MergeTree()
PARTITION BY (exchange, toYYYYMM(ts))
ORDER BY (exchange, symbol, ts)
SETTINGS default_codec = ZSTD(3);

Восстановление состояния стакана

Ключевая операция — восстановление стакана на произвольный момент времени. Реализуется через последовательное применение дельт от последнего снапшота.

class OrderBookReplay:
    def __init__(self, storage: OrderBookStorage):
        self.storage = storage

    async def reconstruct_at(self, exchange: str, symbol: str, target_ts: int) -> OrderBook:
        snapshot = await self.storage.get_last_snapshot_before(exchange, symbol, target_ts)
        if not snapshot:
            raise ValueError("No snapshot available before target timestamp")
        diffs = await self.storage.get_diffs(exchange, symbol, from_ts=snapshot.timestamp, to_ts=target_ts)
        book = OrderBook.from_snapshot(snapshot)
        for diff in diffs:
            book.apply_diff(diff)
        return book

class OrderBook:
    def apply_diff(self, diff: OrderBookDiff):
        for price, qty in diff.bids_changes:
            if qty == 0:
                self.bids.pop(price, None)
            else:
                self.bids[price] = qty
        for price, qty in diff.asks_changes:
            if qty == 0:
                self.asks.pop(price, None)
            else:
                self.asks[price] = qty

Важен порядок применения дельт и проверка через update_id — у Binance каждый diff имеет lastUpdateId, следующий должен начинаться с lastUpdateId+1. Разрыв означает пропущенные данные.

Сжатие и оптимизация

Перед записью в ClickHouse применяем:

  • Delta encoding для цен: храним разницу от лучшего bid/ask в базисных пунктах (bps). Целые числа сжимаются лучше.
  • Binary serialization: Protocol Buffers или MessagePack вместо JSON. Выигрыш 3–5x по размеру и скорости.
  • ClickHouse compression: алгоритм ZSTD(3) для данных Decimal и Float — на 20% эффективнее дефолтного LZ4.

Потоковая запись

Ingestion pipeline работает параллельно: снапшоты каждые 60 секунд, дельты буферизируются и сохраняются батчами по 100 штук.

class OrderBookIngester:
    SNAPSHOT_INTERVAL = 60
    DIFF_BATCH_SIZE = 100

    def __init__(self, storage):
        self.storage = storage
        self.diff_buffer = []
        self.last_snapshot_time = 0

    async def on_orderbook_update(self, book: OrderBook, diff: OrderBookDiff):
        now = time.time()
        if now - self.last_snapshot_time >= self.SNAPSHOT_INTERVAL:
            await self.storage.save_snapshot(book.to_snapshot())
            self.last_snapshot_time = now
        self.diff_buffer.append(diff)
        if len(self.diff_buffer) >= self.DIFF_BATCH_SIZE:
            await self.storage.save_diffs(self.diff_buffer)
            self.diff_buffer.clear()

Аналитические запросы

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

-- Средний спред BTC/USDT по часам за выбранный месяц
SELECT
    toStartOfHour(ts) AS hour,
    avg(spread_bps) AS avg_spread_bps,
    avg(imbalance) AS avg_imbalance
FROM orderbook_metrics
WHERE exchange = 'binance'
  AND symbol = 'BTC/USDT'
  AND ts BETWEEN '2024-01-01' AND '2024-02-01'
GROUP BY hour
ORDER BY hour;

-- Корреляция imbalance с последующим движением цены
WITH book AS (
    SELECT ts, imbalance, mid_price
    FROM orderbook_metrics
    WHERE exchange = 'binance' AND symbol = 'BTC/USDT'
),
future AS (
    SELECT
        b.ts,
        b.imbalance,
        (f.mid_price - b.mid_price) / b.mid_price * 10000 AS fwd_return_bps
    FROM book b
    ASOF JOIN book f ON b.symbol = f.symbol
        AND f.ts BETWEEN b.ts + INTERVAL 1 MINUTE AND b.ts + INTERVAL 2 MINUTE
)
SELECT
    round(imbalance, 1) AS imbalance_bucket,
    avg(fwd_return_bps) AS avg_1min_return_bps,
    count() AS count
FROM future
GROUP BY imbalance_bucket
ORDER BY imbalance_bucket;

Мониторинг и качество данных

Критически важно отслеживать разрывы в последовательностях дельт. Система валидации сравнивает lastUpdateId каждого diff с firstUpdateId следующего и алертит при пробелах. Gap между снапшотами делает восстановление невозможным.

Метрики для мониторинга: частота записи снапшотов на символ, задержка от биржевого timestamp до записи в ClickHouse, размер буфера дельт, процент пропущенных обновлений.

Чек-лист проверки качества данных - Проверить последовательность update_id в диффах - Убедиться, что интервал снапшотов не превышает 60 секунд - Мониторить задержку записи (должна быть < 1 секунды) - Регулярно восстанавливать стакан тестового символа и сравнивать с последним снапшотом

Процесс работы

Этап Длительность Результат
Анализ требований 2-3 дня Техническое задание, прототип схемы
Проектирование схемы 3-5 дней ER-диаграмма, выбор инструментов
Реализация pipeline 5-10 дней Работающий ingestion, тесты
Разработка API 3-5 дней Документация, примеры запросов
Мониторинг и отладка 2-3 дня Дашборды, алерты
Документация и обучение 1-2 дня README, инструкции

Ориентировочные сроки — от 2 до 4 недель в зависимости от сложности. Стоимость рассчитывается индивидуально после ознакомления с задачей.

Что входит в работу

  • Проектирование схемы хранения под вашу нагрузку (частота обновлений, количество символов, требования к задержке).
  • Реализация ingestion pipeline на Python с интеграцией через WebSocket или REST API.
  • Разработка API для доступа к историческим данным (восстановление стакана, выборка дельт, агрегаты).
  • Документация по восстановлению и аналитическим запросам.
  • Обучение команды.
  • Поддержка в течение месяца после запуска.

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

Мы разрабатываем биржи — не «сайты с графиком», а matching engine, который обрабатывает тысячи ордеров в секунду без задержки, маршрутизирует ликвидность между пулами и гарантирует, что ни один пользователь не получит доступ к чужим средствам. Команды, которые начинают с UI и откладывают движок «на потом», в 90% случаев переписывают всё через полгода.

Какие проблемы решает правильная архитектура?

Order Book vs AMM: где ломается большинство проектов

Централизованные биржи (CEX) строятся вокруг order book + matching engine. Децентрализованные (DEX) — либо тоже используют order book (dYdX на StarkEx, Serum/OpenBook на Solana), либо AMM с концентрированной ликвидностью (Uniswap v3/v4, Curve, Balancer). Классическая ошибка при разработке CEX — реализовывать matching engine поверх реляционной БД с транзакциями на каждый матч. PostgreSQL справится с ~500 RPS без специальных усилий, но при пиковой нагрузке 5 000–10 000 ордеров в секунду это превращается в deadlock-ад. Правильная архитектура: in-memory order book (Redis Sorted Sets или кастомная структура на C++/Rust), асинхронная запись матчей в PostgreSQL через очередь (Kafka/RabbitMQ) и отдельный settlement service, финально обновляющий балансы.

Для DEX самая болезненная проблема — sandwich атаки и MEV. Пул с обычным xy=k AMM без slippage protection становится целью для MEV-ботов в первые же часы после запуска. Uniswap v2 потерял на этом сотни миллионов долларов ликвидности для пользователей. Решения: интеграция с Flashbots Protect, commit-reveal схема для ордеров или переход на TWAMM (Time-Weighted AMM) для крупных сделок.

Концентрированная ликвидность и impermanent loss

Uniswap v3 ввёл концентрированную ликвидность — LP выбирают ценовой диапазон, в котором предоставляют ликвидность. Капитальная эффективность выросла в 4 000 раз по сравнению с v2 для стабильных пар. Но реализовать этот механизм правильно — нетривиальная задача. Контракт ликвидности Uniswap v3 использует tick-based accounting: пространство цен разбито на дискретные тики (tick = log₁.0001(price)), каждый тик хранит накопленные fee growth и liquidity delta. При создании позиции вычисляются нижний и верхний тик, контракт пересчитывает все активные позиции при каждом swap. Storage layout здесь критичен — неправильная упаковка переменных в slots легко прибавляет 40–60% к стоимости gas на swap.

Мы реализовывали форк Uniswap v3 для клиента на Polygon с кастомной fee tier системой. Первоначальная версия тратила 180k gas на swap через 2 тика. После slot packing переменных в Tick.Info и инлайнинга нескольких internal вызовов — 112k gas. Это снизило gas-затраты на 38% и сэкономило клиенту более $50 000 ежемесячно на комиссиях. Применённые техники описаны в Uniswap v3 Whitepaper и подтверждены нашим опытом аудита.

Что такое matching engine и почему он критичен?

Production-ready matching engine строится по следующей схеме:

  • Order ingestion layer — WebSocket gateway (Go или Rust), принимает ордера, валидирует подпись, проверяет баланс через Redis, ставит в очередь. Latency на этом уровне должна быть <1ms.
  • Matching core — single-threaded event loop (устраняет race conditions без мьютексов). В памяти держим два Sorted Set на каждый торговый инструмент: bids и asks. FIFO matching для limit ордеров, immediate-or-cancel для маркет. Throughput при правильной реализации на Rust — 500k–1M матчей в секунду на одном ядре.
  • Settlement service — читает матчи из Kafka, атомарно обновляет балансы в PostgreSQL (UPDATE accounts SET balance = balance - $1 WHERE id = $2 AND balance >= $1). Optimistic locking через версионирование строк.
  • Withdrawal pipeline — отдельный сервис с cold/hot wallet архитектурой. Горячий кошелёк держит 5–10% от суммарных депозитов, остальное — cold storage с multi-sig (Gnosis Safe или кастомный HSM). Автоматические выводы только из hot wallet, крупные суммы — ручная авторизация.
Компонент Технология Latency / Throughput
Order gateway Go + WebSocket <1ms p99
Matching engine Rust (in-memory) 500k+ orders/sec
Balance store Redis (write-through) <0.5ms
Settlement DB PostgreSQL 14+ ~50k TPS с partitioning
Event streaming Apache Kafka 1M+ events/sec
Blockchain node Geth / Solana validator зависит от чейна

Как мы строим on-chain DEX: смарт-контракты и gas-оптимизация

Для DEX на EVM (Ethereum, Arbitrum, Optimism, Polygon) весь критический путь живёт в Solidity. Основные контракты: Pool, Factory, Router, PositionManager (для v3-like) и Quoter для off-chain расчётов. Типичные ошибки, которые мы видим в аудитах:

Reentrancy через callback. Uniswap v3 использует flash swap с callback (uniswapV3SwapCallback). Если в вашем роутере нет nonReentrant guard и вы не проверяете msg.sender == pool, контракт дренируется через вложенный вызов. Это не гипотетика — несколько форков v3 теряли средства именно так.

Oracle manipulation в AMM. Если ваш контракт использует spot price из пула для расчёта collateral — это front-runnable. Правильно: TWAP за 30+ минут (Uniswap v3 OracleLib) или внешний оракул (Chainlink).

Unbounded loops в liquidity range. Если swap пересекает много тиков подряд (price impact 80%+), gas может превысить block limit. Нужен MAX_TICKS_CROSSED с partial fill и возвратом остатка.

Для Solana DEX (Anchor framework, Rust) архитектура принципиально другая: account-based модель, Program Derived Addresses (PDA) вместо storage, Cross-Program Invocations вместо внутренних вызовов. Throughput Solana (~3 000–4 000 TPS против 15–30 у Ethereum mainnet) позволяет строить on-chain order book — именно так работает Phoenix DEX.

Liquidity bootstrapping и интеграция с агрегаторами

Запустить пул мало — нужно обеспечить ликвидность на старте. Практические механизмы:

  • Liquidity Bootstrapping Pool (LBP) — начальная цена высокая, весовые коэффициенты активов динамически смещаются, создавая давление продаж и равномерное распределение токена. Реализован в Balancer v2.
  • Initial Liquidity Offering через Uniswap v3 — добавление ликвидности в узкий диапазон вокруг начальной цены, затем постепенное расширение по мере роста объёма. Требует active liquidity management или интеграции с Arrakis/Gamma.
  • Интеграция с 1inch, Paraswap, Li.Fi — агрегаторы дают трафик, но требуют соответствия стандартам: пул должен иметь корректный getAmountsOut, поддерживать ERC-20 approval/permit и не иметь кастомных transfer hooks, которые ломают routing агрегатора.

Процесс разработки

Аналитика и проектирование начинаются с выбора архитектурной модели: CEX с кастодиальным хранением, non-custodial DEX или гибрид (off-chain order book + on-chain settlement, как dYdX v3). Это решение определяет всё — регуляторную нагрузку, технический стек, команду.

Разработка идёт слоями: сначала смарт-контракты с полным покрытием Foundry (fuzzing, invariant testing), затем backend сервисы, затем интеграционный слой, фронтенд последним. Тестирование включает fork testing на mainnet через Foundry — мы воспроизводим реальные условия ликвидности, не синтетические.

Аудит обязателен перед деплоем на mainnet. Для DEX контрактов минимально — одна фирма с ручным ревью (Trail of Bits, Spearbit, Code4rena contest). Для CEX custody — аудит процессов хранения ключей. Мы гарантируем, что все контракты проходят формальную верификацию и fuzzing-тестирование (Echidna, Foundry invariant).

Что входит в работу (deliverables)

По завершении проекта вы получаете:

  • Исходный код смарт-контрактов и backend-сервисов под вашу лицензию
  • Полную техническую документацию (архитектурные схемы, API-спецификации, инструкции по деплою)
  • Доступы к репозиторию и CI/CD pipeline
  • Обучение вашей команды работе с кодом (2–3 сессии)
  • Гарантию на найденные в процессе эксплуатации баги до 6 месяцев
  • Сертификат прохождения стороннего аудита безопасности

Ориентиры по срокам

  • DEX (AMM, xy=k) — от 3 до 5 месяцев: контракты + backend + UI
  • DEX с концентрированной ликвидностью (v3-like) — от 6 до 10 месяцев
  • CEX (matching engine + custody + торговый UI) — от 8 до 14 месяцев
  • Интеграция с существующим протоколом — от 4 до 8 недель

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

Типичные грабли при запуске

  • Забывают про price oracle в AMM. Spot price манипулируется flash loan’ом за одну транзакцию. Если ваш lending protocol использует spot price из своего же пула — это баг, а не фича.
  • Горячий кошелёк без лимитов. CEX без суточных лимитов на автоматические выводы — приглашение для атакующего. Компрометация одного ключа должна потерять максимум 10% от суммарных средств.
  • Отсутствие circuit breaker. Резкое падение цены на 40% за 5 минут должно останавливать автоматические ликвидации или выводы до ручного ревью. Без этого cascading liquidation spiral уничтожает весь TVL.
  • Неправильный decimal handling. USDC использует 6 decimals, WBTC — 8, большинство токенов — 18. Смешивание без нормализации даёт либо потерю точности, либо overflow. В Solidity нет float — работаем с fixed-point через FullMath (mulDiv с overflow protection).

Хотите избежать этих проблем? Свяжитесь с нами для консультации — мы подберём архитектуру под ваш проект и назовём точные сроки. Закажите разработку биржи с гарантией качества и последующей поддержкой.