Как хранить историю изменений цен акций в базе данных
Что проверяют
Умеете ли вы проектировать временной ряд: запись только добавлением, быстрые выборки за период и рост объёма без деградации. Следом обычно спрашивают, что будет через год, когда строк станет миллиард, и как отдать дневные свечи.
Как отвечать
Начните с уточнения: нужны все сделки или только цены закрытия, сколько инструментов и как долго хранить. От этого зависит, хватит ли одной таблицы в PostgreSQL.
Базовая схема:
- таблица цен:
instrument_id,ts timestamptz,price,volume, первичный ключ(instrument_id, ts). Составной ключ сразу закрывает главный запрос «история бумаги за период»; - тип цены:
numericили целое в минимальных единицах.floatдля денег не годится, и в Go тоже:float64копит ошибку округления, берутint64с фиксированным масштабом илиshopspring/decimal; - время: всегда
timestamptzв UTC, часовой пояс биржи это вопрос отображения; - только INSERT: строки не обновляются, история и есть набор строк;
- партиционирование: декларативное по диапазону времени, по месяцу или дню. Старые данные уходят через
DROPпартиции, а неDELETEна миллионы строк; - индекс BRIN по
tsна больших партициях: весит килобайты и хорошо работает, потому что данные ложатся по порядку времени.
При большом объёме назовите специализированные варианты: расширение TimescaleDB с гипертаблицами и непрерывными агрегатами для свечей или ClickHouse с MergeTree и ORDER BY (instrument_id, ts).
Со стороны Go: котировки пишутся пачками, а не отдельным запросом на каждую. В pgx для этого есть CopyFrom и SendBatch, пачка отправляется по размеру или по таймеру.
Где спотыкаются
Кладут цену в DECIMAL(10,2). У части инструментов больше двух знаков после запятой, и точность теряется молча.
Вешают два отдельных индекса на тикер и на время вместо составного. Планировщик может совместить их через bitmap, но это дороже одного индекса, который сразу отдаёт строки в порядке времени.
Хранят только текущую цену и обновляют её через UPDATE, а историю потом пытаются восстановить из логов. Если нужна история, каждое изменение это новая строка со своим моментом времени.