Божественная K-V таблица для мелочей

от автора

Во времена пика интереса к NoSQL базам данных простоватые K-V хранилища были несколько обойдены вниманием — отчасти это понятно, вещь не очень «инновационная», можно даже сказать старинная. В то же время своя «ниша» у них находится до сих пор (не считая того что они используются в более сложных БД в качестве индексов).

В то же время в обычной SQL-ной базе проекта порой «не хватает» такого общего K-V хранилища для разнородных (семантически) записей. В своих проектах я такую обычно завожу. Среди коллег этот подход порой вызывает негатив 🙂

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

Типичные ситуации

Обычно это про сущности которых по-видимому не будет много — у которых не много полей (или вообще одно) и которые не требуется дополнительно индексировать по этим полям, например (с собственного сайта):

  • админские нотификации пользователям (типа «поправь то и то у себя в профиле») — они исчезают после прочтения и поэтому «постоянно» их в пределах жалких 5 штук на 50 тыщ аккаунтов

  • зависимости между упражнениями в духе «решения задачи А не показывать если не решена задача Б» — таких в принципе не много т.к. задач всего штук 400 и подобных связок всего может штук 10

  • «закладки» на задачи для пользователей — эту «фичу» попросили на днях и пока не факт что она много кому понадобится

  • список языков (ru, es, fr…) на которые есть перевод текста данной задачи (переводы есть м.б. у полусотни задач — и переводов обычно меньше 10

  • top-of-week — полтора десятка строчек с пользователями нарешавшими больше всего за последнюю неделю

  • временные ключи для подтверждения / восстановления пароля

Академичный подход заключается…

какую самую большую диаграмму табличек в БД вам доводилось видеть?

какую самую большую диаграмму табличек в БД вам доводилось видеть?

Академичный подход, конечно, в том, чтобы создавать отдельные таблички — одну под нотификации, другую под зависимости, третью под переводы и так далее. У этих табличек будут понятные (по крайней мере сначала) названия, в духе:

create table admin_notifications (userid int, message text); create table task_no_show_solution_deps (taskid int, dependent_task_id int); create table task_bookmarks (userid int, taskid int); create table task_translations (taskid int, lang text); create table top_of_week (userid int, solved int); create table password_reset (userid, temp_password text, ts timestamp)

При этом можно реализовывать и one-to-one, и one-to-many, и many-to-many ситуации, благо таблицы независимы — и по ходу эволюции проекта можно добавлять в них какие-то нужные поля и т.п.

Недостатки такого подхода — не то чтобы критичны:

  • на каждую подобную мелкую фичу нужно добавлять миграцию в БД с созданием новой таблицы

  • со временем получается много «мелких таблиц» однотипных по структуре, часто с небольшим количеством записей (даже порой пустых)

Конечно, миграции сами по себе не зло — но они всегда требуют каких-то мелких дополнительных шагов, отдельной аккуратности на тестовых энвах и так далее. Также и большое количество мелких таблиц — ну что ж, в большинстве БД сжатых ограничений на это количество нет… а если и запутаемся в именах — ну по коду разберемся какая зачем 🙂

Вариант с единой K-V таблицей

Базы типа "ключ-значение" или "персистентные мэпы" обычно так просты что и рисовать нечего

Базы типа «ключ-значение» или «персистентные мэпы» обычно так просты что и рисовать нечего

Интуитивно напрашивается «вынести» название таблицы в отдельное поле и использовать одну таблицу из 3 полей:

create table stuff (id text, record_type text, value text);

в record_type предполагается записывать что-то типа adm_msg, week_top, temp_pwd и так далее — и индексом должна быть пара id, record_type.

Тут сразу видны мелкие недочёты — числовые индексы (например userid, taskid) придётся держать как строки — а record_type будет содержать много повторяющихся значений. Но это действительно мелочи пока в таблице не миллиарды записей — первый не особо аффектит вообще а второй — ну можно либо использовать короткие метки, либо сделать отдельно enum для этих значений (но enum-ам есть свои противопоказания).

Поскольку индекс составной, то в принципе почему бы не упростить таблицу до двух колонок:

create table keyval (key text, val text);

и конкатенировать «айдишник» с типом записи, в духе:

insert into keyval values ('admmsg.12345', 'Please, change your avatar'); insert into keyval values ('trans.115', 'ru fr es sk'); insert into keyval values ('bookmk.54321', '90 115 36 42');

Смысл этого подхода понятен — если для очередной фичи нужна новая простая табличка — то она уже есть — от миграций мы освободились (не считая того что с роллбэками в таблице может оставаться какой-то мусор но в большинстве случаев он никому не помешает).

Для ключей предпочтительно использовать b-tree индекс а не хэш ради того чтобы иметь возможность с его помощью выбирать сразу все значения нужной категории

select key, val from keyval where key like 'admmsg.%'

из тех же соображений «категория» идёт в начале ключа. Впрочем если уверены что вам это не потребуется, можно следовать другим соображениям 🙂

В то же время возможно хранение all-in-one — когда для данной категории сущностей ключ всего один и других не предвидится — например таблицу top-of-week можно хранить как один JSON по единственному ключу (всё равно она нужна только чтобы отдавать её на UI):

insert into keyval values ('weektop',     '{["johndoe", "John X. Doe", 67], ["lyztaylor", "Elizabeth Kitty", 53], ...}');

Недостатков тут можно усмотреть немало, но остановимся на двух:

  • сложность с хранением нескольких значений по одному ключу — нужно «изобретать» какой-то формат внутри «значения» — либо простой список, а может json и т.п.

  • нужны отдельные усилия чтобы эффективно использовать такую таблицу в коде, особенно если используется ORM — не очень культурно если везде по коду будут разбросаны префиксы типа записи (эти admmsg и bookmk)

Однако поскольку такая таблица всего одна то большинство подобных неудобств и решаются в одном месте — вероятно появляется энум (но в коде!) с типами и утильные методы get / put которые умеют преобразовывать записи в массивы и объекты (по какому признаку они решают это делать — это уж на усмотрение автора).

Однозначно что использовать такую «гибкость» оказывается несколько легче в скриптовых языках с динамической типизацией — но в целом это мы уже углубляемся в отдельную тему schema-less хранения по которой много уже написано и копий сломано.

Показания и противопоказания, заключение

Помните, у католиков, перед «Адом» существует такая зона «Чистилище» — из неё в принципе можно попасть и в ад и в рай. Ну или зависнуть до второго пришествия.

картинки про чистилище лучше искать по слову "purgatory" а то больше про кино вываливается

картинки про чистилище лучше искать по слову «purgatory» а то больше про кино вываливается

Можно воспринимать такую K-V таблицу в составе обычной «операционной» БД именно как «Чистилище», для сущностей которые связаны с фичами и идеями нуждающимися в «дозревании».

Суть аналогии ясна — если спустя некоторое время (релиз-другой) мы поняли что «фича» и соответствующией ей записи (сущности) прочно устаканились в проекте и им будет хорошо иметь «собственную» таблицу — что ж, тогда её и сделаем. А если фича оказалась невостребованной или вообще выпилена — скорее всего и делать ничего не придётся.

В проекте давно уже пережившем «бурные времена» и вошедшем в более стабильную фазу, где изменения в схеме БД редки, как праздники — ценность подобной таблички сомнительна.

Наоборот, если проект в динамичной (а то и стартовой) фазе, а аналитики (или в целом идеолухи) приходят с новыми выдумками поразительно часто и вообще склоняются к стилю работы «а-а-а, пожар, срочно выпиливаем то что срочно добавили в предыдщем спринте» — польза от такого подхода к хранению чувствительна. Можно назвать это разновидностью «schema-as-a-code» подхода.

Отдельный юзкейс связан с кастомизацией (которую мы все обожаем) — как часто оказывается, у каждого из вариантов под разных клиентов оказываются какие-то мелкие но очень важные «ништяки» и кастомизировать миграции схемы вообще болезненно (за исключением варианта когда для всех кастомеров создаются все таблицы — и нужные и ненужные). Правда здесь уже почти наверняка можно пойти дальше и организовать полноценный no-sql отдельчик в рамках sql-ной базы (с использованием JSON-колонок и запросов например).

В целом же как и многие подобные вопросы «самой лучшей организации хранения данных» эта маленькая дилемма сводится к персональным вкусам и привычкам разработчика.


ссылка на оригинал статьи https://habr.com/ru/articles/872568/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *