Я работаю в проекте Cassandra (2.2.3), в котором мне нужно хранить отзывы и делать так, чтобы упорядоченные элементы заказывались для минимального, максимального, количества и среднего числа всех прикрепленных отзывов. Для этого, когда я вставляю новый отзыв, мне нужно удалить и заново вставить соответствующую запись, чтобы обновить ключ кластеризации, но для хранения этих ключей я использую другую таблицу, например индекс. Проблема заключается в том, что во время процесса обновления всех этих таблиц я использую пакет, но если одновременно выполняется другой процесс обновления, у меня могут быть повторяющиеся записи в таблицах упорядочения или недопустимые значения в таблице индекса хранения ключей.
Как я могу сделать возможным выполнение пакета без риска одновременной записи?
Вот структура таблиц:
CREATE TABLE IF NOT EXISTS reviews (domain VARCHAR, scenario VARCHAR, refer VARCHAR, type VARCHAR, id VARCHAR, value FLOAT, comment VARCHAR, author VARCHAR, title VARCHAR, date TIMESTAMP, attributes MAP<VARCHAR, VARCHAR>, answer VARCHAR, answer_author VARCHAR, answer_title VARCHAR, answer_date TIMESTAMP, answer_attributes MAP<VARCHAR, VARCHAR>, PRIMARY KEY((domain, scenario, refer, type), id)) WITH CLUSTERING ORDER BY (id DESC);
CREATE TABLE IF NOT EXISTS reviews_ext_ordering_avg (domain VARCHAR, refer VARCHAR, scenario VARCHAR, value FLOAT, type VARCHAR, PRIMARY KEY((domain, scenario, type), value, refer)) WITH CLUSTERING ORDER BY (value DESC);
CREATE TABLE IF NOT EXISTS reviews_ext_ordering_min (domain VARCHAR, refer VARCHAR, scenario VARCHAR, value FLOAT, type VARCHAR, PRIMARY KEY((domain, scenario, type), value, refer)) WITH CLUSTERING ORDER BY (value ASC);
CREATE TABLE IF NOT EXISTS reviews_ext_ordering_max (domain VARCHAR, refer VARCHAR, scenario VARCHAR, value FLOAT, type VARCHAR, PRIMARY KEY((domain, scenario, type), value, refer)) WITH CLUSTERING ORDER BY (value DESC);
CREATE TABLE IF NOT EXISTS reviews_ext_ordering_count (domain VARCHAR, refer VARCHAR, scenario VARCHAR, value INT, type VARCHAR, PRIMARY KEY((domain, scenario, type), value, refer)) WITH CLUSTERING ORDER BY (value ASC);
CREATE TABLE IF NOT EXISTS reviews_ext_index (domain VARCHAR, refer VARCHAR, scenario VARCHAR, count INT, avg FLOAT, min FLOAT, max FLOAT, sum FLOAT, type VARCHAR, PRIMARY KEY((domain, scenario, type), refer)) WITH CLUSTERING ORDER BY (refer ASC);
Вот пример транзакции в CQL (вместо PHP)
BEGIN BATCH
DELETE FROM acme_reviews_ext_ordering_avg WHERE domain = '[DOMAIN]' AND scenario = '[SCENARIO]' AND type = '[TYPE]' AND value = [VALUE] AND refer = '[REFER]';
DELETE FROM acme_reviews_ext_ordering_min WHERE domain = '[DOMAIN]' AND scenario = '[SCENARIO]' AND type = '[TYPE]' AND value = [VALUE] AND refer = '[REFER]';
DELETE FROM acme_reviews_ext_ordering_max WHERE domain = '[DOMAIN]' AND scenario = '[SCENARIO]' AND type = '[TYPE]' AND value = [VALUE] AND refer = '[REFER]';
DELETE FROM acme_reviews_ext_ordering_count WHERE domain = '[DOMAIN]' AND scenario = '[SCENARIO]' AND type = '[TYPE]' AND value = [VALUE] AND refer = '[REFER]';
INSERT INTO acme_reviews_ext_ordering_avg (domain, scenario, type, value, refer) VALUES ('[DOMAIN]', '[SCENARIO]', '[TYPE]', [VALUE], '[REFER]');
INSERT INTO acme_reviews_ext_ordering_min (domain, scenario, type, value, refer) VALUES ('[DOMAIN]', '[SCENARIO]', '[TYPE]', [VALUE], '[REFER]');
INSERT INTO acme_reviews_ext_ordering_max (domain, scenario, type, value, refer) VALUES ('[DOMAIN]', '[SCENARIO]', '[TYPE]', [VALUE], '[REFER]');
INSERT INTO acme_reviews_ext_ordering_count (domain, scenario, type, value, refer) VALUES ('[DOMAIN]', '[SCENARIO]', '[TYPE]', [VALUE], '[REFER]');
UPDATE acme_reviews_ext_index SET min = [MIN], avg = [AVG], max = [MAX], count = 1, sum = [SUM] WHERE domain = '[DOMAIN]' AND scenario = '[SCENARIO]' AND type = '[TYPE]' AND refer = '[REFER]';
APPLY BATCH;
Вот практический пример (также на CQL): A и B — это два клиента, которые вставляют обзор одновременно, так как в этом случае я буду минимальным, я обновлю только среднее: A вставляет значение 4, так что прошлые средние изменения от 3 до 3,5 (это только пример), B вставляет значение 4,5, а среднее значение становится 3,7 вместо прежнего значения 3, здесь два пакетных оператора:
Здесь:
BEGIN BATCH
DELETE FROM acme_reviews_ext_ordering_avg WHERE domain = 'foo.bar' AND scenario = 'article' AND type = 'generic' AND value = 3 AND refer = 'post-id-value';
INSERT INTO acme_reviews_ext_ordering_avg (domain, scenario, type, value, refer) VALUES ('foo.bar', 'article', 'generic', 3.5, 'refer-id-value');
UPDATE acme_reviews_ext_index SET avg = 3.5 WHERE domain = 'foo.bar' AND scenario = 'article' AND type = 'generic' AND refer = 'post-id-value';
APPLY BATCH;
Здесь Б:
BEGIN BATCH
DELETE FROM acme_reviews_ext_ordering_avg WHERE domain = 'foo.bar' AND scenario = 'article' AND type = 'generic' AND value = 3 AND refer = 'post-id-value';
INSERT INTO acme_reviews_ext_ordering_avg (domain, scenario, type, value, refer) VALUES ('foo.bar', 'article', 'generic', 3.7, 'refer-id-value');
UPDATE acme_reviews_ext_index SET avg = 3.7 WHERE domain = 'foo.bar' AND scenario = 'article' AND type = 'generic' AND refer = 'post-id-value';
APPLY BATCH;
В общем случае одновременной записи A удаляет строку и B не потому, что строка уже была удалена пакетом A, но обе вставляют новые строки, вызывающие дублирование, в таблице индексов у меня будет только одно из значений ключа, A или Таким образом, одно из ключевых значений дубликата не индексируется.
Я думаю, что может также случиться так, что, когда пакет A и B завершен, у меня есть только одна запись в таблице упорядочения, поэтому правильное, но неправильное значение в таблице индексов.
Поскольку Cassandra не предлагает никакой изоляции транзакций, я не понимаю, как вы можете решить эту проблему на уровне C *. Вам необходимо синхронизировать своих клиентов, чтобы убедиться, что только один из них имеет монопольный доступ к таблице, для которой требуется удаление-вставка.
Вариант использования также может привести к проблемам с большим количеством надгробий, в зависимости от того, сколько удалений у вас в обычных операциях.
Учитывая эти проблемы, вам может быть лучше перенести поиск сообщений по типу значения X во внешний индекс, такой как Solr или ElasticSearch. ИЛИ, если вы можете перейти на Cassandra 3.x, вы сможете решить свою проблему с помощью недавно представленной материализованные взгляды. Проверь это нить с аналогичной проблемой и решением, описывающим материализованные взгляды.
Других решений пока нет …