Пропали обновления в Cassandra

Я сталкиваюсь с проблемами потерянных записей при обновлении строки в Cassandra. Вот моя схема:

create table balances(
id bigint,
balance decimal,
last_transaction_id bigint,
update_timestamp timestamp,
type varchar,
is_balance_valid boolean, 
primary key (wallet_id)
) 

Всего узлов в кластере: 3 в локальном контроллере домена Коэффициент репликации: 2 Версия Cassandra: 2.1.8

Я обновляю значение столбца «баланс» каждый раз, когда пользователь выполняет транзакцию, читая ранее установленное значение, добавляя сумму транзакции и выдавая обновление. Я использую Java, драйвер Datastax (2.1.5).

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

Сделка №1

10 февраля 2016 г. 18:15:16,984 -[pool-11-thread-1]- INFO – ScratchpadMasterStreamProcessor.processMessage(62) – Идентификатор строки печати: 1466140282Идентификатор блокнота: 9127013322

10 февраля 2016 г. 18:15:16,986 -[pool-11-thread-1]- DEBUG - SclwBalanceUpdater.updateBalance(43) - Текущий баланс: 0,0

10 февраля 2016 г., 18:15:16,986 -[pool-11-thread-1]- DEBUG - SclwBalanceUpdater.updateBalance(44) - Отклонение: 200,0

10 февраля 2016 г. 18:15:16,986 -[pool-11-thread-1]- DEBUG - UserBalanceManager.updateWalletBalance(70) - Обновление пользователя..510978682

10 февраля 2016 г. 18:15:16,987 -[pool-11-thread-1]- DEBUG - SclwBalanceUpdater.updateBalance(51) ​​- Окончательный баланс: 200,0

10 февраля 2016 г., 18:15:16,987 -[pool-11-thread-1]- DEBUG - ScratchpadMasterStreamProcessor.processMessage(79) - Обновление баланса для кошелька 510978682 выполнено успешно

Сделка №2

10 февраля 2016 г., 18:18:19,157 -[pool-11-thread-1]-  INFO – ConsumerThread.run(82) – Событие получено

10 февраля 2016 г. 18:18:19,159 -[pool-11-thread-1]- DEBUG - SclwBalanceUpdater.updateBalance(43) - Текущий баланс: 200,0

10 февраля 2016 г. 18:18:19,159 -[pool-11-thread-1]- DEBUG - SclwBalanceUpdater.updateBalance(44) - Отклонение: 50,0

10 февраля 2016 г. 18:18:19,159 -[pool-11-thread-1]- DEBUG - UserBalanceManager.updateWalletBalance(70) - Обновление пользователя..510978682

10 февраля 2016 г. 18:18:19,160 -[pool-11-thread-1]- DEBUG - SclwBalanceUpdater.updateBalance(51) ​​- Окончательный баланс: 250,0

10 февраля 2016 г. 18:18:19,160 -[pool-11-thread-1]- DEBUG - ScratchpadMasterStreamProcessor.processMessage(79) - Обновление баланса для кошелька 510978682 выполнено успешно

Транзакция №3 (утеряна)

10 февраля 2016 г. 18:18:19,160 -[pool-11-thread-1]-  INFO – ScratchpadMasterStreamProcessor.processMessage(62) – Идентификатор строки печати: 1466162182Идентификатор блокнота: 9127117934

10 февраля 2016 г. 18:18:19,161 -[pool-11-thread-1]- DEBUG - SclwBalanceUpdater.updateBalance(43) - Текущий баланс: 250,0

10 февраля 2016 г. 18:18:19,161 -[pool-11-thread-1]- DEBUG - SclwBalanceUpdater.updateBalance(44) - Отклонение: -250,0

10 февраля 2016 г. 18:18:19,161 -[pool-11-thread-1]- DEBUG - UserBalanceManager.updateWalletBalance(70) - Обновление пользователя..510978682

10 февраля 2016 г. 18:18:19,162 -[pool-11-thread-1]- DEBUG - SclwBalanceUpdater.updateBalance(51) ​​- Окончательный баланс: 0,0

10 февраля 2016 г. 18:18:19,162 -[pool-11-thread-1]- DEBUG - ScratchpadMasterStreamProcessor.processMessage(79) - Обновление баланса для кошелька 510978682 выполнено успешно

Транзакция #4 Прочитал просроченный баланс, упс

10 февраля 2016 г., 18:18:23,140 -[pool-11-thread-1]-  INFO – ConsumerThread.run(82) – Событие получено

10 февраля 2016 г. 18:18:23,140 -[pool-11-thread-1]-  INFO – ScratchpadMasterStreamProcessor.processMessage(62) – Идентификатор строки печати: 1466162730Идентификатор блокнота: 9127120830

10 февраля 2016 г. 18:18:23,141 -[pool-11-thread-1]- DEBUG - SclwBalanceUpdater.updateBalance(43) - Текущий баланс: 250,0

10 февраля 2016 г. 18:18:23,141 -[pool-11-thread-1]- DEBUG - SclwBalanceUpdater.updateBalance(44) - Отклонение: 200,0

10 февраля 2016 г. 18:18:23,141 -[pool-11-thread-1]- DEBUG - UserBalanceManager.updateWalletBalance(70) - Обновление пользователя..510978682

10 февраля 2016 г. 18:18:23,142 -[pool-11-thread-1]- DEBUG - SclwBalanceUpdater.updateBalance(51) ​​- Окончательный баланс: 450,0

10 февраля 2016 г. 18:18:23,142 -[pool-11-thread-1]- DEBUG - ScratchpadMasterStreamProcessor.processMessage(79) - Обновление баланса для кошелька 510978682 выполнено успешно

Я установил уровень согласованности LOCAL_QUORUM как для чтения, так и для записи, и три сервера узлов cassandra имеют одинаковое время (с использованием NTP). В чем может быть проблема?


person user20507    schedule 15.02.2016    source источник
comment
Такие базы данных, как Cassandra, имеют значительные преимущества, когда речь идет о масштабировании, распределении по нескольким центрам обработки данных и доступности. Но это происходит за счет снижения гарантий согласованности. И это, скорее всего, то, что вы испытываете. Я не уверен, что Кассандра - правильный выбор, если баланс должен быть точным при любых обстоятельствах.   -  person Codo    schedule 15.02.2016


Ответы (3)


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

Однако я хотел бы предложить решение, не переходя на другую БД. Вы можете использовать тип счетчика для поля balance. Оператор обновления счетчика работает по-другому. Он отправляет cassandra команду для увеличения/уменьшения поля на определенное значение, поэтому у вас не будет проблемы несоответствия.

Однако решение на основе счетчика подходит не для всех приложений. Например, он ограничен целочисленным типом. Вероятно, более распространенным решением является программное создание своего рода транзакции: сохранение запросов на обновление в отдельной таблице и создание асинхронной процедуры, которая агрегирует все запросы на обновление, сделанные в течение определенного периода времени, и применяет их к значению balance.

person AlexR    schedule 15.02.2016
comment
На самом деле баланс с плавающей запятой, поэтому использовать счетчики не получится. Что касается второго пункта, это будет означать, что при чтении мне придется выполнять поиск в двух таблицах и сохранять маркер для отслеживания транзакций, которые не были агрегированы. Либо я удаляю много строк, либо моя вторая таблица становится огромной, и то, и другое, я думаю, будет бесполезно, так как чтение замедлится. - person user20507; 15.02.2016
comment
Просто умножьте его на 100 и сохраните как int (например, в центах вместо долларов). Если это не соответствует вашим требованиям, прочитайте вторую часть моего предложения. - person AlexR; 15.02.2016
comment
Ну, я требую, чтобы значение баланса всегда было правильным. Во-вторых, использование счетчика также не гарантирует предотвращения устаревших операций чтения ссылка - person user20507; 15.02.2016

Cassandra отлично справляется с неизменяемыми данными и идемпотентными операциями. Не так много с транзакциями или частыми обновлениями/удалениями.

Одна быстрая проверка — используете ли вы облегченные транзакции. Они снижают производительность, но могут быть необходимы для важных данных. Например,

UPDATE balances
SET balance = <new_balance>
WHERE id = <wallet_id>
IF balance = <old_balance>
person yolgun    schedule 23.02.2016

Кажется, у вас есть состояние гонки для операций обновления. Cassandra не обновляет данные, а вставляет новые с другой отметкой времени. Данные с последней отметкой времени являются фактической версией данных.

Указываете ли вы метки времени для запросов на обновление?

Временные метки позволяют указать правильную последовательность операций мутации. Если вы не укажете метку времени результата Транзакции № 3, она может быть перезаписана Транзакцией № 2, поскольку они выполняются в быстрой последовательности.

person Mikhail Baksheev    schedule 17.02.2016