Русский
Русский
English
Статистика
Реклама

Transactions

Из песочницы Node.js MongoDB перформанс транзакций

18.07.2020 14:05:59 | Автор: admin
Иногда мы платим больше всего за то, что получаем бесплатно. А.Эйнштейн

Не так давно в MongoDB версии 4+ появилась поддержка мульти-документных транзакций.

А поскольку наш проект как раз мигрировал на версию 4.2, закономерно возникли вопросы:

  • Что будет с перформансом?
  • На сколько операции замедлятся?
  • Готовы ли мы пожертвовать скоростью ради (хоть какой-то) точности?

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

  • Все ли операции будут замедлены за счет транзакций?
  • На сколько замедлятся комбинации операций?

Давайте попробуем узнать.

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

Для простоты восприятия разделю имплементацию на 3 шага:

  1. Выбор инструментов
  2. Описание комбинаций операций и получения результатов
  3. Анализ результатов

Теперь о каждом шаге отдельно.

Выбор инструментов:

  1. Необходима тестовая MongoDB (реплика с минимальным количеством mongod процессов) и драйвер для нее: mongodb-memory-server, mongodb.
  2. Для простоты измерения времени я выбрал модуль microseconds
  3. Для анализа полученных результатов и визуализации используем следующее: ttest, stdlib.

Описание комбинаций операций и получения результатов:

Имплементируем каждую (из основных) отдельную операцию insertOne, updateOne, deleteOne, findOne, insertMany * updateMany * deleteMany * find * и их комбинации insertOne + updateOne + deleteOne, insertOne + updateOne + deleteOne + findOne, insertMany * + updateMany * + deleteMany * insertMany * + updateMany * + deleteMany * + find * с, и без использования транзакций.

Измерить время выполнения каждой операции.

Для примера insertMany + updateMany + deleteMany с, и без транзакции





Каждую операцию / измерение повторим 300 раз (для анализа будем использовать 100 результатов посередине, то есть с 101-го по 200-й) ** назовем это микроитерациямы (итерациями отдельных операций или комбинаций).

Теперь, постоянно меняя последовательность, проведем 100 макроитерации (1 макроитерация = общее количество микроитараций * 300) *
* количество 300 выбрано абсолютно эмпирически
** для более полной информации об имплементации приглашаю посетить github репозиторий (ссылка ниже по тексту)

Анализ результатов:

В результате всех итераций мы получили 20000 замеров для каждой операции и комбинации операций (10000 с использованием транзакции, 10000 без) в виде массивов



Далее необходимо провести некоторые расчеты.

Обрезать результаты, которые явно выпадают за пределы выборки



Вычислить среднее значение



Вычислить стандартное отклонение



Определить существование статистически достоверной разницы между выборками с помощью ttest (подтверждение или опровержение нулевой гипотезы)



С помощью простых графиков визуализируем результаты. Для примера возьмем комбинацию insertMany + updateMany + deleteMany и отдельно insertOne (все остальные результаты будут изложены в текстовом формате в разделе Выводы). В результате в сгенерированных html-файлах есть график, название которого соответствует названию операции или комбинации операции (бирюзовым цветом обозначены безтранзакционные итерации, оранжевым транзакционные). Is statistically significant (true / false) говорит о том, была ли вообще какая-то статистически значимая разница. Все остальное абсолютные и относительные значения в микросекундах и процентах соответственно.





Выводы:

  1. Вообще нет никакой разницы между операциями с использованием транзакций и без: insertMany + updateMany + deleteMany (ищите иллюстрацию выше)
  2. Существует небольшая разница (до 7%): updateMany, find, insertOne + updateOne + deleteOne + findOne, insertMany + updateMany + deleteMany + find
  3. Транзакции проходят медленнее, но не так критично (91%): updateOne, deleteMany, findOne
  4. Транзакции значительно медленнее (от 197% до 792%): insertOne, insertMany, deleteOne, insertOne + updateOne + deleteOne

Для получения дополнительной информации и возможности проверить результаты, запустив сценарии самостоятельно, посетите github.

Спасибо за то, что прочитали.

Не стесняйтесь комментировать, надеюсь, что у нас выйдет хорошая дискуссия.

Также вы можете запустить все это самостоятельно и получить собственные результаты. Будет круто их сравнить

Полезные ссылки:
medium.com/cashpositive/the-hitchhikers-guide-to-mongodb-transactions-with-mongoose-5bf8a6e22033
blog.yugabyte.com/are-mongodb-acid-transactions-ready-for-high-performance-applications
medium.com/@Alibaba_Cloud/multi-document-transactions-on-mongodb-4-0-eebd662ac237
www.mongodb.com/blog/post/mongodb-multi-document-acid-transactions-general-availability
docs.mongodb.com/manual/core/write-operations-atomicity
www.dbta.com/Columns/MongoDB-Matters/Limitations-in-MongoDB-Transactions-127057.aspx
dzone.com/articles/multi-document-transactions-on-mongodb-40
www.dbta.com/Columns/MongoDB-Matters/MongoDB-Transactions-In-Depth-125890.aspx
www.codementor.io/@christkv/mongodb-transactions-vs-two-phase-commit-u6blq7465
docs.mongodb.com/manual/core/read-isolation-consistency-recency
mathworld.wolfram.com/Outlier.html
support.minitab.com/en-us/minitab-express/1/help-and-how-to/basic-statistics/inference/how-to/two-samples/2-sample-t/interpret-the-results/key-results
Подробнее..

Создаем plugin для IDEA для мониторинга транзакций в Spring

20.04.2021 10:21:38 | Автор: admin

Disclaimer: я не являюсь сотрудником JetBrains (а жаль), поэтому код может являться не оптимальным и служит только для примера и исследовательских целей.

Введение

Часто во время работы со Spring непонятно, правильно ли работает аннотация @Transaction:

  • в правильном ли месте мы ее поставили

  • правильно ли объявился interceptor

  • и т.д.

Самым простым способом для меня было остановиться в debug в IDEA в необходимом методе и исследовать, что возвращает

TransactionSynchronizationManager.isActualTransactionActive();

Но "я же программист" и захотелось это дело хоть как-то автоматизировать, заодно поизучать возможности написания plugin для IDEA.

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

Примерно вот что получилось:

Подробней про транзакции можно прочитать здесь.

С чего начать создания plugin?

Про настройку проекта для создание plugin можно прочитать здесь.

Повторюсь, что главными отправными точками будут:

Реализация

Исходный код получившегося решения размещен здесь.

Создание action

Прочитать про action можно здесь. Под action, в основном, понимается кнопка или элемент меню.

Сначала определим новый action. У action есть два метода update и actionPerformed.

  • update - вызывается idea несколько раз в секунду для того, чтобы установить корректное состояние (включен/не включен, виден/не виден)

  • actionPerformed - вызывается idea при выполнении действия (нажатия на кнопку).

public class PopupDialogAction extends AnAction {  @Override  public void update(AnActionEvent e) {    // Using the event, evaluate the context, and enable or disable the action.  }  @Override  public void actionPerformed(@NotNull AnActionEvent e) {    // Using the event, implement an action. For example, create and show a dialog.  }}

Для того, чтобы зарегистрировать новый action, требуется прописать в plugin.xml:

<actions>  <action id="TransactionStatusAction"          class="com.github.pyltsin.sniffer.debugger.TransactionStatusAction"          icon="SnifferIcons.RUNNING"          text="Current Transaction Status">    <add-to-group group-id="XDebugger.ToolWindow.TopToolbar" anchor="last"/>  </action></actions>

После этого должна была появиться новая кнопка на панели инструментов в debug-окне

Вычисление значений в debug

IDEA позволяет нам вычислять выражения при debug и взаимодействовать с памятью

И не только

Информация взята из twitter Тагира Валеева

В Evaluate даже встроен свой мини-интерпретатор Java, который позволяет выполнять прикольные вещи, например, такие:

Картинка из поста Тагира Валеева (http://personeltest.ru/aways/twitter.com/tagir_valeev/status/1360512527218728962)Картинка из поста Тагира Валеева (http://personeltest.ru/aways/twitter.com/tagir_valeev/status/1360512527218728962)

Поэтому, используя API IDEA, мы легко сможем узнать, когда транзакция активна, выполнив:

const val TRANSACTION_ACTIVE: String =    "org.springframework.transaction.support.TransactionSynchronizationManager"+  ".actualTransactionActive.get()==Boolean.TRUE"

Для начала сделаем так, чтобы наше действие было недоступно, если мы не находимся в режиме debug. Для этого получим XDebugSession и сравним с null

override fun update(e: AnActionEvent) {  val presentation = e.presentation  val currentSession: XDebugSession? = getCurrentSession(e)  if (currentSession == null) {    setDisabled(presentation)    return  }}private fun getCurrentSession(e: AnActionEvent): XDebugSession? {  val project = e.project  return if (project == null) null else   XDebuggerManager.getInstance(project).currentSession}

Многие вещи в idea реализованы через статические методы и паттерн singleton (хотя это уже почти считается антипаттерном - это очень удобно, что мы можем получить требуемые значения из любого места через статические методы, например, XDebuggerManager.getInstance)

Евгений Борисов не одобряет singleton как pattern, когда есть SpringЕвгений Борисов не одобряет singleton как pattern, когда есть Spring

Теперь мы хотим получить значения из контекста Spring из текущей сессии Java. Для этого можно воспользоваться следующим методом:

public abstract class XDebuggerEvaluator {  public abstract void evaluate(@NotNull String expression,                                 @NotNull XEvaluationCallback callback,                                 @Nullable XSourcePosition expressionPosition);}

Например, так

val currentSourcePosition: XSourcePosition? = currentSession.currentStackFrame?.sourcePositioncurrentSession.debugProcess.evaluator?.evaluate(  TRANSACTION_ACTIVE, object : XDebuggerEvaluator.XEvaluationCallback {    override fun errorOccurred(errorMessage: String) {      TODO("Not yet implemented")    }    override fun evaluated(result: XValue) {      TODO("Not yet implemented")    }  },  currentSourcePosition)

В XValue теперь хранится вычисленное значение. Чтобы посмотреть его, можно выполнить:

(result as JavaValue).descriptor.value

Он возвращает объект класса - com.sun.jdi.Value

Часть JavaDoc для com.sun.jdi.Value

The mirror for a value in the target VM. This interface is the root of a value hierarchy encompassing primitive values and object values.

Мы научились вычислять значения в debug с использованием API IDEA.

Рабочая панель (Tool Window)

Теперь попробуем их вывести в рабочую панель (Tool Window). Как всегда начинаем с документации и примера.

Объявляем в plugin.xml новое окно

<toolWindow id="TransactionView" secondary="true" icon="AllIcons.General.Modified" anchor="right"factoryClass="com.github.pyltsin.sniffer.ui.MyToolWindowFactory"/>
public class MyToolWindowFactory implements ToolWindowFactory {  public void createToolWindowContent(@NotNull Project project, @NotNull ToolWindow toolWindow) {    MyToolWindow myToolWindow = new MyToolWindow(toolWindow, project);    ContentFactory contentFactory = ContentFactory.SERVICE.getInstance();    Content content = contentFactory.createContent(myToolWindow.getContent(), "", false);    final ContentManager contentManager = toolWindow.getContentManager();    contentManager.addContent(content);  }}

Само окно можно нарисовать во встроенном редакторе

IDEA сама сгенерирует для нас заготовку класса:

А дальше, вспоминая старый добрый Swing, описываем логику и добавляем необходимые Listener.

Получившееся окно инструментов (Tool Windows)Получившееся окно инструментов (Tool Windows)

Способы передачи данных

Вернемся к нашему action. При нажатии на кнопку вызывается метод actionPerformed.

Как из этого метода достучаться до нашего окна?

Самый простой способ - снова воспользоваться статическим методом:

val toolWindow: ToolWindow? =ToolWindowManager.getInstance(project).getToolWindow("TransactionView")

И передать туда требуемые значения.

IDEA предоставляет еще один способ - Message Bus (детальное описание лучше смотреть в документации). Один из вариантов использования следующий:

Объявить интерфейс:

public interface ChangeActionNotifier {    Topic<ChangeActionNotifier> CHANGE_ACTION_TOPIC = Topic.create("custom name", ChangeActionNotifier.class)    void beforeAction(Context context);    void afterAction(Context context);}

В месте, где принимаем сообщения:

bus.connect().subscribe(ActionTopics.CHANGE_ACTION_TOPIC, new ChangeActionNotifier() {        @Override        public void beforeAction(Context context) {            // Process 'before action' event.        }        @Override        public void afterAction(Context context) {            // Process 'after action' event.        }});

В месте, где отправляем сообщения:

ChangeActionNotifier publisher = myBus.syncPublisher(  ActionTopics.CHANGE_ACTION_TOPIC);publisher.beforeAction(context);

В любом случае необходимо быть аккуратным с многопоточностью и не выполнять долгие операции на UI Thread (подробности).

Осталось собрать все вместе и протестировать.

Исходный код получившегося решения размещен здесь.

Краткий "как бы" вывод

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

Ссылки

Подробнее..

Архитектура транзакций в Apache Ignite

20.11.2020 10:22:07 | Автор: admin
В этой статье мы рассмотрим, как устроены транзакции в Apache Ignite. Не будем останавливаться на концепции Key-Value хранилища, а перейдем сразу к тому, как это реализовано в Ignite. Начнем с обзора архитектуры, а затем проиллюстрируем ключевые моменты логики транзакций при помощи трейсинга. На простых примерах вы увидите, как работают транзакции (и по каким причинам могут не работать).

Необходимое отступление: кластер в Apache Ignite


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



Данные, с логической точки зрения, принадлежат партициям, которые в соответствии с некоторой affinity-функцией распределены по узлам (подробнее о распределении данных в Ignite). У основных (primary) партиций могут быть копии (backups).



Как устроены транзакции в Apache Ignite


Архитектура кластера в Apache Ignite накладывает на механизм транзакций определенное требование: консистентность данных в распределенной среде. Это означает, что данные, находящиеся на разных узлах, должны изменяться целостно с точки зрения ACID принципов. Существует ряд протоколов, позволяющих реализовать требуемое. В Apache Ignite используется алгоритм на основе двухфазного коммита, который состоит из двух этапов:
  • prepare;
  • commit;

Отметим, что, в зависимости от уровня изолированности транзакции, механизма взятия локов и ряда других параметров, детали в фазах могут изменяться.

Рассмотрим, как происходят обе фазы, на примере следующей транзакции:
Transaction tx = client.transactions().txStart(PESSIMISTIC, READ_COMMITTED);client.cache(DEFAULT_CACHE_NAME).put(1, 1);tx.commit();

Prepare фаза


  1. Узел координатор транзакций (near node в терминах Apache Ignite) отправляет prepare-сообщение на узлы, содержащие primary-партиции для всех ключей, принимающих участие в данной транзакции.
  2. Узлы с primary-партициями отправляют Prepare-сообщение на соответствующие узлы с backup-партициями, если таковые имеются, и захватывают необходимые локи. В нашем примере backup-партиций две.
  3. Узлы с backup-партициями отправляют Acknowledge-сообщения на узлы с primary-патрициями, которые, в свою очередь, отправляют аналогичные сообщения на узел, координирующий транзакцию.


Commit фаза


После получения подтверждающих сообщений от всех узлов, содержащих primary-партиции, узел координатор транзакций отправляет Commit-сообщение, как показано на рисунке ниже.



Транзакция считается завершенной в тот момент, когда координатор транзакций получил все подтверждающие (Acknowledgment) сообщения.

От теории к практике


Чтобы рассмотреть логику работы транзакции, обратимся к трейсингу.
Для включения трейсинга в Apache Ignite необходимо выполнить следующие шаги:
  • Включим модуль ignite-opencensus и зададим OpenCensusTracingSpi как tracingSpi посредством конфигурации кластера:
    <bean class="org.apache.ignite.configuration.IgniteConfiguration">    <property name="tracingSpi">        <bean class="org.apache.ignite.spi.tracing.opencensus.OpenCensusTracingSpi"/>    </property></bean>
    

    или
    IgniteConfiguration cfg = new IgniteConfiguration();cfg.setTracingSpi(    new org.apache.ignite.spi.tracing.opencensus.OpenCensusTracingSpi());
    

  • Зададим некоторый отличный от нуля уровень сэмплирования транзакций:
    JVM_OPTS="-DIGNITE_ENABLE_EXPERIMENTAL_COMMAND=true" ./control.sh --tracing-configuration set --scope TX --sampling-rate 1
    

    или
    ignite.tracingConfiguration().set(            new TracingConfigurationCoordinates.Builder(Scope.TX).build(),            new TracingConfigurationParameters.Builder().                    withSamplingRate(SAMPLING_RATE_ALWAYS).build());
    

    Остановимся на нескольких моментах чуть подробнее:
    • Конфигурация трейсинга относится к классу экспериментальных API и потому требует включения флага
      JVM_OPTS="-DIGNITE_ENABLE_EXPERIMENTAL_COMMAND=true"
      
    • Мы задали sampling-rate равным единице, таким образом, сэмплировать будут все транзакции. Это оправдано для целей иллюстрации рассматриваемого материала, но не рекомендуется к использованию в продакшене.
    • Изменение параметров трейсинга, за исключением выставления SPI, имеет динамическую природу и не требует перезапуска узлов кластера. Ниже, в соответствующем разделе, доступные параметры настройки будут разобраны более подробно.

  • Запустим PESSIMISTIC, SERIALIZABLE транзакцию с клиентского узла на кластере из трех узлов.
    Transaction tx = client.transactions().txStart(PESSIMISTIC, SERIALIZABLE);client.cache(DEFAULT_CACHE_NAME).put(1, 1);tx.commit();
    


Обратимся к GridGain Control Center (подробный обзор инструмента) и взглянем на получившееся дерево спанов:



На иллюстрации мы видим, что корневой спан transaction, созданный в начале вызова transactions().txStart, порождает две условных группы спанов:
  1. Машинерию, связанную с захватом локов, инициированную put() операцией:
    1. transactions.near.enlist.write
    2. transactions.colocated.lock.map с подэтапами
  2. transactions.commit, созданный в момент вызова tx.commit(), который, как ранее упоминалось, состоит из двух фаз prepare и finish в терминах Apache Ignite (finish-фаза тождественна commit-фазе в классической терминологии двухфазного коммита).

Давайте теперь рассмотрим детально prepare-фазу транзакции, которая, начавшись на узле координаторе транзакций (near-узел в терминах Apache Ignite), продуцирует спан transactions.near.prepare.

Попав на primary-партицию, prepare-запрос триггерит создание transactions.dht.prepare спана, в рамках которого осуществляется отправка prepare-запросов на бекапы tx.process.prepare.req, где они обрабатываются tx.dht.process.prepare.response и отсылаются обратно на primary-партицию, которая отправляет подтверждающее сообщение на координатор транзакций, попутно создавая спан tx.near.process.prepare.response. Finish-фаза в рассматриваемом примере будет аналогична prepare-фазе, что избавляет нас от необходимости детального ее разбора.

Кликнув по любому из спанов, мы увидим соответствующую метаинформацию:



Так, например, для корневого спана transaction мы видим, что он был создан на клиентском узле 0eefd.

Мы также можем увеличить степень детализации трейсинга транзакций, включив трейсинг коммуникационного протокола.
Настройка параметров трейсинга
JVM_OPTS="-DIGNITE_ENABLE_EXPERIMENTAL_COMMAND=true" ./control.sh --tracing-configuration set --scope TX --included-scopes Communication --sampling-rate 1 --included-scopes COMMUNICATION

или
       ignite.tracingConfiguration().set(           new TracingConfigurationCoordinates.Builder(Scope.TX).build(),           new TracingConfigurationParameters.Builder().               withIncludedScopes(Collections.singleton(Scope.COMMUNICATION)).               withSamplingRate(SAMPLING_RATE_ALWAYS).build())




Теперь нам доступна информация о передаче сообщений по сети между узлами кластера, что, например, поможет ответить на вопрос, не была ли вызвана потенциальная проблема нюансами сетевого взаимодействия. Не будем подробно останавливаться на деталях, отметим лишь, что множество спанов socket.write и socket.read отвечают за, соответственно, запись в сокет и чтение того или иного сообщения.

Обработка исключений и восстановление после сбоев


Таким образом, мы видим, что реализация протокола распределенных транзакций в Apache Ignite близка к каноничной и позволяет получить должную степень консистентности данных в зависимости от выбранного уровня изоляции транзакций. Очевидно, что дьявол кроется в деталях и большой пласт логики остался за рамками разобранного выше материала. Так, например, мы не рассмотрели механизмы работы и восстановления транзакций в случае падения узлов, участвующих в ней. Сейчас мы это исправим.

Выше мы говорили, что в контексте транзакций в Apache Ignite можно выделить три типа узлов:
  • Координатор транзакций (near node);
  • Узел с primary-партицией для соответствующего ключа (primary node);
  • Узлы с backup-партициями ключей (backup nodes);

и две фазы самой транзакции:
  • Prepare;
  • Finish;

Путем нехитрых вычислений получим необходимость обработки шести вариантов падений узла от падения бекапа на prepare-фазе до падения координатора транзакций на finish-фазе. Рассмотрим эти варианты подробнее.

Падение бекапа как на prepare, так и на finish-фазах


Такая ситуация не требует каких-либо дополнительных действий. Данные на новые backup-узлы доедут самостоятельно в рамках ребаланса с primary-узла.



Падение primary-узла на prepare-фазе


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



Падение primary-узла на finish-фазе


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



Падение координатора транзакций


Наиболее интересный случай потеря контекста транзакции. В такой ситуации primary- и backup-узлы непосредственно обмениваются локальным транзакционным контекстом друг с другом, тем самым восстанавливая глобальный контекст, что позволяет принять решение о верификации коммита. Если, например, один из узлов сообщит, что не получал Finish-сообщение, то произойдет откат транзакции.



Резюме


В приведенных примерах мы рассмотрели flow транзакций, проиллюстрировав его при помощи трейсинга, который в деталях показывает внутреннюю логику. Как видите, реализация транзакций в Apache Ignite близка к классической концепции двухфазного коммита с некоторыми ухищрениями в области производительности транзакций, связанными с механизмом взятия локов, особенностями восстановления после сбоев и логикой таймаута транзакций.
Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru