Мой опыт внедрения Apache Cassandra

    Как и большинство NoSQL-решений, C* подвержена одной крайне неприятной эпидемии: она является отличным инструментом для узкого класса задач, но позиционируется евангелистами как очередная серебряная пуля по хранению данных. В этой статье я расскажу о своём опыте внедрения C* в (сравнительно) нагруженный проект веб-аналитики. Она будет полезна всем, кто стоит перед выбором масштабируемого хранилища данных, и развенчает мифы и заблуждения об этом инструменте.



    Для начала о позитивных моментах. Как я и сказал раньше, с основными своими задачами C* справляется на Ура. Она была создана для быстрой записи, масштабируемости и отказоустойчивости, и в этом она, наверное, лучшая: я ещё не встречал более простого управления кластером, и за всё это время она меня не подвела ни разу. Однако, самая большая проблема C* в том, что её область применения гораздо уже, чем может показаться из документации. И далее я по пунктам расскажу почему.

    CQL — это не SQL


    Честно говоря, я вообще не понимаю, зачем разработчики C* решили создать CQL. Он запутывает и дезориентирует, создаёт ложные впечатления о специфике работы C*. Вот вам несколько фактов:

    1. Главное заблуждение, в которое вводит CQL всех новичков, — это иллюзии о том, что вы сможете делать какие-то выборки. Это не так. С* — это key-value хранилище. Вы не сможете получить подмножество строк в таблице. Либо одну (по ключу), либо все. Для обхода этого ограничения в C* есть «wide rows» — возможность писать в строку любые колонки (независимость от схемы, до 2 млрд уникальных колонок в строке). Но и это спасает только при особом подходе к планированию модели данных.
    2. CQL вводит понятия partition key и cluster key внутри PRIMARY KEY. Ещё одно крупное заблуждение в принципе работы этой БД. На самом деле строка в C* определяется только через partition-часть. Все «записи», отличающиеся по cluster key, будут просто укладываться друг за другом внутри одной и той же строки.
    3. Нет никакого compound partition key. Проще всего понять поведение compound key — это представить, что значения полей ключа конкатенируются перед сохранением. И строку можно получить только по полному значению ключа (как в redis, к примеру).
    4. INSERT и UPDATE в C* — это одно и то же. Отныне и во веки веков.
    5. Коллекции в CQL — это лишь синтаксический сахар, и записи в них хранятся в отдельных колонках.
    6. Вторичные индексы — тоже лишь синтаксический сахар. C* создаёт новое семейство ключей (таблицу) для каждого вторичного индекса и дублирует туда записи.


    Судя по всему, CQL был создан, чтобы популяризовать эту БД среди новичков, пытаясь скрыть самые важные фундаментальные понятия в работе этой БД.

    Особенности проектирования данных


    Главный принцип при проектировании данных в C* — «SELECT-driven model». Вы проектируете данные так, чтобы вы их смогли потом получить с помощью SELECT. В рамках key-value wide-row хранилища это подразумевает очень сильную денормализацию. Вы не просто денормализуете данные, как вы раньше привыкли это делать в реляционных БД, вы фактически создаёте отдельную таблицу под каждый запрос. И во многих проектах (там, где очень много данных по объёму) это даёт либо огромный overhead во время map/reduce и агрегации, либо огромный overhead по объёму хранимых данных.

    И да, вам сразу стоит быть готовым к тому, что без распределённого агрегирования (Hadoop, Spark, Hive и т.д.) эта БД бесполезна. Разработчики обещают в следующей версии операторы для агрегации данных в CQL, но можете особо не надеяться на них. По архитектуре этой БД ясно, что они будут работать только внутри одной строки.

    Каунтеры


    Отвожу этому типу данных в этой БД свой собственный раздел, и вот почему: изначально, когда я начал внедрять C* в свой проект, я очень обрадовался: для веб-аналитики очень круто иметь атомарные каунтеры, они очень сильно упрощают систему. Но потом я понял простую истину: никогда, слышите? НИКОГДА не используйте каунтеры в C* (по крайней мере в версии до 2.1.* включительно). Дело в том, что этот тип данных противоречит всей идеологии этой БД, и из-за этого у него огромное количество проблем. Если вы спросите у любого специалиста по C* про каунтеры, он в ответ начнёт лишь злобно хихикать.

    Короче:
    1. Вы не можете класть в строку с каунтерами никаких других значений, кроме каунтеров
    2. Вы не можете класть каунтеры в коллекции (по причине выше)
    3. Вы не можете сделать каунтер cluster и partition key (по причине выше), сортировать по ним и ставить на них вторичные индексы, само собой, тоже
    4. Вы не можете установить каунтеру Time To Live
    5. Каунтеры имеют проблемы с репликацией (редко, но метко)
    6. Если вы удалите каунтер, то вы ещё долго не сможете создать его заново


    Суть расхождения идеологии каунтеров и самой БД в том, что все операции в C* — идемпотентны (т.е. повторное применении операции не изменяет результата). Именно это даёт безотказную архитектуру при падении узлов, датацентров, проблемах со связью и т.п. Каунтеры же нарушают этот принцип, и внутри сделаны через «light transactions» — сначала считать значение, потом увеличить его. И это вызывает много проблем. В общем, если вам нужны счётчики, лучше воспользуйтесь Redis-прослойкой, а в С* уже складывайте окончательное значение.

    На этом пока всё. Выбирайте инструменты с умом. Если что-то кажется слишком хорошим, чтобы быть правдой, то возможно, так оно и есть.
    Метки:
    Поделиться публикацией
    Похожие публикации
    Комментарии 19
    • +3
      Спасибо за статью, жаль ее не было, когда я так в ней нуждался :)

      Как раз недавно делал аналитику и БД была Cassandra
      Вкратце — задача была хранить логи действий пользователей в системе и потом на основе этих данных выводить статистику

      Так вот
      1) действительно столкнулся с проблемой каунтеров (но без них тяжеловато было бы вести подсчет)

      2) данные действительно очень денормализуются, вместо 1 коассической реляционной таблицы получилось 12 «кассандровских»
      Правда потом уменьшилось до 4, так как часть работы по аггрегации данных вынеслась в логику приложения

      В остальном пока все отлично, вставка действительно очень быстрая, выборка при правильно аггрегированных данных тоже работает хорошо
      • 0
        Ещё кассандра хороша, как движок для хорошего параллельного hash-merge join'а =)
      • +2
        аэээээмм, elasticsearch?
    • 0
      Подтверждаю, со многим столкнулся. И в моем случае postgresql показал лучшие результаты по скорости выборки. И вместо mysql+C* просто перешел на postgres.
      • +4
        Ну тут вы уж загнули. если задачу можно решить с помощью реляционных БД, то её нужно решить с помощью реляционных БД.
        Я вообще не вижу смысла в инструментах типа C*, если ваш набор данных может обработать одна машина.
        • 0
          Дело в том, что задача — накопление данных скажем по датчикам, чтото типа timeseries (что как раз хорошо ложится в идею К). Можно решить реляционными БД? Конечно, но хотелось использовать чтото заточенное под накопление данных. После сравнительных тестов мускль, К (4 ноды в кластере) и простой постгрес. Победил постгрес по всем показателям.
          • 0
            Для накопления данных неплохи всякие решения типа rrd, influx, carbon/whisper, если data flow понятен на этапе проектирования
            • 0
              Да, но rrd/carbon не подходил — выборки не только по time индексу, в К держал двойную-тройную копию данных под разные запросы. influxdb на тот момент был еще слишком молод.
            • 0
              influxdb не подходит по тем же причинам (кастомные индексы насколько я знаю должны появится в 0.9).
              pg справился хорошо — одна большая таблица и несколько индексов.
          • +2
            так ведь о том и речь. C* не заточена под абстрактное «накопление данных», она заточена под задачи, в которых вам 100% не хватит одной машины (с PG, не с PG, не важно). просто если рано или поздно PG начнёт захлёбываться, и вам придётся выписывать кульбиты с шардингом и репликациями (и множеством сопутствующих проблем), C* позволит линейно и безотказно расширяться с помощью лишь одной команды в консоли.
            • 0
              В ближайшие пару-тройку лет сможем расширятся вертиально. Память и диск сейчас достаточно дешевые. Потом будем думать.
    • 0
      Если речь заходит об аналитике на больших данных, map-reduce и всё такое, то надо смотреть в сторону HBase. Это хранилище спроектированно именно для этих целей. Кассандра больше подходит для географически распределенных систем, с повышенной доступностью.
    • 0
      Вопрос по п.5.
      Почему update и insert — это одно и то же? Afaik в C* это разные вещи.
      • 0
        Отличается только тем, что Insert не работает с каунтерами, в остальном — одно и тоже
      • 0
        И да — по факту и Insert, и Update это Upsert :)
    • 0
      По поводу для чего придумали CQL.
      Подход мне тоже не очень нравится — там внутри неочевидная магия делается, которую чтобы понять надо знать как физически хранятся данные.
      Однако программировать через альтернативный Thrift API — это застрелиться, достаточно тяжело, т.ч. SQL-like язык нужен и полезен.
      • 0
        Был ещё кроме голого thrift'а довольно удобный hector
    • 0
      прекрасная статья, большое спасибо!

    Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.