Настройка Cassandra для хранения цен акций

Продолжая поиски баз данных, наиболее подходящих для хранения истории цен, получил комментарий от Романа Щеголихина. Роман был любезен и подготовил инструкцию по установке и настройке БД Cassandra и несколько примеров использования на Python 2.7🐍.

Cassandra👁️ — это NoSQL база данных, предназначенная для распределенного хранения огромных массивов данных. Отлично подходит для хранения времянных рядов.

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

  • open — цена открытия данного таймфрейма (ТФ);
  • high — максимальная цена ТФ;
  • low — минимальная цена ТФ;
  • close — цена закрытия ТФ;
  • vol — объём внутри ТФ.

Таким образом, требуется хранить следующие данные (и уметь по ним делать выборки):

  • инструмент, точнее, уникальный идентификатор инструмента;
  • таймфрейм (ТФ) — цены обычно группируются по следующим интервалам (таймфреймам): 1 мин, 5 мин, 10 мин, 15 мин, 30 мин, 1 час и 1 день. Существуют и другие таймфреймы.
  • отметка даты и времени.

Также мы можем хранить объем сделок для каждого интервала.

В целом, получается одна большая таблица. Но насколько большая таблица? И какую выбрать для неё базу данных?

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

💽Выбор базы данных

По результатам сравнения с MySQL, PostgreSQL и Tarantool (https://tarantool.org/ru/) для хранения основных данных была выбрана БД Cassandra. Среди них только Cassandra сохранила стабильность скорости записи при увеличении количества операций. Tarantool работал быстрее всех, но когда база данных перестала умещаться в памяти — показал резкую просадку по скорости. Возможно, я не до конца разобрался с tarantool, но учитывая, что Cassandra поддерживается и развивается командой Apache, а также, что Cassandra имеет более дружественный язык запросов CQL, я выбрал именно её для хранения исходных данных.

⚠️Перед тем как начать

Если Вы до сих пор ничего не знаете про NoSQL-базы данных, то рекомендую погуглить и, в частности, почитать как хранит данные сама Cassandra. Это будет полезно для общего развития и для будущего проектирования структуры таблиц. Полезно, но необязательно. Я дам свою структуру, но данные хранить можно по-разному.

Вообще, Cassandra — это монстр, разработанный для хранения и обработки действительно большого количества данных. Cassandra была создана в недрах Facebook и позже передана Apache Foundation. В документации и в примерах часто идет описание, как это всё делать для целого кластера (сеть из нескольких компьютеров). Тем не менее, база данных хорошо работает и на одной машине в кластере из одной ноды. Под Linux на настройках по умолчанию она любит сожрать всю память (что регулируется в настройках), под Windows она намного более демократична в этом вопросе.

💾Ставим Cassandra на Centos 7

С ходу пара полезных ссылок:

https://www.datastax.com/ — компания, которая кроме прочего занимается продвижением проекта Apache Cassandra. Здесь есть много документации, особенно примеров по использованию Cassandra. Полезно зарегистрироваться, чтобы получать рассылку, ну и скачать отсюда тоже можно много интересного.

http://cassandra.apache.org/ — собственно сама Cassandra. Здесь тоже можно скачать Кассандру. Например, сейчас последняя версия 3.11.0. Я опишу установку версии 3.9.0.

Я лично привык устанавливать различный софт под centos через менеджер пакетов yum. Поэтому его и будем рассматривать. Конечно, можно попробовать собрать кассандру и из исходников, но это для фанатов. Нам же нужно получить гарантированно рабочий инструмент для хранения данных.

Cassandra написана и работает под java. Поэтому перед установкой базы данных, нам надо установить саму java. Если не требуется вести разработку на java, то должно хватить JRE. Если нет, то надо ставить JDK.

☕Установим Oracle Java 8 JRE

Здесь и далее я имею в виду, что у нас есть доступ к более-менее мощной машине под Centos 7 и что логинимся мы под root-ом (я поэтому не всегда пользуюсь sudo). Я тестировал всё на реальном сервере Xeon 3.4GHz с 16Gb памяти и SSD-диском. Использование SSD крайне рекомендовано. В идеале взять для кассандры два SSD (для журнала транзакций и для данных), но у меня всё отлично работало и на одном SSD.

Нужную (самую свежую) версию JRE ищем здесь. Качаем и ставим:

$ cd ~
$ wget --no-cookies --no-check-certificate --header \
  "Cookie: gpw_e24=http%3A%2F%2Fwww.oracle.com%2F; oraclelicense=accept-securebackup-cookie" \
  download.oracle.com/otn-pub/java/jdk/8u144-b01/090f390dda5b47b9b721c7dfaa008135/jre-8u144-linux-x64.rpm
$ sudo yum localinstall jre-8u144-linux-x64.rpm

проверяем:

$ java -version
java version "1.8.0_144"
Java(TM) SE Runtime Environment (build 1.8.0_144-b01)
Java HotSpot(TM) 64-Bit Server VM (build 25.144-b01, mixed mode)

Вроде бы ОК. Теперь JRE установлен в каталог /usr/java/jre1.8.0_144 и надо задать переменную окружения JAVA_HOME, желательно для всех пользователей:

$ sudo sh -c "echo export JAVA_HOME=/usr/java/jre1.8.0_144 >> /etc/environment"

Для надежности на этом месте можно перезагрузиться и посмотреть, что выдает команда env:

$ env
  ...
  PWD=/root
  JAVA_HOME=/usr/java/jre1.8.0_144
  LANG=ru_RU.UTF-8
  ...

👁️Установим Cassandra

Отлично! Можно ставить Casandra. Настроим репозиторий:

$ vi /etc/yum.repos.d/datastax.repo
  [datastax]
  name = DataStax Repo for Apache Cassandra
  baseurl = http://rpm.datastax.com/datastax-ddc/3.9
  enabled = 1
  gpgcheck = 0

$ yum -y install datastax-ddc
  ...

Пробуем запустить:

$ /etc/init.d/cassandra start
  Reloading systemd:                                         [  OK  ]
  Starting cassandra (via systemctl):                        [  OK  ]
$ systemctl status cassandra
  ...
  Starting Cassandra: OK

Остановим:

$ /etc/init.d/cassandra stop

Пока можно на этом остановиться, но скажу заранее, что возможно, потребуется настройка самой кассандры, которая производится в файле:/etc/cassandra/conf/cassandra.yaml

Для автоматического запуска кассандры при старте системы выполните команду: systemctl enable cassandra.service

Команда nodetool показывает состояние вашего узла Cassandra:

$ nodetool status
  Datacenter: datacenter1
  ...

Для подключения к консоли установленной Cassandra из терминала используйте команду cqlsh:

$ cqlsh
  Connected to Test Cluster at 127.0.0.1:9042.
  ...
cqlsh>

Дальше будем работат в консоли cqlsh. Создадим базу данных и создадим одну таблицу в ней (параметры берем стандартные):

cqlsh> CREATE KEYSPACE rates WITH replication = {'class':'SimpleStrategy', 'replication_factor' : 3};

Для дальнейшей работы вводим:

cqlsh> use rates;
cqlsh:rates>

Создадим таблицу для хранения котировок:

cqlsh:rates> CREATE TABLE rates.rate (
...     tools_id int,
...     time_frame int,
...     datetime int,
...     close float,
...     high float,
...     low float,
...     open float,
...     vol float,
...     PRIMARY KEY ((tools_id, time_frame), datetime)
... ) WITH CLUSTERING ORDER BY (datetime ASC)
...     AND bloom_filter_fp_chance = 0.01
...     AND caching = {'keys': 'ALL', 'rows_per_partition': 'NONE'}
...     AND comment = ''
...     AND compaction = {'class': 'org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy', 'max_threshold': '32', 'min_threshold': '4'}
...     AND compression = {'chunk_length_in_kb': '64', 'class': 'org.apache.cassandra.io.compress.LZ4Compressor'}
...     AND crc_check_chance = 1.0
...     AND dclocal_read_repair_chance = 0.1
...     AND default_time_to_live = 0
...     AND gc_grace_seconds = 864000
...     AND max_index_interval = 2048
...     AND memtable_flush_period_in_ms = 0
...     AND min_index_interval = 128
...     AND read_repair_chance = 0.0
...     AND speculative_retry = '99PERCENTILE';
cqlsh:rates> CREATE INDEX idx_datetime ON rates.rate (datetime);
cqlsh:rates> CREATE INDEX idx_tools_id ON rates.rate (tools_id);
cqlsh:rates> CREATE INDEX idx_time_frame ON rates.rate (time_frame);

Cassandra поддерживает тип поля DECIMAL. Для хранения котировок рекомендован именно данный тип, для исключения потери точности. RAA.

Где:

  • tools_id – уникальный идентификатор инструмента, включая идентификацию рынка и брокера. Я собираю и храню данные от различных поставщиков. Соответственно, у меня есть промежуточная база данных (MySQL), в которой описаны поставщики данных (например, Yahoo, Quandl, IEX…), рынки и сами инструменты. Например tools_id=3555 у меня означает акции MCRI (Monarch Casino & Resort), взятые с биржи NASDAQ, предоставленные поставщиком Yahoo.Finance. Надеюсь, идея ясна.
  • time_frame – таймфрейм (7 — день, 6 — час, 5 — 30 мин, 4 — 15 мин, 3 — 10 мин, 2 — 5 мин, 1 — 1 мин, 0 — тики). Номера можно использовать любые, это лишь пример.
  • datetime – дата и время в UNIX формате по Гринвичу или UTC.
  • close, high, low, open, vol – соответственно: цена закрытия, максимум, минимум цены, цена открытия и объем.

Первичный ключ – поля (tools_id и time_frame).

Кластерный ключ – поле datetime (сортировка по возрастанию).

Вставим несколько строчек данных:

cqlsh:rates> insert into rate (tools_id, time_frame, datetime, close, high, low, open, vol) 
...   values (3555, 7, 1202083200, 26.98, 27.05, 26.97, 26.99, 7700);
cqlsh:rates> insert into rate (tools_id, time_frame, datetime, close, high, low, open, vol) 
...   values (3555, 7, 1202169600, 27.17, 27.23, 27.12, 27.15, 26700);
cqlsh:rates> insert into rate (tools_id, time_frame, datetime, close, high, low, open, vol) 
...   values (3555, 7, 1202256000,27.03,27.1,27.01,27.09,12600);

Поверьте, очень круто, что у NoSQL-базы данных имеется SQL-образный язык запросов! Он называется cql.

Сделаем выборку:

cqlsh:rates> select * from rate;
 tools_id  | time_frame | datetime   | close | high  | low   | open  | vol
 ----------+------------+------------+-------+-------+-------+-------+-------
 3555      |          7 | 1202083200 | 26.98 | 27.05 | 26.97 | 26.99 |  7700
 3555      |          7 | 1202169600 | 27.17 | 27.23 | 27.12 | 27.15 | 26700
 3555      |          7 | 1202256000 | 27.03 |  27.1 | 27.01 | 27.09 | 12600

С условием:

cqlsh:rates> select * from rate where tools_id=3555 and time_frame=7 and datetime>=1202169600;
 tools_id  | time_frame | datetime   | close | high  | low   | open  | vol
 ----------+------------+------------+-------+-------+-------+-------+-------
 3555      |          7 | 1202169600 | 27.17 | 27.23 | 27.12 | 27.15 | 26700
 3555      |          7 | 1202256000 | 27.03 |  27.1 | 27.01 | 27.09 | 12600

🐍Подключаемся из Python 2.7

Чтобы наш питон научился работать с Cassandra, надо установить соответствующий драйвер: pip install cassandra-driver

Возможно, Вам сначала потребуется установить сам pip:

$ wget https://bootstrap.pypa.io/get-pip.py
$ python get-pip.py
$ pip -V  # проверить версию установленного pip

Для начала проверяем подключение к кластеру.

Внимание! Пример на Python 2.7!

Создаем вот такой файл vi test_cas.py:

# test_cas.py
# Python 2.7 example
# -*- coding: utf-8
# подключение к cassandra
from cassandra.cluster import Cluster
cluster = Cluster(['127.0.0.1'])
session = cluster.connect('rates')
print "cassandra connect ok"

Запускаем:

$ python test_cas.py
  cassandra connect ok

Если не получено ошибок, значит всё работает.

Ну и делаем выборку:

# -*- coding: utf-8
# подключение к cassandra
from cassandra.cluster import Cluster
cluster = Cluster(['127.0.0.1'])
session = cluster.connect('rates')
print "cassandra connect ok"
cql = "SELECT datetime, close FROM rate WHERE tools_id=%d AND time_frame=%d AND datetime >= %d" % (3555, 7, 1202169600)
rows1 = session.execute(cql)
for row1 in rows1:
    print row1

Запускаем:

$ python test_cas.py
  cassandra connect ok
  Row(datetime=1202169600, close=27.170000076293945)
  Row(datetime=1202256000, close=27.030000686645508)

🏁Заключение

Всё, что написано выше — учебный, но вполне реальный пример. Cassandra может на обычной машине хранить и обрабатывать сотни миллионов и даже миллиарды строк без потери в скорости записи(!).

Для реального использования необходимо разобраться в настройках Cassandra и, как минимум, изучить пакетную вставку данных (BATCH).

Python имеет встроенную поддержку Cassandra, что позволяет легко получить доступ из своих скриптов к мощнейшему движку NoSQL-баз.

Cassandra также имеет поддержку Php, C++ в том числе под Windows.

Роман Щеголихин
roman.shchegolikhin@bk.ru

💬В комментариях напишите, полезен ли вам материал или поблагодарите Романа. Что вы можете порекомендовать? Может видите где-то ошибку?

☝Рынок любит лёгкую добычу🐰. Чтобы не стать ей, рекомендую учиться🎓 у профессиональных трейдеров👍.