Как и большинство NoSQL-решений, C* подвержена одной крайне неприятной эпидемии: она является отличным инструментом для узкого класса задач, но позиционируется евангелистами как очередная серебряная пуля по хранению данных. В этой статье я расскажу о своём опыте внедрения C* в (сравнительно) нагруженный проект веб-аналитики. Она будет полезна всем, кто стоит перед выбором масштабируемого хранилища данных, и развенчает мифы и заблуждения об этом инструменте.
Для начала о позитивных моментах. Как я и сказал раньше, с основными своими задачами C* справляется на Ура. Она была создана для быстрой записи, масштабируемости и отказоустойчивости, и в этом она, наверное, лучшая: я ещё не встречал более простого управления кластером, и за всё это время она меня не подвела ни разу. Однако, самая большая проблема C* в том, что её область применения гораздо уже, чем может показаться из документации. И далее я по пунктам расскажу почему.
Честно говоря, я вообще не понимаю, зачем разработчики C* решили создать CQL. Он запутывает и дезориентирует, создаёт ложные впечатления о специфике работы C*. Вот вам несколько фактов:
Судя по всему, CQL был создан, чтобы популяризовать эту БД среди новичков, пытаясь скрыть самые важные фундаментальные понятия в работе этой БД.
Главный принцип при проектировании данных в C* — «SELECT-driven model». Вы проектируете данные так, чтобы вы их смогли потом получить с помощью SELECT. В рамках key-value wide-row хранилища это подразумевает очень сильную денормализацию. Вы не просто денормализуете данные, как вы раньше привыкли это делать в реляционных БД, вы фактически создаёте отдельную таблицу под каждый запрос. И во многих проектах (там, где очень много данных по объёму) это даёт либо огромный overhead во время map/reduce и агрегации, либо огромный overhead по объёму хранимых данных.
И да, вам сразу стоит быть готовым к тому, что без распределённого агрегирования (Hadoop, Spark, Hive и т.д.) эта БД бесполезна. Разработчики обещают в следующей версии операторы для агрегации данных в CQL, но можете особо не надеяться на них. По архитектуре этой БД ясно, что они будут работать только внутри одной строки.
Отвожу этому типу данных в этой БД свой собственный раздел, и вот почему: изначально, когда я начал внедрять C* в свой проект, я очень обрадовался: для веб-аналитики очень круто иметь атомарные каунтеры, они очень сильно упрощают систему. Но потом я понял простую истину: никогда, слышите? НИКОГДА не используйте каунтеры в C* (по крайней мере в версии до 2.1.* включительно). Дело в том, что этот тип данных противоречит всей идеологии этой БД, и из-за этого у него огромное количество проблем. Если вы спросите у любого специалиста по C* про каунтеры, он в ответ начнёт лишь злобно хихикать.
Короче:
Суть расхождения идеологии каунтеров и самой БД в том, что все операции в C* — идемпотентны (т.е. повторное применении операции не изменяет результата). Именно это даёт безотказную архитектуру при падении узлов, датацентров, проблемах со связью и т.п. Каунтеры же нарушают этот принцип, и внутри сделаны через «light transactions» — сначала считать значение, потом увеличить его. И это вызывает много проблем. В общем, если вам нужны счётчики, лучше воспользуйтесь Redis-прослойкой, а в С* уже складывайте окончательное значение.
На этом пока всё. Выбирайте инструменты с умом. Если что-то кажется слишком хорошим, чтобы быть правдой, то возможно, так оно и есть.
Для начала о позитивных моментах. Как я и сказал раньше, с основными своими задачами C* справляется на Ура. Она была создана для быстрой записи, масштабируемости и отказоустойчивости, и в этом она, наверное, лучшая: я ещё не встречал более простого управления кластером, и за всё это время она меня не подвела ни разу. Однако, самая большая проблема C* в том, что её область применения гораздо уже, чем может показаться из документации. И далее я по пунктам расскажу почему.
CQL — это не SQL
Честно говоря, я вообще не понимаю, зачем разработчики C* решили создать CQL. Он запутывает и дезориентирует, создаёт ложные впечатления о специфике работы C*. Вот вам несколько фактов:
- Главное заблуждение, в которое вводит CQL всех новичков, — это иллюзии о том, что вы сможете делать какие-то выборки. Это не так. С* — это key-value хранилище. Вы не сможете получить подмножество строк в таблице. Либо одну (по ключу), либо все. Для обхода этого ограничения в C* есть «wide rows» — возможность писать в строку любые колонки (независимость от схемы, до 2 млрд уникальных колонок в строке). Но и это спасает только при особом подходе к планированию модели данных.
- CQL вводит понятия partition key и cluster key внутри PRIMARY KEY. Ещё одно крупное заблуждение в принципе работы этой БД. На самом деле строка в C* определяется только через partition-часть. Все «записи», отличающиеся по cluster key, будут просто укладываться друг за другом внутри одной и той же строки.
- Нет никакого compound partition key. Проще всего понять поведение compound key — это представить, что значения полей ключа конкатенируются перед сохранением. И строку можно получить только по полному значению ключа (как в redis, к примеру).
- INSERT и UPDATE в C* — это одно и то же. Отныне и во веки веков.
- Коллекции в CQL — это лишь синтаксический сахар, и записи в них хранятся в отдельных колонках.
- Вторичные индексы — тоже лишь синтаксический сахар. C* создаёт новое семейство ключей (таблицу) для каждого вторичного индекса и дублирует туда записи.
Судя по всему, CQL был создан, чтобы популяризовать эту БД среди новичков, пытаясь скрыть самые важные фундаментальные понятия в работе этой БД.
Особенности проектирования данных
Главный принцип при проектировании данных в C* — «SELECT-driven model». Вы проектируете данные так, чтобы вы их смогли потом получить с помощью SELECT. В рамках key-value wide-row хранилища это подразумевает очень сильную денормализацию. Вы не просто денормализуете данные, как вы раньше привыкли это делать в реляционных БД, вы фактически создаёте отдельную таблицу под каждый запрос. И во многих проектах (там, где очень много данных по объёму) это даёт либо огромный overhead во время map/reduce и агрегации, либо огромный overhead по объёму хранимых данных.
И да, вам сразу стоит быть готовым к тому, что без распределённого агрегирования (Hadoop, Spark, Hive и т.д.) эта БД бесполезна. Разработчики обещают в следующей версии операторы для агрегации данных в CQL, но можете особо не надеяться на них. По архитектуре этой БД ясно, что они будут работать только внутри одной строки.
Каунтеры
Отвожу этому типу данных в этой БД свой собственный раздел, и вот почему: изначально, когда я начал внедрять C* в свой проект, я очень обрадовался: для веб-аналитики очень круто иметь атомарные каунтеры, они очень сильно упрощают систему. Но потом я понял простую истину: никогда, слышите? НИКОГДА не используйте каунтеры в C* (по крайней мере в версии до 2.1.* включительно). Дело в том, что этот тип данных противоречит всей идеологии этой БД, и из-за этого у него огромное количество проблем. Если вы спросите у любого специалиста по C* про каунтеры, он в ответ начнёт лишь злобно хихикать.
Короче:
- Вы не можете класть в строку с каунтерами никаких других значений, кроме каунтеров
- Вы не можете класть каунтеры в коллекции (по причине выше)
- Вы не можете сделать каунтер cluster и partition key (по причине выше), сортировать по ним и ставить на них вторичные индексы, само собой, тоже
- Вы не можете установить каунтеру Time To Live
- Каунтеры имеют проблемы с репликацией (редко, но метко)
- Если вы удалите каунтер, то вы ещё долго не сможете создать его заново
Суть расхождения идеологии каунтеров и самой БД в том, что все операции в C* — идемпотентны (т.е. повторное применении операции не изменяет результата). Именно это даёт безотказную архитектуру при падении узлов, датацентров, проблемах со связью и т.п. Каунтеры же нарушают этот принцип, и внутри сделаны через «light transactions» — сначала считать значение, потом увеличить его. И это вызывает много проблем. В общем, если вам нужны счётчики, лучше воспользуйтесь Redis-прослойкой, а в С* уже складывайте окончательное значение.
На этом пока всё. Выбирайте инструменты с умом. Если что-то кажется слишком хорошим, чтобы быть правдой, то возможно, так оно и есть.