Как трассировка запросов с помощью open-source Glowroot помогает Atlassian инженеру?

 Как трассировка запросов с помощью open-source Glowroot помогает Atlassian инженеру?

Требования

Установка

Первичные анализы

Чем больше система, тем неожиданные сюрпризы

В качестве вывода

Ссылки


В настоящей статье расскажу историю о том, как можно столкнуться довольно простой проблемой именно на больших инсталляции в продуктов компании Atlassian, в частности на Jira. 

Фактически методами анализа и поиска узких мест :

  1. статистика

  2. сэмплирование

  3. профилирование и трассировка

В этой статье мы разберем трассировку запросов, которая показывает практически весь отрезок запроса, а именно от начала запроса с браузера клиента до перехода к обратному прокси, если он существует, к серверу приложений и от него до кэшей, поисковых индексов Lucene, СУБД. 

Важный момент — сложно провести анализ системы со стороны заказчика, так как с их стороны были жесткие требования по использованию инструментов, которые перечислены в требованиях.



Требования 

Расходы на APM-инструменты (инструменты для мониторинга производительности) изначально не были заложены в бюджете проекта, поэтому одним из главных критериев выбора стала бесплатность решения. Другой важный момент — нужно было предварительно проверить инструмент на наличие уязвимостей и вредоносного кода. Стало очевидно, что чтобы удовлетворить первые два критерия поиска, нужно искать open source решения. Еще одно требование — чтобы данные не выходили за пределы ограниченного контура, и была дополнительная возможность интеграции с существующей инфраструктурой.


Поэтому критерии выбора инструмента были следующими: 

  • бесплатный

  • установка на собственном инфраструктура (On-Premise)

  • открытый исходный код 

  • возможность интеграции с Elasticsearch (Opendistro)

  • совместимость с JVM (поскольку Atlassian написан в основном, на Java), или как javaagent

  • возможность связки запроса HTTP c SQL-запросы

  • наличие графиков в процентилях

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


 


В качестве отправной точки мы использовали сайт  https://openapm.io/landscape, который собрал практически все актуальные APM-инструменты. После проведения сравнительного анализа, мы остановили выбор на инструменте Glowroot, который удовлетворял всем критериям.


Установка 

Установка приложения довольно простая на агентах, и в данной статье показана схема работы со встроенным коллектором, но для промышленных инсталляции используется с Cassandra (как представлена на диаграмме)  со связкой с Elasticsearch.


  1. Скачиваем инсталлятор

wget -c https://github.com/glowroot/glowroot/releases/download/v0.13.6/glowroot-0.13.6-dist.zip

  1. Для проверки создаем директорию mkdir -p /jira/glowroot/tmp

  2. Что процесс Jira мог писать и устанавливаем права на владение для простоты, но рекомендуется просто выдать возможность записи владельцу процесса Jira.

chown -R jira: /jira/home/glowroot


  1. Устанавливаем дополнительно аргумент в setenv.sh в инсталляционной директории jira, в нашем случае это /jira/current/bin. По умолчанию, Атлассиан использует директорию /opt/atlassian/jira/bin в файле добавляем  следующую строку:

JVM_SUPPORT_RECOMMENDED_ARGS="-javaagent:/jira/glowroot/glowroot.jar ${JVM_SUPPORT_RECOMMENDED_ARGS}"


  1. После перезапускаем приложение: 

systemctl restart jira

  1. И мониторим на наличие ошибок посредством команды:

 tail -f {jira_installation_directory}/logs/catalina.out 

  1. Так как по умолчанию, биндится адрес 127.0.0.1, по идем в директорию в glowroot и смотрим, что появились файлы такие как admin.json.

В ней можете поменять bindAddress на 0.0.0.0 или на требуюмый вам.

В моем случае я добавлю contextPath - /glowroot

  1. И после на reverse proxy, устанавливаем 

location /glowroot {

                proxy_pass http://127.0.0.1:4000;

        }


Дополнительно можете посмотреть на приложенную документацию

https://github.com/glowroot/glowroot/wiki/Agent-Installation-(with-Embedded-Collector)


Как результат по нашему адресу, jira.example.com/glowroot видит транзакции.

Первичные анализы

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

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


Чем больше система, тем неожиданные сюрпризы

Посредством glowroot было обнаружено, то что при каждом изменении в задаче в проекту с количеством пользователей больше 30000 создавались при проверке прав временные таблицы на стороны СУБД. Ниже представлен trace, которые демонстрирует данную деятельность.


Как результат создаются временные таблицы на стороне СУБД PostgreSQL, а на кластере PostgreSQL эта активность сильно влияет на производительность в связи с высоким показателем IOPS.


Возникал вопрос почему кэширование на стороне приложения Jira не отрабатывало, ведь согласно thread dump информации показывает, что существует кэширование объектов.

Согласно stacktrace, был видно что реализация кэширования выполнена абсолютно удобно.

jdbc query: create temporary table temp142 (item varchar(900) primary key) => 0 rows

location stack trace

org.apache.commons.dbcp2.DelegatingStatement.executeUpdate(DelegatingStatement.java:236)

org.ofbiz.core.entity.jdbc.SQLProcessor.executeUpdate(SQLProcessor.java:602)

org.ofbiz.core.entity.GenericDAO$InQueryRewritter.generateTemporaryTable(GenericDAO.java:1704)

org.ofbiz.core.entity.GenericDAO$InQueryRewritter.createTemporaryTablesIfNeeded(GenericDAO.java:1616)

org.ofbiz.core.entity.GenericDAO.selectListIteratorByCondition(GenericDAO.java:855)

org.ofbiz.core.entity.GenericHelperDAO.findListIteratorByCondition(GenericHelperDAO.java:216)

org.ofbiz.core.entity.GenericDelegator.findListIteratorByCondition(GenericDelegator.java:1243)

com.atlassian.jira.ofbiz.DefaultOfBizDelegator.findListIteratorByCondition(DefaultOfBizDelegator.java:398)

com.atlassian.jira.ofbiz.WrappingOfBizDelegator.findListIteratorByCondition(WrappingOfBizDelegator.java:278)

com.atlassian.jira.crowd.embedded.ofbiz.OfBizUserDao.lambda$search$1(OfBizUserDao.java:852)

com.atlassian.jira.crowd.embedded.ofbiz.db.DefaultOfBizTransaction.lambda$withTransaction$0(DefaultOfBizTransaction.java:44)

com.atlassian.jira.crowd.embedded.ofbiz.db.DefaultOfBizTransaction.withTransaction(DefaultOfBizTransaction.java:28)

com.atlassian.jira.crowd.embedded.ofbiz.db.DefaultOfBizTransaction.withTransaction(DefaultOfBizTransaction.java:42)

com.atlassian.jira.crowd.embedded.ofbiz.db.DefaultOfBizTransactionManager.withTransaction(DefaultOfBizTransactionManager.java:9)

com.atlassian.jira.crowd.embedded.ofbiz.OfBizUserDao.search(OfBizUserDao.java:850)

com.atlassian.jira.crowd.embedded.ofbiz.DelegatingUserDao.search(DelegatingUserDao.java:123)

com.atlassian.jira.crowd.embedded.ofbiz.IndexedUserDao.search(IndexedUserDao.java:419)

com.atlassian.jira.crowd.embedded.ofbiz.DelegatingUserDao.search(DelegatingUserDao.java:123)

com.atlassian.jira.crowd.embedded.ofbiz.SwitchingUserDao.search(SwitchingUserDao.java:30)

com.atlassian.crowd.directory.AbstractInternalDirectory.searchUsers(AbstractInternalDirectory.java:785)

com.atlassian.crowd.manager.directory.DirectoryManagerGeneric.searchUsers(DirectoryManagerGeneric.java:329)

com.atlassian.crowd.manager.application.SingleDirectorySearchStrategy.searchUsers(SingleDirectorySearchStrategy.java:39)

com.atlassian.crowd.manager.application.InMemoryNonAggregatingSearchStrategy.findRemainingEntities(InMemoryNonAggregatingSearchStrategy.java:287)

com.atlassian.crowd.manager.application.InMemoryNonAggregatingSearchStrategy.removeNonCanonicalEntities(InMemoryNonAggregatingSearchStrategy.java:262)

com.atlassian.crowd.manager.application.InMemoryNonAggregatingSearchStrategy.searchNestedGroupRelationships(InMemoryNonAggregatingSearchStrategy.java:184)

com.atlassian.crowd.manager.application.ApplicationServiceGeneric.searchNestedGroupRelationships(ApplicationServiceGeneric.java:2280)

com.atlassian.crowd.embedded.core.CrowdServiceImpl.searchNestedGroupRelationships(CrowdServiceImpl.java:280)

com.atlassian.crowd.embedded.core.CrowdServiceImpl.search(CrowdServiceImpl.java:200)

com.atlassian.jira.security.groups.DefaultGroupManager.getAllUsersInGroup(DefaultGroupManager.java:178)

com.atlassian.jira.security.groups.DefaultGroupManager.getUsersInGroup(DefaultGroupManager.java:162)

com.atlassian.jira.security.groups.RequestCachingGroupManager.getUsersInGroup(RequestCachingGroupManager.java:118)

com.atlassian.jira.security.roles.actor.GroupRoleActorFactory$GroupRoleActor.getUsers(GroupRoleActorFactory.java:88)

com.atlassian.jira.security.roles.DefaultRoleActorsImpl.getUsers(DefaultRoleActorsImpl.java:43)

com.atlassian.jira.security.roles.CachingProjectRoleAndActorStore$CachedRoleActors.getUsers(CachingProjectRoleAndActorStore.java:310)

com.atlassian.jira.notification.type.ProjectRoleSecurityAndNotificationType.getUsersFromRole(ProjectRoleSecurityAndNotificationType.java:178)

com.atlassian.jira.notification.type.ProjectRoleSecurityAndNotificationType.getUsers(ProjectRoleSecurityAndNotificationType.java:115)

com.atlassian.jira.permission.DefaultPermissionSchemeManager.getUsers(DefaultPermissionSchemeManager.java:558)

com.atlassian.jira.permission.WorkflowBasedPermissionSchemeManager.getUsers(WorkflowBasedPermissionSchemeManager.java:87)

com.atlassian.jira.permission.WorkflowBasedPermissionSchemeManager.getUsers(WorkflowBasedPermissionSchemeManager.java:82)

com.atlassian.jira.scheme.AbstractSchemeManager.getUsers(AbstractSchemeManager.java:687)

sun.reflect.GeneratedMethodAccessor1256.invoke()

sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)

java.lang.reflect.Method.invoke(Method.java:498)

com.atlassian.plugin.util.ContextClassLoaderSettingInvocationHandler.invoke(ContextClassLoaderSettingInvocationHandler.java:26)

com.sun.proxy.$Proxy303.getUsers()


При изучении stacktrace было обнаружено, что метод createTemporaryTablesIfNeeded, влиял на эту деградацию по производительности. 

И нижеприведенный пулл реквест изменил поведение приложения,  https://bitbucket.org/atlassian/entity-engine/pull-requests/32/stable-issue-stable-issue-raid-438-in/diff, как следствие мы столкнулись на большой инсталляции с этой проблемой.

Причиной изменения было ограничения неправильно настроенных проектов, и ограничение действительно больших проектов. Что данное решение является довольно прагматичным решением в программной инженерии.


В качестве вывода

Как мы видим из примера изменения в entity engine, вендор также беспокоится за продукт и с удовольствием принимает запросы на улучшение, если это относится к его компоненте. 

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

Где фактически у нас были варианты следующие по устранению поведения приложения для большого количества пользователей:

  • Изменить константу и пересобрать компонент

  • Изменить схему прав безопасности на уровне приложения и провести очистку и полное ревью

  • Переделать на unlogged таблицу cwd_users в PostgreSQL


Подскажи, как вы делаете подобные упражнения? Как много времени потратили на установку, настройку инструментов и выявление проблем? 

Буду рад вашим вопросам.

Хорошего дня.

Ссылки



Comments

Popular posts from this blog

How only 2 parameters of PostgreSQL reduced anomaly of Jira Data Center nodes

Atlassian Community, let's collaborate and provide stats to vendors about our SQL index usage

How do you analyze GC logs, thread dumps and head dumps?