Продолжая поиски баз данных, наиболее подходящих для хранения истории цен, получил комментарий от Романа Щеголихина. Роман был любезен и подготовил инструкцию по установке и настройке БД 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 ищем здесь. Качаем и ставим:
[code bash]
1 2 3 4 5 |
$ 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 |
[/code]
проверяем:
[code bash]
1 2 3 4 |
$ 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) |
[/code]
Вроде бы ОК. Теперь JRE установлен в каталог /usr/java/jre1.8.0_144 и надо задать переменную окружения JAVA_HOME, желательно для всех пользователей:
[code bash]
1 |
$ sudo sh -c "echo export JAVA_HOME=/usr/java/jre1.8.0_144 >> /etc/environment" |
[/code]
Для надежности на этом месте можно перезагрузиться и посмотреть, что выдает команда env:
[code bash]
1 2 3 4 5 6 |
$ env ... PWD=/root JAVA_HOME=/usr/java/jre1.8.0_144 LANG=ru_RU.UTF-8 ... |
[/code]
[raaAdSenceArticle]
👁️Установим Cassandra
Отлично! Можно ставить Casandra. Настроим репозиторий:
[code bash]
1 2 3 4 5 6 7 8 9 |
$ 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 ... |
[/code]
Пробуем запустить:
[code bash]
1 2 3 4 5 6 |
$ /etc/init.d/cassandra start Reloading systemd: [ OK ] Starting cassandra (via systemctl): [ OK ] $ systemctl status cassandra ... Starting Cassandra: OK |
[/code]
Остановим:
[code bash]
1 |
$ /etc/init.d/cassandra stop |
[/code]
Пока можно на этом остановиться, но скажу заранее, что возможно, потребуется настройка самой кассандры, которая производится в файле:[code]/etc/cassandra/conf/cassandra.yaml[/code]
Для автоматического запуска кассандры при старте системы выполните команду: [code]systemctl enable cassandra.service[/code]
Команда nodetool показывает состояние вашего узла Cassandra:
[code bash]
1 2 3 |
$ nodetool status Datacenter: datacenter1 ... |
[/code]
Для подключения к консоли установленной Cassandra из терминала используйте команду cqlsh:
[code bash]
1 2 3 4 |
$ cqlsh Connected to Test Cluster at 127.0.0.1:9042. ... cqlsh> |
[/code]
Дальше будем работат в консоли cqlsh. Создадим базу данных и создадим одну таблицу в ней (параметры берем стандартные):
[code sql]
1 |
cqlsh> CREATE KEYSPACE rates WITH replication = {'class':'SimpleStrategy', 'replication_factor' : 3}; |
[/code]
Для дальнейшей работы вводим:
[code sql]
1 2 |
cqlsh> use rates; cqlsh:rates> |
[/code]
Создадим таблицу для хранения котировок:
[code sql]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
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); |
[/code]
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 (сортировка по возрастанию).
Вставим несколько строчек данных:
[code sql]
1 2 3 4 5 6 |
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); |
[/code]
Поверьте, очень круто, что у NoSQL-базы данных имеется SQL-образный язык запросов! Он называется cql.
Сделаем выборку:
[code sql]
1 2 3 4 5 6 |
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 |
[/code]
С условием:
[code sql]
1 2 3 4 5 |
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 |
[/code]
🐍Подключаемся из Python 2.7
Чтобы наш питон научился работать с Cassandra, надо установить соответствующий драйвер: [code]pip install cassandra-driver[/code]
Возможно, Вам сначала потребуется установить сам pip:
[code bash]
1 2 3 |
$ wget https://bootstrap.pypa.io/get-pip.py $ python get-pip.py $ pip -V # проверить версию установленного pip |
[/code]
Для начала проверяем подключение к кластеру.
Внимание! Пример на Python 2.7!
Создаем вот такой файл [code]vi test_cas.py[/code]:
[code python]
1 2 3 4 5 6 7 8 |
# 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" |
[/code]
Запускаем:
[code bash]
1 2 |
$ python test_cas.py cassandra connect ok |
[/code]
Если не получено ошибок, значит всё работает.
Ну и делаем выборку:
[code python]
1 2 3 4 5 6 7 8 9 10 |
# -*- 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 |
[/code]
Запускаем:
[code bash]
1 2 3 4 |
$ python test_cas.py cassandra connect ok Row(datetime=1202169600, close=27.170000076293945) Row(datetime=1202256000, close=27.030000686645508) |
[/code]
🏁Заключение
Всё, что написано выше — учебный, но вполне реальный пример. Cassandra может на обычной машине хранить и обрабатывать сотни миллионов и даже миллиарды строк без потери в скорости записи(!).
Для реального использования необходимо разобраться в настройках Cassandra и, как минимум, изучить пакетную вставку данных (BATCH).
Python имеет встроенную поддержку Cassandra, что позволяет легко получить доступ из своих скриптов к мощнейшему движку NoSQL-баз.
Cassandra также имеет поддержку Php, C++ в том числе под Windows.
Роман Щеголихин
roman.shchegolikhin@bk.ru
💬В комментариях напишите, полезен ли вам материал или поблагодарите Романа. Что вы можете порекомендовать? Может видите где-то ошибку?