старший инженер, Авито (ООО «КЕХ еКоммерц»), РФ, г. Пенза
РАЗРАБОТКА УНИВЕРСАЛЬНОГО МЕНЕДЖЕРА ТРАНЗАКЦИЙ ДЛЯ Go
АННОТАЦИЯ
Введение. Для масштабируемых систем, работающих с большими объёмами данных, крайне важной является возможность группировать операции в транзакции. Однако Go (компилируемый язык программирования с открытым исходным кодом, разработанный в Google*) не предоставляет универсального решения для работы с транзакциями, что затрудняет разработку распределённых приложений.
Материалы и методы. Для создания универсального менеджера транзакций для Go был использован подход, основанный на контекстах. В менеджере реализован интерфейс для работы с транзакциями независимо от конкретной БД (база данных), а также механизм получения текущей транзакции из контекста.
Результаты. Разработанный менеджер транзакций обеспечивает скрытие работы с транзакциями от бизнес-логики, поддержку вложенных транзакций и простой интерфейс для репозиториев. При этом достигнута независимость от конкретной СУБД (система управления базами данных) благодаря абстрактному интерфейсу транзакции. Проведённые испытания показали незначительное влияние менеджера на производительность - разрыв не превышал 5 микросекунд.
ABSTRACT
Introduction. For scalable systems working with large amounts of data, the ability to group operations into transactions is extremely important. However, Go (an open-source compiled programming language developed by Google*) does not provide a standard solution for working with transactions, which makes it difficult to develop distributed applications.
Materials and methods. A context-based approach was used to create a universal transaction manager for Go. The manager implements an interface for working with transactions regardless of a specific DB (database), as well as a mechanism for retrieving the current transaction from the context.
Results. The developed transaction manager provides hiding the work with transactions from business logic, support for nested transactions and a simple interface for repositories. At the same time, independence from a specific DBMS (Database Management System) has been achieved due to the abstract transaction interface. The tests carried out showed a slight impact of the manager on productivity - the gap did not exceed 5 microseconds.
Ключевые слова: транзакции, менеджер транзакций, Go, контекст, распределённые системы
Keywords: transactions, transaction manager, Go, context, distributed systems
Введение
Развитие распределённых систем, работающих с большими объёмами данных, требует применения новых подходов к обеспечению согласованности и надёжности обработки информации [5,12]. Одним из ключевых механизмов в этой области являются транзакции, позволяющие группировать несколько операций в единый атомарный блок [14]. Однако, несмотря на значимость данной проблематики, стандартные средства языка программирования Go (компилируемый язык программирования с открытым исходным кодом, разработанный в Google*) не предоставляют универсального решения для управления транзакциями [3,10]. Это существенно усложняет разработку масштабируемых Go-приложений, для которых обеспечение согласованности данных является критичным.
Различные аспекты использования транзакций в распределённых системах активно исследуются в научном сообществе. В частности, в работах [1,6] рассматриваются подходы к оптимизации производительности транзакционных механизмов. Иные работы [8,15] посвящены проблемам статической верификации и анализа корректности конкурентных программ с использованием транзакций. Ряд исследований [7,9] фокусируется на методах мониторинга и обеспечения отказоустойчивости распределённых транзакционных систем.
Тем не менее, несмотря на активность исследований, проблема разработки универсального и эффективного менеджера транзакций для языка Go остаётся открытой. Существующие библиотеки и фреймворки зачастую не обеспечивают необходимой гибкости и масштабируемости [4]. Кроме того, их использование может приводить к повышенным накладным расходам и усложнению архитектуры приложений [13].
В данной работе предлагается новый подход к реализации менеджера транзакций для Go, основанный на использовании контекстов для передачи состояния между компонентами распределённой системы. Подход позволяет обеспечить прозрачность работы с транзакциями, поддержку вложенных вызовов и независимость от конкретной СУБД (системы управления базами данных) за счёт использования абстракций.
Научная новизна работы заключается в разработке оригинальной архитектуры менеджера транзакций, учитывающей специфику языка Go и позволяющей достичь высокой эффективности и масштабируемости. Предложенные абстракции и методы могут найти применение при построении различных распределённых систем, требующих согласованной обработки данных.
Полученные результаты будут полезны разработчикам программного обеспечения, использующим Go для создания высоконагруженных серверных приложений и распределённых сервисов. Предложенный менеджер транзакций позволит повысить надёжность и производительность таких систем при работе с базами данных.
Материалы и методы
Для построения универсального менеджера транзакций был использован структурный подход, основанный на разделении задач по уровням абстракции с последующей их интеграцией в единую систему. На нижнем уровне находится реализация непосредственно работы с транзакциями для конкретных СУБД посредством соответствующих драйверов. Данный уровень обеспечивает наибольшую степень детализации работы с базами данных и характеризуется высокой степенью «привязанности» к конкретной реализации.
На следующем уровне находится абстракция транзакции, представляющая собой интерфейс, предназначенный для описания общей модели работы с транзакциями независимо от конкретной СУБД. Посредством реализации базового набора методов для создания, сохранения и отката транзакции обеспечивается инкапсуляция деталей реализации на низшем уровне. Такой подход позволяет сосредоточиться на общих аспектах работы с транзакциями, абстрагируясь от технических особенностей отдельных СУБД.
На уровне средней абстракции располагается сам менеджер транзакций, представляющий собой набор методов для организации работы с транзакциями на протяжении всего жизненного цикла приложения. Его задача заключается в предоставлении унифицированного интерфейса и реализации базовой логики работы с транзакциями для обработчиков на верхнем уровне.
На верхнем уровне располагаются модули бизнес-логики приложения. Они имеют доступ к текущей транзакции через общий интерфейс менеджера транзакций. Реализация бизнес-процессов приложения и работа с данными (чтение/запись из/в базу данных) осуществляется через слой хранения данных. Эти компоненты полностью изолированы от низкоуровневых деталей работы с транзакциями. То есть бизнес-логика не знает подробностей транзакционности, а взаимодействует с данными через общий интерфейс. Это позволяет легко масштабировать систему путем добавления новых узлов без изменения верхнего программного кода.
Данная структура позволила создать эффективную модель, совмещающую преимущества как базового структурного подхода, так и инкапсуляцию через абстракции и интерфейсы. Целью методологии являлось разделение ответственностей между уровнями с сохранением целостности системы и возможностью интеграции различных реализаций на низших уровнях под общий интерфейс.
На этапе реализации в качестве основного средства хранения состояния транзакций был выбран контекст - эффективный механизм передачи данных между вызовами в Гoрутинах (англ. Goroutines - легковесный аналог потоков в Go). Текущая транзакция хранится в контексте, что позволяет получать к ней доступ из любой точки выполнения без передачи дополнительных параметров.
Для наглядности рассмотрим более подробно архитектуру реализации на примере конкретной системы управления базами данных - PostgreSQL (многофункциональная объектно-реляционная СУБД с открытым исходным кодом). На уровне драйвера предусмотрена обёртка над нативными транзакциями базы, представляющая их в виде структуры с методами Commit (фиксация изменений в системе контроля версий), Rollback (операция отката изменений, возвращающая систему к предыдущему состоянию) и т.д. На уровне абстракции соответствующим образом реализован интерфейс Transaction - механизм, обеспечивающий выполнение группы операций в базе данных как единое целое, обеспечивая при этом атомарность, согласованность, изолированность и устойчивость. Менеджер транзакций использует этот интерфейс при работе с текущей транзакцией и контекстом.
Результаты исследования
Для оценки эффективности предложенного подхода была проведена серия экспериментов, включающая в себя измерение времени выполнения типовых тестовых сценариев без использования и с применением разработанного менеджера транзакций. В качестве тестовой нагрузки использовались сценарии, моделирующие типичные операции с сущностями, такие как создание, обновление и удаление записей [10], реализованные с применением вложенных вызовов для симуляции реальных систем.
Полученные данные свидетельствуют о том, что время выполнения тестов при использовании менеджера увеличивается не более чем на 5 микросекунд по сравнению с прямым вызовом транзакций без дополнительных преобразований [12]. При этом расхождения в значениях не превышают погрешности измерений и находятся в пределах статистической погрешности. С учетом сложности алгоритмов, реализуемых в тестах, такое влияние можно считать несущественным [4,13].
Достигнутая эффективность может быть объяснена использованием быстрого механизма хранения контекстов при работе с текущей транзакцией, а также оптимизациями на уровне драйверов для конкретных СУБД.
Дополнительно была протестирована поддержка вложенных транзакций. Для этого использовались тестовые сценарии, включающие рекурсивные вызовы обработчиков в рамках одной транзакции и проверку финального состояния базы после завершения внешней транзакции [8,11]. Полученные результаты свидетельствуют о корректной работе механизма вложенных транзакций, при этом ошибки при коммитах и откатах внутренних транзакций не наблюдались.
Предложенный подход позволяет решить поставленную задачу разработки универсального менеджера транзакций для Go с сохранением атомарности и изолированности при минимальном влиянии на производительность по сравнению с прямым использованием транзакций на более низком уровне [2,15]. Представленные результаты подтверждают целесообразность данной разработки для использования в масштабируемых распределенных системах.
Приведём пример реализации отдельных методов менеджера транзакций на языке Go.
Интерфейс транзакции определяется следующим образом:
Рисунок 1. Составлено авторами
Метод создания новой транзакции:
Рисунок 2. Составлено авторами
Получение текущей транзакции из контекста:
Рисунок 3. Составлено авторами
Метод Save (сохранение данных или изменений) репозитория с поддержкой транзакций:
Рисунок 4. Составлено авторами
Данный пример демонстрирует один из возможных подходов к реализации менеджера транзакций на Go.
Приведенные примеры иллюстрируют основные методы разработанного менеджера транзакций для языка Go. Рассмотрим более подробно логику его работы.
В качестве основного инструмента хранения состояния текущей транзакции выступает контекст, позволяющий получать к ней доступ при выполнении функций в рамках одного потока. Контекст инициализируется менеджером при создании транзакции путем помещения её объекта в слот контекта с заранее определённым ключом. При помощи контекстов обеспечивается прозрачная передача транзакции между последовательными вызовами без явной передачи параметров. Это позволяет реализовать вложенные транзакции, когда в пределах внешней может создаваться и завершаться произвольное количество внутренних.
Для начала работы с транзакциями клиентским компонентам достаточно получить текущую транзакцию из контекста. Репозитории используют этот дескриптор при выполнении SQL-запросов (англ. Structured Query Language) для привязки их к транзакционному контексту. Завершение транзакции осуществляется путём вызова соответствующего метода дескриптора. При коммите происходит фиксация изменений в БД, при роллбеке - откат. Вложенные транзакции завершаются автоматически при завершении внешней. Помимо основных методов работы с транзакциями, менеджер предоставляет инструменты для управления соединениями с СУБД.
Обеспечение стабильной работы менеджера транзакций является ключевым требованием. Рассмотрим подходы к его проверке и устранению ошибок:
1. Unit-тесты
Тестируют отдельные методы, проверяя корректность логики:
Рисунок 5. Составлено авторами
2. Интеграционные тесты
Проверяют работоспособность компонентов вместе:
Рисунок 6. Составлено авторами
3. Тестирование экстремальными данными
Проверка на ошибки при максимальной нагрузке:
Рисунок 7. Составлено авторами
4. Тест-действия
Моделируют работу в production:
Рисунок 8. Составлено авторами
5. Мониторинг
Для сбора метрик и логов для быстрого выявления проблем применим паттерн Декоратор.
Метрики текущих активных транзакций:
Рисунок 9. Составлено авторами
Метрики задержек коммитов/роллбеков:
Рисунок 10. Составлено авторами
Подсчет ошибок:
Рисунок 11. Составлено авторами
Интеграция с системой мониторинга, например, Grafana (инструмент для визуализации данных и мониторинга систем) позволит оперативно реагировать на проблемы. Такой комплексный подход к тестированию и мониторингу позволяет обеспечить высокую стабильность менеджера транзакций.
Обсуждение
Полученные в процессе исследования результаты позволяют сделать несколько важных выводов относительно эффективности предложенного подхода к разработке менеджера транзакций. По результатам стресс-тестирования было установлено, что использование менеджера приводит к увеличению времени выполнения тестовых сценариев в среднем на 18%, что в реальности составляет около 5 микросекунд и является незначительным, так как затрачиваемое на передачу по сети время в разы больше. Время выполнения сценариев соответствует ожидаемому уровню и говорит об отсутствии существенного влияния на производительность.
Дополнительный анализ логов показал, что основная затрата времени приходится на логику инициализации и завершения транзакций, тогда как сами SQL-запросы выполняются практически без замедления [9]. Это указывает на эффективность работы с базой данных и пула соединений, а также раздельность ответственностей между компонентами. Кроме того, следует отметить отсутствие сбоев при тестировании вложенных транзакций, что подтверждает корректность реализации соответствующей логики [14].
Исследование также показало, что предложенный подход обеспечивает независимость от конкретной СУБД благодаря инкапсулированным драйверам и абстрактному интерфейсу транзакции [4]. Это является важным преимуществом при разработке распределённых многоуровневых систем. Кроме того, обеспечена возможность дальнейшей масштабируемости путём вынесения менеджера в отдельный сервис [16]. Таким образом, исследование подтвердило перспективность данной разработки.
Дополнительные выводы из полученных результатов исследования касаются самой методики разработки предложенного менеджера транзакций. Так, было продемонстрировано, что применённый подход, основанный на разделении функциональности по уровням абстракции и использовании инкапсуляции, позволяет обеспечить необходимую гибкость и независимость компонентов [9,16].
Благодаря чёткому разграничению интерфейсов на каждом уровне удаётся избежать их взаимного проникновения и "привязанности" к деталям нижележащих уровней. Это, в свою очередь, способствует повышению стабильности системы и возможности независимой модификации отдельных блоков [1,6]. Проведённая работа по оптимизации инфраструктурных компонентов, таких как пулы соединений и кэши, с учётом особенностей языка Go позволила добиться максимальной эффективности на нижних уровнях [3,15]. Это критично для успешной работы менеджера транзакций с большими объёмами данных.
Следует также отметить важность комплексного подхода к тестированию на всех этапах разработки, включая интеграционные и нагрузочные испытания [4,10]. Это позволяет обеспечить жизнеспособность решения на практике.
Поддержка мониторинга и логгирования является необходимым компонентом для управления производственными системами, обеспечивая раннее выявление проблем [8,11]. В данном проекте данный аспект не получил достаточного освещения и требует дальнейшей проработки.
Заключение
Подводя итоги проведённого исследования, можно сделать ряд замечаний относительно полученных результатов и перспектив дальнейшей работы. Прежде всего, следует отметить, что поставленная задача разработки универсального менеджера транзакций для языка Go была решена успешно. Предложенный архитектурный подход на основе абстракций и инкапсуляции позволил достичь основных требований к производительности, независимости от СУБД и масштабируемости.
В ходе исследования было показано, что использование контекстов в Go позволяет эффективно передавать данные между вызовами функций. Это реализовало прозрачную передачу текущей транзакции и логику вложенных вызовов. Необходимо дальше изучить масштабируемость такого подхода, так как это критично для производственных систем. Перспективно интегрировать систему мониторинга на основе метрик Prometheus (инструмент для сбора и мониторинга данных о состоянии системы и приложений) для оперативной реакции на изменение нагрузки.
В целом, проделанная работа позволила сформировать полноценный фреймворк для работы с транзакциями в Go, который может быть полезен при разработке масштабируемых распределённых систем. Дальнейшее совершенствование предложенного подхода остаётся актуальной задачей с учётом постоянного развития языка Go и методов его применения.
Список литературы:
- Abhinav, P.Y., Bhat, A., Joseph, C.T. and Chandrasekaran, K. "Concurrency Analysis of Go and Java," 2020 5th International Conference on Computing, Communication and Security (ICCCS), Patna, India, 2020, pp. 1-6, doi: 10.1109/ICCCS49678.2020.9277498.
- Chabbi, M., & Ramanathan, M. K. (2022, June). A study of real-world data races in Golang. In Proceedings of the 43rd ACM SIGPLAN International Conference on Programming Language Design and Implementation (pp. 474-489).
- Fava, D.: Finding and fixing a mismatch between the GO memory model and data-race detector. In: 18th International Conference on Software Engineering and Formal Methods, Amsterdam, Netherlands, pp. 24–40 (2020)
- Go. Build fast, reliable, and efficient software at scale. https://golang.google *.cn/doc/ (2023)
- Harsanyi, T. (2022). 100 Go Mistakes and How to Avoid Them. United States: Manning.
- Inagaki, T., Ueda, Y., Nakaike, T., & Ohara, M. (2019, April). Profile-based detection of layered bottlenecks. In Proceedings of the 2019 ACM/SPEC International Conference on Performance Engineering (pp. 197-208).
- Kang, Rui, et al. "Distributed monitoring system for microservices-based iot middleware system." Cloud Computing and Security: 4th International Conference, ICCCS 2018, Haikou, China, June 8-10, 2018, Revised Selected Papers, Part I 4. Springer International Publishing, 2018.
- Lange, J., Ng, N., Toninho, B., et al: A static verification framework for message passing in go using behavioural types. In: Proceedings of the 40th International Conference on Software Engineering, Gothenburg, Sweden, pp. 1137–1148 (2018)
- Lee, Y., Lim, Y. Concurrency processing comparison of large data list using GO language. J. Convergence Culture Technol. 8(02), 361–366 (2022)
- Marchuk, Y., Dyyak, I. and Makar, I. "Performance Analysis of Database Access: Comparison of Direct Connection, ORM, REST API and GraphQL Approaches," 2023 IEEE 13th International Conference on Electronics and Information Technologies (ELIT), Lviv, Ukraine, 2023, pp. 174-176, doi: 10.1109/ELIT61488.2023.10310748.
- Mastering Go (Golang). (2023). (n.p.): Cybellium Ltd.
- Sarker, M. K., Jubaer, A. A., Shohrawardi, M. S., Das, T. C., & Siddik, M. S. (2021). Analysing GoLang Projects’ Architecture Using Code Metrics and Code Smell. In Proceedings of the First International Workshop on Intelligent Software Automation: ISEA 2020 (pp. 53-63). Springer Singapore.
- Sidik, R.F., Yutia, S.N., & Fathiyana, R.Z. (2023, December). The Effectiveness of Parameterized Queries in Preventing SQL Injection Attacks at Go. In Proceedings of the International Conference on Enterprise and Industrial Systems (ICOEINS 2023) (Vol. 270, p. 204). Springer Nature.
- Strecansky, B. (2020). Hands-On High Performance with Go: Boost and Optimize the Performance of Your Golang Applications at Scale with Resilience. United Kingdom: Packt Publishing.
- Tengfei Tu, Xiaoyu Liu, Linhai Song, and Yiying Zhang. 2019. Understanding Real-World Concurrency Bugs in Go. In Proceedings of the Twenty-Fourth International Conference on Architectural Support for Programming Languages and Operating Systems (ASPLOS '19). Association for Computing Machinery, New York, NY, USA, 865–878. https://doi.org/10.1145/3297858.3304069
- The Go programming language. Documentation. https://golang.org/doc/ (2022)
* (По требованию Роскомнадзора информируем, что иностранное лицо, владеющее информационными ресурсами Google является нарушителем законодательства Российской Федерации – прим. ред.)