Синхронизация данных между двумя хранилищами

Есть сайт, основная БД - MYSQL, прикручиваю поиск по сущностям, которые часто обновляются и добавляются новые. Manticore получается как второе хранилище данных. Вопрос. как избежать неконсистентности данных? то есть, в mysql одни значения в Manticore другие, такое происходит из за гонки, когда приходят два конкурирующих запроса на обновление 1 сущности, в первом хранилище запрос оказался первым, во втором хранилище второй запрос стал первым и данные побились. Можно строить очереди запросов, но это сильно скажется на производительности, и тогда появится задержка, что добавлено в mysql, еще нет в elastic, что по сути тоже рассинхрон данных. Кто как решал? Есть ли какие то мысли?

вы можете делать запросы на вставку данных (REPLACE) в MySQL и Manticore RT index

Или использовать Manticore как основное хранилище

ну как основное хранилище - страшно.
про Replace не совсем понял, чем это спасет,
пришел запрос 1: name = A и 2: name = B
они работают параллельно, в Mysql вставилась запись c именем A, потом запрос мог где то затупить, и запрос 2 вставил имя B и пошел вставлять данные в Manticore, вставил имя B, тут первый запрос опять начал работать и вставил в manticore имя A.
Результат в Mysql - B, в Manticore - A

полной синхронизации снаружи не добится никак, только всякими хаками и проверками на клиенте

можете оставить MySQL как основное хранилище откуда вставлять данные в Manticore - но клиент будет работать только с Manticore и не будет обращатся в MySQL - тогда клиент никогда не увидет не консистентности данных.

Какие запросы сейчас клиент делает в MySQL и почему он не делает эти запросы в Manticore?

в мантикор идут сложные фильтрации и полнотекстовый поиск, с простыми фильтрами Mysql справляется очень хорошо.
в Предложенном варианте, клиент все таки увидит рассинхрон, когда мы записали в RT - у него одно имя. Потом собрался дельта индекс из mysql - и значение поменялось?
А жить только на rt индексах с учетом что индекс может весить 20 гигов, кажется маловероятным

вы можете

  • использовать plain индексы или main + delta схему, если вас RT не устраивают
  • можете использовать stored поля или access_XXX_attrs = mmap - чтобы оставить больше данных на диске, а не в памяти

Если только plain индексы использовать - тогда теряется мгновенный поиск. Поэтому и используется схема, сначала запись в RT потом собирается дельта и удаляем записи которые попали в дельту из RT

а зачем вам дельта индекс вообще - если вы используете RT индекс?

почему не оставить только RT индекс?

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

Если вы будете делать FLUSH \ rt_flush_period - то восстановление из бинлога будет идти только тех данных которые вставились\изменились в RT индексе после флаша. Это очень сокращает рестарт демона и восстановление индекса.
Проблему с блокированием RT индекс во время флаша исправили.
OPTIMIZE заканчивается когда остается 1 disk chunk - количество disk chunk вам показывает

mysql> show index rt_name status

Ну и если вас устраивает схема RT + plain - то вы можете делать ATTACH plain индекс в RT - вместо того чтобы удалять из RT записи которые есть в delta.

Ну те у вас какой-то очень специфичный сетап и мне кажется что синхронизация MySQL и Manticore тут не самая главная проблема.

Как-то так попробуйте:

MySQL:

mysql> desc data;
+---------+------------+------+-----+-------------------+-----------------------------+
| Field   | Type       | Null | Key | Default           | Extra                       |
+---------+------------+------+-----+-------------------+-----------------------------+
| id      | bigint(20) | NO   | PRI | 0                 |                             |
| body    | text       | YES  |     | NULL              |                             |
| updated | timestamp  | NO   |     | CURRENT_TIMESTAMP | on update CURRENT_TIMESTAMP |
+---------+------------+------+-----+-------------------+-----------------------------+
3 rows in set (0.00 sec)

mysql> desc helper;
+----------+--------------+------+-----+-------------------+-----------------------------+
| Field    | Type         | Null | Key | Default           | Extra                       |
+----------+--------------+------+-----+-------------------+-----------------------------+
| chunk_id | varchar(255) | NO   | PRI |                   |                             |
| built    | timestamp    | NO   |     | CURRENT_TIMESTAMP | on update CURRENT_TIMESTAMP |
+----------+--------------+------+-----+-------------------+-----------------------------+
2 rows in set (0.00 sec)

Manticore config:

source main
{
        type = mysql
        sql_host = localhost
        sql_user = root
        sql_pass =
        sql_db = test
        sql_query_pre = replace into helper set chunk_id = '1_tmp', built = now()
        sql_query = select id, body, unix_timestamp(updated) updated from data where updated >= from_unixtime($start) and updated <= from_unixtime($end)
        sql_query_range = select (select unix_timestamp(min(updated)) from data) min, (select unix_timestamp(built) - 1 from helper where chunk_id = '1_tmp') max
        sql_query_post_index = replace into helper set chunk_id = '1', built = (select built from helper t where chunk_id = '1_tmp')
        sql_range_step = 100
        sql_field_string = body
        sql_attr_timestamp = updated
}

source delta : main
{
        sql_query_pre =
        sql_query_range = select (select unix_timestamp(built) from helper where chunk_id = '1') min, unix_timestamp() max
        sql_query_killlist = select id from data where updated >= (select built from helper where chunk_id = '1')
}

index idx
{
        type = distributed
        local = idx_main
        local = idx_delta
}

Обратите внимание на updated в mysql, который обновляется при любом обновлении записи. Это ключевой момент. Через него же строятся индексы main и delta.

что после сборки дельты и мейна будут согласованные данные - это понятно, тут вопрос что делать с RT? что бы данные в реалтайме были актуальные, с этим проблема

как я уже писал

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

Смотря что вы понимаете под real-time’ом:

  • абсолютно синхронное появление данных в mysql и manticore обеспечить не получится никак, т.к. нет такого механизма между ними (синхронная репликация), который мог бы это обеспечить. Всё равно в какой-то момент придётся сперва отпустить лок в одном месте, потом во втором. Задержка между этими действиями будет, может быть несколько микросекунд, но будет, и есть шанс, что за это время что-то проскочит. Можно разве что лок распространять выше вполть до, например, веб-сервера, чтоб он не обрабатывал новые коннекты какое-то время. Но это уже паранойя.

  • соответственно дальше речь идёт о какой-то задержке. В mysql/manticore уже вставили, в manticore/mysql ещё нет. Тут нужно понять куда вставлять раньше. Вы пишете “основная БД - MYSQL”. Значит сперва логично в mysql, т.к. если туда не вставилось, то опасно вставлять в manticore.

  • далее по обстоятельствам. Какая задержка считается нормой? 1мс? Нужен real-time индекс. 5 секунд - уже может быть достаточно дельты. Есть системы, где delta-индексы перестраиваются каждые пару секунд. В таких случаях обычно есть не только main, но промежуточные индексы между main и delta

  • 5 секунд - много. Нужен RT. В таком случае можно пойти по пути main+delta+RT. Т.е. идея какая: после каждого перестроения delta тут же очищаем RT (т.к. всё новое уже в delta). А новые документы вставляются сперва в mysql, затем тут же в RT (при успешной вставке в mysql). Ну а индексация main и delta идёт в фоне.

  • Кроме задержки, как вы заметили может быть проблема такого плана:

    в Mysql вставилась запись c именем A, потом запрос мог где то затупить, и запрос 2 вставил имя B и пошел вставлять данные в Manticore, вставил имя B, тут первый запрос опять начал работать и вставил в manticore имя A

    В данном случае delta индекс полезен, т.к. даже если при вставке в RT такое случилось то при индексации delta неконсистентность будет устранена. Т.е. важно спустя какое-то время (секунда, две, а может и 10мс, зависит от того, насколько могут тупить запросы) пройтись ещё раз по всему новому и поправить. Это может быть delta в классическом понимании (plain индекс), а может быть и REPLACE в RT индекс, как @tomat писал уже.

  • альтернатива: можно привязаться к mysql binary log (лучше row-based). Читать оттуда, парсить и писать в manticore в RT (в один поток). На этом принципе собственно основаны Debezium, MySQL streamer, DBLog (еще не в opensource). В принципе можно что-то из них рассмотреть, если это облегчит чтение/парсинг бинлога. В таком случае задержка может быть побольше, но рассинхрона быть не должно. И в таком случае схема может быть main+RT: перестраиваете main, засекаете момент, до которого в main есть данные, остальное досинкиваете через бинлог в RT.