Поговорим о сохранении данных после обработки.
Варианты действий по завершению анализа:
- Отбросить данные (по результатам анализа)
- Передать данные потоковой платформе
- Сохранить данные для использования в режиме реального времени
- Сохранить данные для последующей пакетной обработки
Первый вариант не столь фантастичен, однако надо помнить, что в рассматриваемой архитектуре отброшенные данные пропадают необратимо.
Передача данных потоковой платформе – это вариант композиции, когда выход одной системы обработки становится входом для другой.
Долговременное хранилище
Долговременное хранилище прежде всего интересно, если необходимо хранение исторических данных для последующей обработки в пакетном режиме. В качестве примеров долговременного хранилища можно назвать HDFS, HBase, реляционные СУБД и т.п. Если возникает необходимость записывать данные в долговременное хранилище, есть три принципиальных подхода как это делать:
- Сохранять каждое сообщение отдельно
- Сохранять сообщения пакетами, формируемыми в звене анализа
- Сохранять сообщения пакетами, формируемыми очередью сообщений
Первые два варианта – варианты прямой записи. В первом варианте каждое сообщение записывается сразу после обработки, по сути, со скоростью потока. Во втором сообщения накапливаются звеном анализа и по достижении определённого порога или времени записываются в долговременное хранилище.
С этими вариантами связаны потенциальные проблемы и риски. Скажем, если хранилище не справляется со скоростью потока, то мы как минимум не сможем сохранить данные, а возможно затормозим и анализ. К тому же при крахе компонента анализа велик шанс потерять данные.
Третий вариант – вариант непрямой записи. Здесь связь между долговременным хранилищем и обработкой потока разрывается при помощи очереди. Можно использовать все те же подходы, что и с очередью сообщений, используемой для входных данных. При этом данные из очереди сообщений извлекаются специальным компонентом, называемым пакетным загрузчиком. Из инструментов пакетной загрузки можно назвать Gobblin (https://gobblin.readthedocs.io, sic!) компании LiknedIn и Secor (https://github.com/pinterest/secor) компании Pinterest. Оба реализуют сохранение данных из Kafka или другого источника в HDFS, Openstack Swift или другое решение распределённого хранения (напр. MS Azure Blob Storage, Amazon S3, etc).
Хранение данных в памяти
При создании системы потокового анализа, целью является обработка данных в режиме “почти” реального времени, по мере поступления. Кроме того, для анализа может быть необходим какой-то объём исторических данных, доступ к которым должен быть максимально быстрым (чтобы не замедлять обработку потока)
В общем, для максимально быстрого доступа к данным, эти данные должны находиться в оперативной памяти. Даже самые быстрые системы хранения на текущий момент в 1000 или более раз медленные, чем оперативная память. А в современных системах, объем доступной оперативной памяти одного узла может достигать нескольких терабайт, что делает хранение данных в оперативной памяти реальным и достаточно заманчивым подходом.
Рассмотрим какие есть варианты для таких систем.
Встраиваемые хранилища
Сначала рассмотрим вариант, который плохо подходит для систем потоковой обработки – решения для хранения, предназначенные для встраивания в программу. Такие решения рассчитаны на один узел и не предоставляют никаких средств распределённой обработки, которые обычно имеются в невстраиваемых системах.
По сути, при использовании встраиваемых решений, на каждом узле анализа оказывается своё, отдельное хранилище данных, что становится весьма проблематично, как только возникает необходимость получить данные, недоступные звену локально: неясно, как определить, какой обработчик владеет нужными данными, и что делать, если он стал недоступен.
Несколько примеров встраиваемых систем:
- SQLite
- RocksDB
- LMDB
- Perset
Это ни в коем случае не исчерпывающий список.
Системы кэширования
Решения этой категории могут называться по-разному: системы кэширования, хранилище в RAM, хранилище ключ-значение в RAM и т.п. В любом случае, речь о решениях, предназначенных для ускорения доступа к данным за счёт хранения в оперативной памяти.
Системы кэширования ставятся между потребителями данных и системой хранения (обычно долговременным хранилищем).
Здесь следует отдельно отметить, что долговременное или по крайней мере отказоустойчивое хранилище в любом случае необходимо.
Рассмотрим несколько возможных стратегий реализации кэширования.
Стратегии чтения
- Сквозное чтение
Система кэширования читает данные из постоянного хранилища, если у неё запрашивают запись, отсутствующую в кэше. Это приводит к задержкам при первом запросе данных.
- Опережающее обновление
В этом варианте кэш обновляет данные, к которым недавно осуществлялся доступ, в фоновом режиме. Идея в том, чтобы избежать накладных расходов, характерных для сквозного чтения. Если система опережающего обновления настроена согласованно с системой хранения, то велика вероятность уменьшить задержки. В потоковой системе, однако, организовать точную координацию может быть затруднено.
Стратегии записи
- Обходная запись
- Идея этой стратегии в том, что постоянное хранилище, проксированное кэшем, обновляется помимо кэша. “Помимо” в данном случае означает, что система кэширования вообще может не знать о том, когда и как обновляется хранилище, и полагаться на то, что какой-то другой процесс обновит кэш при обновлении хранилища. На предыдущих схемах такая запись показана точечным пунктиром.
- Сквозная запись
Здесь система кэширования проксирует не только чтение, но и запись. Однако, в данном случае, запись производится сразу, и подтверждение успешности записи поступает только когда запись в долговременное хранилище завершена. Это в свою очередь приводит к задержкам.
- Отложенная запись
В случае отложенной записи, система кэширования записывает данные не сразу, а “рано или поздно”. В отличие от сквозной записи, запрос на запись возвращается, как только обновлены данные в кэше, а запись в хранилище производится в фоновом режиме. Преимуществом такой системы является отсутствие накладных расходов на ввод-вывод, но возникает проблема: пока данные не записаны в хранилище, есть вероятность их утери в связи с крахом узла кэша.
Таким образом, приходится выбирать между задержками на сохранение данных и шансом их утраты.
Несколько наиболее популярных продуктов:
- Memcached – популярная система кэширования, но для записи в хранилище требуется обходная запись
- EHCache – система кэширования, поддерживающая различные стратегии
- Hazelcast – решение не только и не столько для кэширования, но в части кэширования поддерживает сквозное чтение и сквозную запись
- Redis – поддерживает постоянное хранение с помощью собственного формата, однако любую из перечисленных выше стратегий по сути необходимо реализовывать вручную
Базы данных и сетки данных в памяти
Базы данных в памяти (IMDB) и сетки данных в памяти (IMDG) представляют более мощное решение, чем системы кэширования. В отличие от систем кэширования, для энергонезависимого хранения непосредственно используется диск. Однако в отличие от традиционных СУБД (включая многие NoSQL решения) в том, что данные в основном хранятся в памяти, а диск используется только для журналирования и периодического сохранения “снимков” данных, т.е. используется только чтобы не потерять данные в случае сбоя.
Исторически, IMDB и IMDG отличались в основном наличием в IMDG распределённой архитектуры, позволяющей выполнять обработку данных непосредственно “рядом с данными”, по аналогии с хранимыми процедурами в РСУБД. Кроме того, IMDB обычно предоставляют API на базе SQL, в то время как IMDG – нет. Но на практике эти границы крайне размыты. IMDB приближаются по функциональности к IMDG, а IMDG-продукты в свою очередь соревнуются с фреймворками обработки.
- MemSQL – система, совместимая с MySQL, но хранящая данные в оперативной памяти с возможностью сохранения на диск.
- VoldDB – высокопроизводительная SQL-совместимая БД, предлагающая вариант хранения в памяти
- Aerospike – оптимизированный для SSD-накопителей движок NoSQL
- Apache Geode – движок для хранения в памяти. Предлагает SQL-подобный язык запросов OQL
- Couchbase – документно-ориентированная база данных, появившаяся как гибрид Memcached и CouchDB. Поддерживает SQL-подобный язык запросов N1QL.
- Apache Ignite – IMDG, с развитой структурой распределённых вычислений, поддержкой SQL и интеграцией с Hadoop и Spark.
- Hazelcast – IMDG с поддержкой собственного языка запросов, распределённого обобщения, MapReduce и т.д.
- Infinispan – IMDG, предлагающая интеграцию со Spark