Janusgraph удаляет вершину и завершает коммит, но следующая операция все еще видит вершину - PullRequest
1 голос
/ 18 июня 2019

У меня есть кусок кода, который удаляет вершину и фиксирует транзакцию.Следующая операция все еще видит вершину по какой-то причине.Также странно, что он видит только то, что иногда может быть основан на времени и т. Д. Например, графовая служба - содержит -> маршрут

операция 1: удаление содержит ребро и удаление вершины и операция фиксации

2: get содержит ребро из сервисного узла, и он по-прежнему получает узел маршрута, который был удален в операции 1

2 операции выполняются одна за другой и не выполняются параллельно, поэтому не возникает проблем с чтением до 1-гоcommit.

Также, если 1-й коммит завершен успешно, то я понимаю, что все остальные потоки должны немедленно увидеть обновления.

с использованием janusgraph api для java с cassandra db

sampleпсевдокод:

synchronized methodA:
 do some operations
 figure out route X need to be deleted from graph
 get all routes using contains edge from service node
 // service---contains--> route
 get route X from all routes 
 singlethreadExecutor.submitTask(DeleteRoute X)
 update some other DB with service without route X

Task DeleteRoute (route x)
 get  route X from graph DB
 delete route X vertex 
 commit

Operation1 calls into methodA:
 service with 4 routes R1,R2, R3, R4
 Expected to delete R3
 Works as expected
 R3 is deleted from graph as well as other DB

Operation2 calls into methodA:
 service expected routes in graph with R1, R2, R4
 however, method A still gets all 4 routes including R3 which is deleted in operation 1

Обратите внимание, что метод А синхронизирован, поэтому операции 1 и 2 не конфликтуют друг с другом.операция 1 завершена, а затем начинается операция 2

Меня это озадачивает, особенно когда мои журналы указывают, что фиксация завершена для операции 1, а операция 2 все еще получает узел маршрута R3 из графа с использованием janusgraph api.

Мы не используем потоковую транзакцию. Мы не используем новую транзакцию. Мы полагаемся на то, что tinkerpop открывает новую транзакцию с первой операцией для потока.

фрагменты журнала:

Операция 1:

2019-06-17 14: 58: 25,213 |deleteNode: маршрут: 1560307936368: 1683669533 2019-06-17 14: 58: 25 216 |совершить 2019-06-17 14: 58: 25,350 |Время, затраченное на коммит = 133

Операция 2:

2019-06-17 14: 58: 25,738 |updateNode 2019-06-17 14: 58: 25,739 |updateNode Узел, подлежащий обновлению: маршрут: 1560307936368: 1683669533 2019-06-17 14: 58: 25,740 |updateVertex: вершина обновлена ​​для ключа: маршрут: 1560307936368: 1683669533 2019-06-17 14: 58: 25,741 |Время обновления updateNode, занятое в updateNode = 3

Как вы можете видеть, Операция 1 удаляет узел маршрута и фиксирует, а операция 2, когда она читает из графика, все еще получает тот же узел маршрута и смогла его обновить.Наш обновляющий API проверяет, присутствует ли вершина перед обновлением, и выдает ошибку, если ее нет.

Таким образом, очевидно, что вершина все еще возвращается из графа с использованием janusgraph getVertex api на основе ключа идентификатора узла, даже если удаление было успешными фиксация была завершена как раз перед этим.

Тот же самый код работает, как и ожидалось, если разница во времени между двумя операциями манипулируется, чтобы быть больше пары минут.

Мы также настроили использоватьjanushgraph cache.

Учитывая все это, я действительно озадачен, как это вообще происходит.

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

ожидается, что не вернет вершину во 2-й операции после ее удаления и фиксации в 1-й операции, особенно когда обе операции синхронизированы и происходятодин за другим без каких-либо фаяприманки / исключения.


Вариант использования 1:

Поток-1 ---- вызовы ---> синхронизированный метод-1 ---> получитьребро / вершина, обновить вершину, зафиксировать ---- отправляет ---> singleThreadedExecutorTask ---> удалить ребро / вершину, зафиксировать ----> вызовы -> синхронизированный метод-1 (для операции 2) ----> здесь get edge / vertex по-прежнему получает старое ребро / vertex

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

Я пробовал это для варианта использования 2, и он работает как ожидалось !!


Вариант использования 2:

Thread-1 ---- вызовы ---> синхронизированный метод-1 ---> получить ребро / вершину, обновить вершину, зафиксировать ---- отправляет ---> singleThreadedExecutorTask ---> удалить ребро / вершину , совершить ----> Завершение темы-1.

Примерно через минуту:

Thread-2 ---- вызовы ---> синхронизированный метод-1 ---> получить ребро / вершину, обновить вершину, зафиксировать ---- отправляет ---> singleThreadedExecutorTask ---> удалить ребро / вершину , commit ----> Thread-2 Завершает.

Проблема Вызов Thread-2 в синхронизированный метод-1 по-прежнему получает старый ребро / вершину, которая удалена как часть процесса Thread-1.

Теперь в этом случае.

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

Поток-2, когда он запускается через минуту, открывает собственную транзакцию с областью потока с 1-й операцией графа - эта операция get в этой новой области транзакции потока должна быть в состоянии получить правильные данные без удаленного ребра / вершины из потока 1, особенно с учетом ти начинается почти через 1 минуту. Это даже не кластерная установка. И даже с кластерной настройкой - я думал, что кворум должен быть удовлетворен, прежде чем вызовы коммитов могут вернуться, а остальная часть репликации может произойти независимо (с задержкой)

Это часть, которую я не могу понять, конечно, если я добавлю ручное вмешательство с двумя потоками, такими как запуск потока 1, может быть через 2 минуты, это работает по какой-то причине.

2 минуты, кажется, очень долго для возможной последовательности в этом случае.

Так каков вариант для приложения, чтобы справиться с этим?

Есть ли способ заставить графическую операцию ждать возможной согласованности? Как и thread-2, я могу указать, что первая операция get должна ждать, пока она не вернет согласованные данные, разрешив все конфликты и т. Д.

Я не думаю, что открытие новой транзакции в потоке 2 или попытка выполнить какое-либо глобальное принятие для закрытия предыдущей устаревшей транзакции, если таковой существует, является правильным способом сделать это, поскольку это только начало нового потока.


1 Ответ

2 голосов
/ 18 июня 2019

Мутации Кассандры не появляются мгновенно

Cassandra - это то, что известно как в конечном итоге согласованная база данных, это означает, что записанные в нее изменения не гарантируются, чтобы быть немедленно видимыми для всех потребителей. Это делает все возможное, чтобы это произошло, но это не всегда происходит. Суть в том, что вы не должны ожидать появления мутаций, написанных на Кассандре, сразу после их написания.

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

Блокировка и синхронизация JanusGraph не зависят от последовательности Кассандры

JanusGraph гарантирует, что он запускает только один звонок Кассандре за раз, но это не обходит тот факт, что после того, как звонок Януса Кассандре завершен, все еще остается период времени, пока Кассандра распространяет мутации; если следующий вызов Кассандре, который сделает Янус, будет завершен до того, как эта мутация будет завершена, данные будут устаревшими.

Общая рекомендация - проверка на стороне приложения

Использование в конечном итоге согласованной системы хранения приведет к возникновению подобных проблем; рекомендуемый начальный путь для этого из Документации JanusGraph по окончательно согласованным бэкэндам заключается в устранении таких несоответствий в вашем приложении при чтении. Разработайте свое приложение таким образом, чтобы оно не предполагало, что возвращение вызова мутации означает, что мутация будет видимой, если вы сможете.

В вашем примере я бы вставил что-то между вашими двумя транзакциями, которое либо ожидает соответствующее количество времени (я бы сказал, что даже несколько секунд должно быть достаточно времени), либо проверяет, что удаление завершено.

Но, Cassandra не является хорошим решением, если вам нужна строгая согласованность данных

Я хотел бы отметить, что, хотя предыдущий абзац является быстрым и простым способом проверки этого, если вы обнаружите, что вам абсолютно необходимо подтверждать каждую операцию добавления и удаления, вам может быть лучше использовать другой бэкэнд хранилища, такой как HBase или BerkeleyDB. Вот список опций для хранилищ в соответствии с Руководством JanusGraph .

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...