Apache Cassandra — это одна из популярных распределенных дисковых NoSQL баз данных с открытым исходным кодом. Она применяется в ключевых частях инфраструктуры такими гигантами как Netflix, eBay, Expedia, и снискала популярность за свою скорость, способность линейно масштабироваться на тысячи узлов и “best-in-class” репликацию между различными центрами обработки данных.

Apache Ignite — это In-Memory Computing Platform, платформа для распределенного хранения данных в оперативной памяти и распределенных вычислений по ним в реальном времени с поддержкой JCache, SQL99, ACID-транзакциями и базовой алгеброй машинного обучения.

Apache Cassandra является классическим решением в своей области. Как и в случае с любым специализированным решением, её преимущества достигнуты благодаря ряду компромиссов, значительная часть которых вызвана ограничениями дисковых хранилищ данных. Cassandra оптимизирована под максимально быструю работу с ними в ущерб остальному. Примеры компромиссов: отсутствие ACID-транзакций и поддержки SQL, невозможность произвольных транзакционных и аналитических транзакций, если под них заранее не адаптированы данные. Эти компромиссы, в свою очередь, вызывают закономерные затруднения у пользователей, приводя к некорректному использованию продукта и негативному опыту, либо вынуждая разделять данные между различными видами хранилищ, фрагментируя инфраструктуру и усложняя логику сохранения данных в приложениях.

Возможное решение проблемы — использование Cassandra в связке с Apache Ignite. Это позволит сохранить ключевые преимущества Cassandra, при этом скомпенсировав ее недостатки за счет симбиоза двух систем.

Как? Читайте дальше, и смотрите пример кода.


Ограничения Cassandra


Для начала хочу кратко пройтись по основным ограничениям Cassandra, с которыми будем работать:

  1. Пропускная способность и время отклика ограничены характеристиками жесткого диска или твердотельного накопителя;
  2. Специфическая структура хранения данных, оптимизированная под последовательные запись и чтение, не адаптирована под оптимальное выполнение классических реляционных операций над данными. Это не позволяет нормализовать данные и эффективно сопоставлять при помощи JOIN-ов, а также накладывает значительные ограничения, например, на такие операции, как GROUP BY и ORDER;
  3. Как следствие из п. 2 — отсутствие поддержки SQL в пользу своей более ограниченной вариации — CQL;
  4. Отсутствие ACID-транзакций.

Можно поспорить, что я хочу использовать Cassandra не по назначению, и я полностью соглашусь. Моя цель — показать, что если решить эти проблемы, “назначение” Cassandra можно значительно расширить. Совмещая человека и лошадь, получаем всадника, который может уже совсем иной перечень вещей, нежели человек и лошадь по-отдельности.

Как же можно обойти эти ограничения?

Я бы сказал, что классическим вариантом является фрагментация данных, когда часть лежит в Cassandra, а часть — в других системах, которые поддерживают необходимые гарантии.

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

Apache Ignite


Ещё один способ — поставить другую систему поверх Cassandra, разделив ответственность между ними. Я считаю, что Apache Ignite — идеальный кандидат для этой схемы:

  1. Исчезает накладываемое диском ограничение производительности: Apache Ignite работает с оперативной памятью, сейчас нет ничего быстрее. Кроме того, она дешевеет настолько стремительно, что поставить достаточное количество RAM на пул серверов (Apache Ignite — распределенная система, как и Cassandra) — посильная задача;
  2. Полная поддержка классического SQL99, включая JOIN-ы, GROUP BY, ORDER BY, а также INSERT, UPDATE, DELETE, MERGE и так далее, позволяет нормализовать данные, облегчает аналитику, а с учетом производительности при работе с RAM — открывает потенциал HTAP, аналитики в реальном времени по операционным данным;
  3. Поддержка стандартов JDBC и ODBC облегчает интеграцию с существующими инструментами, например, Tableau, и фреймворками вроде Hibernate или Spring Data;
  4. Поддержка ACID-транзакций, гибкие и глубокие настройки обеспечения отказоустойчивости и дублирования данных;
  5. Распределенные вычисления, потоковая обработка данных, машинное обучение — можно легко реализовать множество новых для бизнеса сценариев использования, приносящих дивиденды.

В такой схеме Apache Ignite встает поверх Apache Cassandra, которая играет роль слоя постоянного энергонезависимого хранения. Несмотря на то, что уже в ближайших версиях Apache Ignite появится свое собственное решение Persistence с поддержкой расширения памяти диском, lazy run и сквозным SQL, Cassandra всё равно может быть интересна в этой роли за счет своей вылизанности за долгие годы развития, распространенности и возможности разделить ответственность, не складывая все яйца в одну корзину там, где это не нужно.

Кластер Apache Ignite вбирает в себя из Apache Cassandra все или часть данных (например, за исключением архивных), по которым нужно выполнять запросы, после чего работает в режиме write-through, самостоятельно обслуживая API- или SQL-запросы на чтение, и дублируя в синхронном или асинхронном режиме запросы на запись в Cassandra, надежно сохраняя их на диск.

Далее эти данные анализируются в реальном времени, могут использоваться средства визуализации наподобие Tableau, применяться распределенные алгоритмы машинного обучения, а также формироваться витрины.

А на примере?


Далее я приведу пример простой «синтетической» интеграции Apache Cassandra и Apache Ignite, чтобы показать, как это работает и что это совсем не сложно, пусть и требует определенной доли boilerplate-кода.

Для начала я создам необходимые таблицы в Cassandra и заполню их данными, затем инициализирую Java-проект и напишу DTO-классы, после чего покажу основную часть — конфигурирование Apache Ignite для работы с Cassandra.

Я буду использовать Mac OS Sierra, Cassandra 3.10 и Apache Ignite 2.0. В Linux команды должны быть аналогичны.

Cassandra: таблицы и данные


Для начала загрузим дистрибутив Cassandra в директорию ~/Downloads, пройдя по ссылке, либо используя curl/wget.

Далее зайдем в директорию и распакуем его:

$ cd ~/Downloads
$ tar xzvf apache-cassandra-3.10-bin.tar.gz
$ cd apache-cassandra-3.10

Запустим Cassandra с настройками по умолчанию, для тестирования этого будет достаточно.

$ bin/cassandra

Далее запустим интерактивный шелл Cassandra и создадим тестовые структуры данных (выберем обычный суррогатный id как ключ — для таблиц в Cassandra зачастую имеет смысл выбирать ключи, более осмысленные с точки зрения последующего извлечения данных, но мы упростим пример):

$ cd ~/Downloads/apache-cassandra-3.10
$ bin/cqlsh

CREATE KEYSPACE IgniteTest WITH replication = {'class': 'SimpleStrategy', 'replication_factor' : 1};

USE IgniteTest;

CREATE TABLE catalog_category (id bigint primary key, parent_id bigint, name text, description text);
CREATE TABLE catalog_good (id bigint primary key, categoryId bigint, name text, description text, price bigint, oldPrice bigint);

INSERT INTO catalog_category (id, parentId, name, description) VALUES (1, NULL, 'Бытовая техника', 'Различная бытовая техника для вашего дома!');
INSERT INTO catalog_category (id, parentId, name, description) VALUES (2, 1, 'Холодильники', 'Самые холодные холодильники!');
INSERT INTO catalog_category (id, parentId, name, description) VALUES (3, 1, 'Стиральные машинки', 'Замечательные стиралки!');

INSERT INTO catalog_good (id, categoryId, name, description, price, oldPrice) VALUES (1, 2, 'Холодильник Buzzword', 'Лучший холодильник 2027!', 1000, NULL);
INSERT INTO catalog_good (id, categoryId, name, description, price, oldPrice) VALUES (2, 2, 'Холодильник Foobar', 'Дешевле не найти!', 300, 900);
INSERT INTO catalog_good (id, categoryId, name, description, price, oldPrice) VALUES (3, 2, 'Холодильник Barbaz', 'Люкс на вашей кухне!', 500000, 300000);
INSERT INTO catalog_good (id, categoryId, name, description, price, oldPrice) VALUES (4, 3, 'Машинка Habr#', 'Стирает, отжимает, сушит!', 10000, NULL);

Проверим, что все данные записались корректно:

cqlsh:ignitetest> SELECT * FROM catalog_category;

id | description | name | parentId
----+--------------------------------------------+--------------------+-----------
1 | Различная бытовая техника для вашего дома! | Бытовая техника | null
2 | Самые холодные холодильники! | Холодильники | 1
3 | Замечательные стиралки! | Стиральные машинки | 1

(3 rows)
cqlsh:ignitetest> SELECT * FROM catalog_good;

id | categoryId | description | name | oldPrice | price
----+-------------+---------------------------+----------------------+-----------+--------
1 | 2 | Лучший холодильник 2027! | Холодильник Buzzword | null | 1000
2 | 2 | Дешевле не найти! | Холодильник Foobar | 900 | 300
4 | 3 | Стирает, отжимает, сушит! | Машинка Habr# | null | 10000
3 | 2 | Люкс на вашей кухне! | Холодильник Barbaz | 300000 | 500000

(4 rows)


Инициализация Java-проекта


Есть 2 способа работы с Ignite: можно скачать дистрибутив с сайта ignite.apache.org, подложить ему необходимые Jar-файлы с собственными классами и XML с конфигурацией, либо использовать Ignite как зависимость в Java-проекте. В этой статье я рассмотрю второй вариант.

Создадим новый проект — я буду использовать maven как классический и понятный максимально широкой, на мой взгляд, аудитории инструмент.

В зависимости пропишем:

  • ignite-cassandra-store для обеспечения интеграции с Cassandra;
  • ignite-spring для загрузки XML-конфигурации в формате Spring Context, отсюда транзитивно нам прилетит кусок Spring, альтернативно можно не включать данный пакет и самим создать необходимые классы (в первую очередь IgniteConfiguration).

Транзитивно будет также загружен ignite-core, который содержит основные классы Apache Ignite.

<dependencies>
    <dependency>
        <groupId>org.apache.ignite</groupId>
        <artifactId>ignite-spring</artifactId>
        <version>2.0.0</version>
    </dependency>

    <dependency>
        <groupId>org.apache.ignite</groupId>
        <artifactId>ignite-cassandra-store</artifactId>
        <version>2.0.0</version>
    </dependency>
</dependencies>

Далее необходимо создать DTO-классы, которые будут представлять таблицы Cassandra в мире Java:

import org.apache.ignite.cache.query.annotations.QuerySqlField;

public class CatalogCategory {
    @QuerySqlField private long id;
    @QuerySqlField private Long parentId;
    @QuerySqlField private String name;
    @QuerySqlField private String description;

    // public getters and setters
}

public class CatalogGood {
    @QuerySqlField private long id;
    @QuerySqlField private long categoryId;
    @QuerySqlField private String name;
    @QuerySqlField private String description;
    @QuerySqlField private long price;
    @QuerySqlField private long oldPrice;

    // public getters and setters
}

...или на Kotlin
import org.apache.ignite.cache.query.annotations.QuerySqlField

data class CatalogCategory(@QuerySqlField var id: Long,
                           @QuerySqlField var parentId: Long?,
                           @QuerySqlField var name: String?,
                           @QuerySqlField var description: String?) {
	constructor() : this(0, null, null, null)
}

data class CatalogGood(@QuerySqlField var id: Long,
                       @QuerySqlField var categoryId: Long,
                       @QuerySqlField var name: String?,
                       @QuerySqlField var description: String?,
                       @QuerySqlField var price: Long,
                       @QuerySqlField var oldPrice: Long) {
	constructor() : this(0, 0, null, null, 0, 0)
}


Мы помечаем аннотацией @QuerySqlField те поля, которые будут участвовать в SQL-запросах. Если поле не помечено данной аннотацией, его невозможно будет извлечь его посредством SQL или фильтровать по нему.

Также можно сделать более тонкие настройки для определения индексов и полнотекстовых индексов, которые выходят за рамки данного примера. Подробнее о настройке SQL в Apache Ignite можно прочитать в соответствующем разделе документации.

Конфигурация Apache Ignite


Создадим в src/main/resources нашу конфигурацию в файле apacheignite-cassandra.xml (название выбрано произвольно). Я приведу полную конфигурацию, которая достаточно объемна, после чего рассмотрю ее по частям:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean class="org.apache.ignite.cache.store.cassandra.datasource.DataSource" name="cassandra">
        <property name="contactPoints" value="127.0.0.1"/>
    </bean>

    <bean class="org.apache.ignite.configuration.IgniteConfiguration">
        <property name="cacheConfiguration">
            <list>
                <bean class="org.apache.ignite.configuration.CacheConfiguration">
                    <property name="name" value="CatalogCategory"/>
                    <property name="writeThrough" value="true"/>
                    <property name="sqlSchema" value="catalog_category"/>
                    <property name="indexedTypes">
                        <list>
                            <value type="java.lang.Class">java.lang.Long</value>
                            <value type="java.lang.Class">com.gridgain.test.model.CatalogCategory</value>
                        </list>
                    </property>
                    <property name="cacheStoreFactory">
                        <bean class="org.apache.ignite.cache.store.cassandra.CassandraCacheStoreFactory">
                            <property name="dataSource" ref="cassandra"/>
                            <property name="persistenceSettings">
                                <bean class="org.apache.ignite.cache.store.cassandra.persistence.KeyValuePersistenceSettings">
                                    <constructor-arg type="java.lang.String"><value><![CDATA[
                                        <persistence keyspace="IgniteTest" table="catalog_category">
                                            <keyPersistence class="java.lang.Long" strategy="PRIMITIVE" column="id"/>
                                            <valuePersistence class="com.gridgain.test.model.CatalogCategory" strategy="POJO"/>
                                        </persistence>]]></value></constructor-arg>
                                </bean>
                            </property>
                        </bean>
                    </property>
                </bean>

                <bean class="org.apache.ignite.configuration.CacheConfiguration">
                    <property name="name" value="CatalogGood"/>
                    <property name="readThrough" value="true"/>
                    <property name="writeThrough" value="true"/>
                    <property name="sqlSchema" value="catalog_good"/>
                    <property name="indexedTypes">
                        <list>
                            <value type="java.lang.Class">java.lang.Long</value>
                            <value type="java.lang.Class">com.gridgain.test.model.CatalogGood</value>
                        </list>
                    </property>
                    <property name="cacheStoreFactory">
                        <bean class="org.apache.ignite.cache.store.cassandra.CassandraCacheStoreFactory">
                            <property name="dataSource" ref="cassandra"/>
                            <property name="persistenceSettings">
                                <bean class="org.apache.ignite.cache.store.cassandra.persistence.KeyValuePersistenceSettings">
                                    <constructor-arg type="java.lang.String"><value><![CDATA[
                                        <persistence keyspace="IgniteTest" table="catalog_good">
                                            <keyPersistence class="java.lang.Long" strategy="PRIMITIVE" column="id"/>
                                            <valuePersistence class="com.gridgain.test.model.CatalogGood" strategy="POJO"/>
                                        </persistence>]]></value></constructor-arg>
                                </bean>
                            </property>
                        </bean>
                    </property>
                </bean>
            </list>
        </property>
    </bean>

</beans>

Формат конфигурации — Spring Beans.

Конфигурацию можно поделить на два раздела: определение DataSource для установки связи с Cassandra и определение настроек Apache Ignite, которые сводятся в данном примере к указанию рабочих кешей, полностью соответствующих таблицам в Cassandra.

Первая часть конфигурации лаконична:

    <bean class="org.apache.ignite.cache.store.cassandra.datasource.DataSource" name="cassandra">
        <property name="contactPoints" value="127.0.0.1"/>
    </bean>

Мы определяем источник данных Cassandra, указываем адреса, по которым можно попытаться установить соединение.

Далее задается конфигурация Apache Ignite. В рамках этого теста будет минимальное отклонение от настроек по-умолчанию, поэтому переопределяем только свойство cacheConfiguration, которое будет содержать список кешей, запускаемых на кластере:

    <bean class="org.apache.ignite.configuration.IgniteConfiguration">
        <property name="cacheConfiguration">
            <list>
                ...
            </list>
        </property>
    </bean>

Первый кеш — представляющий таблицу catalog_category:

                <bean class="org.apache.ignite.configuration.CacheConfiguration">
                    <property name="name" value="CatalogCategory"/>
                    ...
                </bean>

В нем включаем режим сквозных чтения и записи (если будем что-то писать в кеш, операция записи будет автоматически дублироваться в Cassandra), указываем, что в SQL будет использоваться схема catalog_category, а также перечисляем типы, которые будут храниться в этом кеше и должны быть обработаны для обращения к ним через SQL. Типы указываются всегда парами ключ-значение, поэтому количество элементов списка всегда должно быть четным.

                    <property name="readThrough" value="true"/>
                    <property name="writeThrough" value="true"/>
                    <property name="sqlSchema" value="catalog_category"/>
                    <property name="indexedTypes">
                        <list>
                            <value type="java.lang.Class">java.lang.Long</value>
                            <value type="java.lang.Class">com.gridgain.test.model.CatalogCategory</value>
                        </list>
                    </property>

Наконец, зададим связь с Cassandra, здесь будет два основных подраздела. Во-первых, укажем ссылку на созданный ранее DataSource: cassandra. Во-вторых, нам нужно будет указать, как соотносить между собой таблицы Cassandra и записи «ключ-значение» Ignite. Это будет делаться через свойство persistenceSettings, в котором лучше сослаться на внешний XML-файл с конфигурацией меппинга, но для простоты встроим этот XML непосредственно в Spring-конфигурацию как CDATA-элемент:

                    <property name="cacheStoreFactory">
                        <bean class="org.apache.ignite.cache.store.cassandra.CassandraCacheStoreFactory">
                            <property name="dataSource" ref="cassandra"/>
                            <property name="persistenceSettings">
                                <bean class="org.apache.ignite.cache.store.cassandra.persistence.KeyValuePersistenceSettings">
                                    <constructor-arg type="java.lang.String"><value><![CDATA[
                                        <persistence keyspace="IgniteTest" table="catalog_category">
                                            <keyPersistence class="java.lang.Long" strategy="PRIMITIVE" column="id"/>
                                            <valuePersistence class="com.gridgain.test.model.CatalogCategory" strategy="POJO"/>
                                        </persistence>]]></value></constructor-arg>
                                </bean>
                            </property>
                        </bean>
                    </property>

Конфигурация меппинга выглядит достаточно интутивно понятно:

<persistence keyspace="IgniteTest" table="catalog_category">
    <keyPersistence class="java.lang.Long" strategy="PRIMITIVE" column="id"/>
    <valuePersistence class="com.gridgain.test.model.CatalogCategory" strategy="POJO"/>
</persistence>

На верхнем уровне (тег persistence) указывается Keyspace (IgniteTest в данном случае) и Table (catalog_category), которые мы будем соотносить. Затем указывается, что ключом Ignite-кеша будет тип Long, который является примитивным и должен соотноситься с колонкой id в таблице Cassandra. При этом значением является класс CatalogCategory, который должен при помощи Reflection (stategy="POJO") формироваться из колонок таблицы Cassandra.

Подробнее о более тонких настройках меппинга, которые выходят за рамки данного примера, можно прочитать в соответствующем разделе документации.

Конфигурация второго кеша, содержащего данные о товарах аналогична.

Запуск


Для запуска создадим класс com.gridgain.test.Starter:

package com.gridgain.test;

import org.apache.ignite.Ignite;
import org.apache.ignite.Ignition;

public class Starter {
    public static void main(String... args) throws Exception {
        final Ignite ignite = Ignition.start("apacheignite-cassandra.xml");

        ignite.cache("CatalogCategory").loadCache(null);
        ignite.cache("CatalogGood").loadCache(null);
    }
}

Здесь мы используем инструкцию Ignition.start(...) для запуска узла Apache Ignite, указав в качестве источника конфигурации лежащий на classpath файл apacheignite-cassandra.xml.

SQL


Для выполнения SQL-запросов можно использовать любой клиент, который поддерживает JDBC, например, встроенный в IntelliJ IDEA, либо SquirrelSQL. В последнем случае, например, нужно будет добавить драйвер Apache Ignite (который находится в Jar-файле ignite-core, его можно скачать в составе дистрибутива):



Создадим новое соединение по URL вида jdbc:ignite://localhost/CatalogGood, где localhost — адрес одного из узлов Apache Ignite, а CatalogGood — кеш, к которому будут идти по умолчанию запросы.



Пара примеров возможных SQL-запросов:

SELECT cg.name goodName, cg.price goodPrice, cc.name category, pcc.name parentCategory
FROM catalog_category.CatalogCategory cc
  JOIN catalog_category.CatalogCategory pcc
  ON cc.parentId = pcc.id
  JOIN catalog_good.CatalogGood cg
  ON cg.categoryId = cc.id;

goodName goodPrice category parentCategory
Холодильник Buzzword 1000 Холодильники Бытовая техника
Холодильник Foobar 300 Холодильники Бытовая техника
Холодильник Barbaz 500000 Холодильники Бытовая техника
Машинка Habr# 10000 Стиральные машинки Бытовая техника

SELECT cc.name, AVG(cg.price) avgPrice
FROM catalog_category.CatalogCategory cc
  JOIN catalog_good.CatalogGood cg
  ON cg.categoryId = cc.id
WHERE cg.price <= 100000
GROUP BY cc.id;

name avgPrice
Холодильники 650
Стиральные машинки 10000

Заключение


На этом простом примере можно видеть, как используя Apache Ignite можно поверх Apache Cassandra поднять распределенный SQL-движок с ACID-транзакциями и скоростью оперативной памяти.

Столкнувшись с Apache Cassandra в существующей инфраструктуре либо в greenfield-проекте, вспомните об этой статье и о том, что Cassandra хороша со вкусом Ignite. Или можно уже сейчас попробовать сочинить какой-то проект, например, из мира интернета вещей, использующий сильные стороны Ignite и Cassandra.
Поделиться с друзьями
-->

Комментарии (10)


  1. Amelius0712
    23.06.2017 11:54

    А есть примеры живых проектов в продакшене с такой вот синергией?


    1. artemshitov
      23.06.2017 13:01

      Есть.

      Из того, что сразу приходит на ум — один из наших клиентов использует для хранения и обработки данных с IoT-устойств десятки наших узлов в кластере, за которым стоит более сотни узлов Cassandra, другой наш клиент — крупный западный банк — также использует Ignite в связке с Cassandra. Cassandra может очень хорошо и быстро сохранять данные, при этом она замечательно масштабируется и обеспечивает отказоустойчивость. Ignite может очень эффективно обрабатывать данные на потоке в реальном времени, обогащая их и отвечая на поступающие запросы, в том числе обеспечивая ad-hoc аналитику.


      1. artemshitov
        23.06.2017 13:10

        Пример компании, которая использует Ignite и Cassandra.

        https://www.optioncarriere.be/jobviewx/0310e7c44f1afde7a9e89aa5c7c7e259.html — You will work with the team that builds the core of the One Digital Backbone solution providing new digital channels for ING worldwide. We mainly use the following technologies and frameworks: Java 8, Apache Ignite/GridGain, Spring, Kafka, Cassandra, Jenkins, Maven and Nolio.


  1. nash1k
    23.06.2017 13:54

    Спасибо, за очень продуктивный пост!
    1) Но какова ситуация при большой нагрузке на касандру? Уже довольно долго использовали в production данную связку, но, к сожалению, вынуждены отказаться из-за многих граблей, про которые посты в свое время не были написаны( В частности, что если данных приходит очень много (сотни тысяч операций в секунду), то при использовании IgniteDataStreamer нагрузка становится настолько большой, что касандра просто отваливается по timeout (found long running cache future), скорее всего из за резко возрастающего read latency, а кластер ignite умирает (хотя и не признается в этом) — при перезапуске приложения ловится deadlock. Конечно все фишки с сохранением пачками и асинхронной обработкой учтены. Логи, касательно причин и реальных проблем, наверх не приходят (хоть и специально выведены), а расширение кластера игнайта и касандры не помогает.
    2) Касательно типов сохраняемых данных в таблицу. Если использовать самописный класс, его действительно необходимо подложить в конфигурацию каждой ноды и перезапустить их все (на большом кластере это изрядно напрягало, ведь на одном кластере крутится очень много сервисов). Но тут возникают сразу две проблемы. 1. При его изменении — со всей накопленной информацией можно проститься (не десериализуется на уровне получения данных из касандры в кэш), либо переходить на использование BinaryObject и сохранять в кассандру не POJO а BLOB (тип класса предварительно указав BinaryObjectImpl), то есть преобразовывать свой класс ручками к данному формату, что позволит как изменять классы, так и отказаться от постоянного подкладывания новых jar на ноды, но удобства просмотря данных уже не будет (кстати тут появляется новая проблема — при переезде на новую версию игнайта BinaryObjectImpl мог измениться и история снова не десериализуется — найдено при переезде с версии 1.8 на версию 2.0 — данная проблема вынуждает держать несколько кластеров разных версий, пока полностью не переведем все сервисы на новый кластер, предварительно реализовав множество утилиток). 2. Если же остаться на самописном классе и сохранять объект как POJO (или BLOB), то тут проблема бывает еще хуже — если 10 сервисов одновременно работают с игнайтом и сохраняют данные в касандру, а на кластере подложено несколько jar со всеми самописными классами, то тут снова все не идеально — клиенты являются локальными нодами игнайта, то есть тоже должны знать обо всех этих классах, получается надо постоянно держать актуальные версии ВСЕХ классов в каждом приложении — это мука и БОЛЬ (решения 2 — свой кластер на каждый сервис или же jar со ВСЕМИ классами работающих приложений, при изменении которого надо перезапускать все сервисы с полученными обновлениями). 3. Peer class loading в данном случае не работает (а очень помог бы).
    3) Есть еще несколько небольших замечаний опять же с IgniteDataStreamer, но они не так велики.

    Оговорюсь, что данные ситуации были найдены в реальном production на больших объёмах. Использовалась для этого бесплатная версия (возможно именно в этом корень всех зол). Есть вероятность, что в каких-то местах все же конфигурация не была докручена, но тут все упирается в достаточно скудную документацию данной интеграции, которая не предупреждает о возможных проблемах (найти часть из которых можно, ознакомившись внимательно со всей документацией).

    Но при всем этом, скорости показываемые ignite действительно впечатляют (особенно при использовании IgniteDataStreamer). И очень надеюсь, что версия 2.1 приятно удивит.


    1. artemshitov
      23.06.2017 15:41
      +1

      Очень обстоятельный и детальный комментарий! Спасибо за позитивный отзыв о нас.

      Учитывая, что система у вас, судя по всему, достаточно сложная и нагруженная, нужно подробнее изучить ситуацию, конфигурацию нашу и Cassandra, после чего думать, как решить ваши проблемы. Напишу вам в ЛС с предложением пообщаться подробнее на эту тему.

      И очень надеюсь, что версия 2.1 приятно удивит.

      Должен. Там мы добавим наше собственное распределенное дисковое хранилище с возможностью вытеснять часть данных из памяти на диск (а backup-ы данных и вовсе держать только на диске пока жива primary-копия), делать сквозные SQL-запросы как по данным в памяти, так и по данным на диске, научимся lazy run-у с диска и прогревом памяти уже во время работы узлов кластера, начнем добавлять алгоритмы машинного обучения поверх алгебры, которая появилась в 2.0 и т.д.

      Дисковое хранилище можно уже пощупать в вышедшем пару дней назад GridGain 8.1. Код передан в Apache и как только сообщество его переварит и интегрирует в себя, хранилище в полном виде появится и в Apache Ignite, это будет как раз в 2.1, в проприетарной версии будет дополнительно функционал по снятию snapshot-ов.


      1. nash1k
        23.06.2017 15:43

        Да, с удовольствием. Спасибо! И да, почитал основательно и как раз надеюсь на новое хранилище.


  1. alexeykuzmin0
    23.06.2017 13:56

    А есть ли смысл в такой связке? Если данных достаточно мало, чтобы они могли уместиться в RAM, может, проще и быстрее использовать SQL-хранилище с репликацией?
    Не наезда ради, а самообразования для.


    1. nash1k
      23.06.2017 13:59

      Кстати как оказалось, это очень удобно, держать в кэше только горячие данные (настроено вытеснение и протухание), а в cassandra всю историю без реализации какого бы то ни было своего persistence слоя. Держать в RAM все на больших объемах не окупает себя.


    1. artemshitov
      23.06.2017 15:09
      +1

      Данных может быть очень по-разному. Здесь важно понимать, что Apache Ignite, как и Apache Cassandra, — распределенная партиционированная система, поэтому «уместиться в RAM» не значит «уместиться в RAM на одной машине».

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

      Соответственно, если есть инсталляция Cassandra, например, на сотни гигабайт, и необходимо добавить скорость вплоть до анализа в реальном времени и возможность работы с произвольными запросами, можно поднять в связке с ней несколько машин с Apache Ignite, с суммарным объемом RAM в несколько сотен гигабайт (сколько — зависит от данных и коэффициента избыточности, которая нужна для HA), после чего обслуживать запросы с кластера Ignite.

      Учитывая, что RAM активно дешевеет, а также появляются такие технологии как NVRAM, это может быть во многих сценариях интересным вариантом, который позволяит делать то, что раньше было невозможно.


      1. alexeykuzmin0
        23.06.2017 15:34

        Спасибо