Оптимизация операций базы данных с помощью OpenTelemetry

Оптимизация операций базы данных с помощью OpenTelemetry

24 мая 2022 г.

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


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


В этой статье мы рассмотрим некоторые распространенные проблемы с производительностью базы данных, чтобы понять, как мы можем использовать OpenTelemetry для их легкого выявления и устранения. Для практического обучения мы создадим простое приложение, использующее базу данных SQL Server. Мы снабдим приложение стандартными библиотеками OpenTelemetry и подключим приложение к платформе загрузки Lightstep. Наконец, мы будем использовать полученную телеметрию для выявления проблем с базой данных и обсуждения шагов по их устранению.


Основы наблюдаемости и OpenTelemetry


Если вы не знакомы с наблюдаемостью или OpenTelemetry, я рекомендую ознакомиться с предыдущий пост в этой серии «OpenTelemetry в действии». В этом посте мы рассмотрим три типа информации, собираемой в наблюдаемом приложении (журналы, метрики и трассировки). Мы также рассмотрим ключевые компоненты модели данных OpenTelemetry.


Вы можете обратиться к этой документации для получения более подробной информации о том, как работает OpenTelemetry.


Использование OpenTelemetry для мониторинга баз данных


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


Из-за ограничений доступа или характера вашей платформы вам может быть запрещено устанавливать библиотеки мониторинга на сервере базы данных. Вместо этого вы можете использовать инструментарий OpenTelemetry для мониторинга базы данных со стороны клиента. Хотя инструментарий не даст вам понимания внутреннего устройства базы данных, он предоставит вам достаточно информации для устранения проблем с производительностью, чтобы улучшить взаимодействие с пользователем приложения.


Использование OpenTelemetry для обнаружения проблем с производительностью базы данных


Настройка нашего проекта для обнаружения проблем с производительностью базы данных очень похожа на [нашу настройку в первой части] (https://hackernoon.com/how-to-use-opentelemetry-to-identify-database-dependencies), в которой мы использовали OpenTelemetry. для определения зависимостей базы данных.


Опять же, мы будем использовать инструментарий .NET SQLClient для OpenTelemetry и [Lightstep] (https://lightstep.com/) для хранения и анализа телеметрии.


Экспорт трассировок OTEL из приложения .NET


Мы снабдим наше приложение SDK OpenTelemetry для генерации сигналов наблюдаемости. Мы будем использовать средство экспорта OpenTelemetry Protocol (OTLP) для отправки данных в Lightstep, агрегирования наших трассировок и предоставления нам информационных панелей для анализа для получения информации.


Демонстрация


Для нашей демонстрации мы создадим простую службу управления сотрудниками (EMS), смоделированную как минимальный API ASP.NET Core. Наш API имеет следующие конечные точки:


  1. POST /ems/pay/{employeeId}: вычисляет заработную плату сотрудника на основе часов, которые он провел в различных проектах. Эта конечная точка будет демонстрировать диалоговое взаимодействие с базой данных.

  1. POST /ems/billing/pay-raise: обновляет заработную плату каждого сотрудника, зарабатывающего менее 300 долларов США, до 300 долларов США. Эта конечная точка будет отображать запрос к неиндексированному полю в базе данных.

  1. POST /ems/payroll/remove/{employeeId}: удаляет сотрудника из платежной ведомости. Эта конечная точка покажет, как блокировки базы данных влияют на производительность запросов.

  1. POST /ems/add-employee/{employeeId}: добавляет сотрудника в системы начисления заработной платы и учета рабочего времени. Это моделирует, как производительность бизнес-транзакции, охватывающей несколько служб, и, следовательно, несколько вызовов базы данных, может повлиять на производительность системы.

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


База данных приложения состоит из двух таблиц: «Зарплата» и «Табель учета рабочего времени», в которых сохраняется ставка заработной платы сотрудников и часы, отработанные над проектом.


Полный исходный код этого приложения EMS доступен в этом [репозитории GitHub] (https://github.com/rahulrai-in/otel-db-samples/tree/main/db-optimizations).


Раскрутка базы данных


Начнем с запуска экземпляра SQL-сервера в докере:


``` ударить


докер запустить \


-e "ПРИНЯТЬ_EULA=Y" \


-e "SA_PASSWORD=Str0ngPa$$w0rd" \


-p 1433:1433 \


--name монолит-БД \


--имя хоста sql1 \


-d mcr.microsoft.com/mssql/server:2019-последняя


Затем мы создаем базу данных EMS и таблицы, используемые нашим приложением, а также сохраняем некоторые начальные данные:


```sql


ЕСЛИ НЕ СУЩЕСТВУЕТ (ВЫБЕРИТЕ * ИЗ sys.databases, ГДЕ имя = 'EMSDb')


НАЧИНАТЬ


СОЗДАТЬ БАЗУ ДАННЫХ EMSDb


КОНЕЦ


ИДТИ


ИСПОЛЬЗОВАТЬ EMSDb


ЕСЛИ OBJECT_ID('[dbo].[Хранение времени]', 'U') IS NULL


НАЧИНАТЬ


СОЗДАТЬ ТАБЛИЦУ [Хранение времени] (


[EmployeeId] INT NOT NULL,


[ProjectId] INT NOT NULL,


[WeekClosingDate] DATETIME NOT NULL,


[Часы работы] INT NOT NULL,


ОГРАНИЧЕНИЕ [PK_Timekeeping] ПЕРВИЧНЫЙ КЛЮЧ CLUSTERED ([EmployeeId] ASC, [ProjectId] ASC, [WeekClosingDate] ASC)


КОНЕЦ


ИДТИ


ЕСЛИ OBJECT_ID('[dbo].[Зарплатная ведомость]', 'U') IS NULL


НАЧИНАТЬ


СОЗДАТЬ ТАБЛИЦУ [Зарплата] (


[EmployeeId] INT NOT NULL,


[PayRateInUSD] ДЕНЬГИ ПО УМОЛЧАНИЮ 0 НЕ NULL,


ОГРАНИЧЕНИЕ [PK_Payroll] ПЕРВИЧНЫЙ КЛЮЧ CLUSTERED ([EmployeeId] ASC)


КОНЕЦ


ИДТИ


TRUNCATE TABLE Заработная плата


TRUNCATE TABLE Хронометраж


ВСТАВЬТЕ В Значения заработной платы (1, 100)


ВСТАВЬТЕ В Значения заработной платы (2, 200)


ВСТАВЬТЕ В Значения заработной платы (3, 300)


ВСТАВИТЬ В Значения хронометража (1, 1111, GETDATE(), 10)


ВСТАВЬТЕ В Значения хронометража (1, 2222, GETDATE(), 15)


ВСТАВИТЬ В Значения хронометража (2, 1111, GETDATE(), 15)


ВСТАВЬТЕ В Значения хронометража (3, 2222, GETDATE(), 20)


ИДТИ


Реализация конечных точек API


Затем мы пишем код для конечных точек API. Мы заменяем шаблонный код в классе Program следующим:


```csharp


используя System.Data.SqlClient;


с помощью System.Diagnostics;


с помощью Даппера;


с помощью OpenTelemetry.Exporter;


используя OpenTelemetry.Resources;


с помощью OpenTelemetry.Trace;


var builder = WebApplication.CreateBuilder(аргументы);


var lsToken = builder.Configuration.GetValue("LsToken");


builder.Services.AddScoped(_ =>


новое SqlConnection(


builder.Configuration.GetConnectionString("EmployeeDbConnectionString")


builder.Services.AddEndpointsApiExplorer();


builder.Services.AddSwaggerGen();


var app = builder.Build();


приложение.UseSwagger();


приложение.UseSwaggerUI();


app.MapGet("/ems/pay/{empId}", async (int empId, SqlConnection db) =>


// оп 1


вар платежная ведомость =


await db.QuerySingleOrDefaultAsync("SELECT EmployeeId,PayRateInUSD FROM Payroll WHERE EmployeeId=@EmpId",


новый {EmpId = empId});


// оп 2


var project = await db.QueryAsync("SELECT EmployeeId, ProjectId, WeekClosingDate, HoursWorked FROM Timekeeping WHERE EmployeeId=@EmpId",


новый {EmpId = empId});


var moneyEarned = Projects.Sum(p => p.HoursWorked) * payroll.PayRateInUSD;


вернуть Results.Ok (заработанные деньги);


.WithName("ПолучитьПлатеж")


.Produces(StatusCodes.Status200OK);


app.MapPost("/ems/billing/pay-raise/", async (SqlConnection db) =>


var recordsAffected = await db.ExecuteAsync ("ОБНОВЛЕНИЕ расчета заработной платы SET PayRateInUSD = 300, ГДЕ PayRateInUSD < 300");


вернуть Results.Ok (затронутые записи);


.WithName("Повышение заработной платы")


.Produces(StatusCodes.Status200OK);


app.MapPost("/ems/payroll/remove/{empId}", async (int empId, SqlConnection db) =>


Запись платежной ведомости = new();


асинхронная задача DeleteRecord()


БД.Открыть();


await с использованием var tr = await db.BeginTransactionAsync();


await db.ExecuteAsync("УДАЛИТЬ ИЗ Расчета заработной платы, ГДЕ EmployeeId=@EmpId", new {EmpId = empId}, tr);


Thread.Sleep(5000);


ждать tr.CommitAsync();


асинхронная задача GetRecord()


ждите, используя var db1 =


новое SqlConnection(builder.Configuration.GetConnectionString("EmployeeDbConnectionString"));


Нить.Сон(100);


db1.Открыть();


Запись платежной ведомости =


await db1.QuerySingleOrDefaultAsync<Зарплата>(


"ВЫБЕРИТЕ EmployeeId,PayRateInUSD FROM Payroll WHERE EmployeeId=@EmpId", new {EmpId = empId});


ожидание db1.CloseAsync();


await Task.WhenAll(DeleteRecord(), GetRecord());


вернуть Results.Ok(payrollRecord);


.WithName("RemoveEmployeeFromPayroll")


.Produces(StatusCodes.Status200OK);


app.MapPost("/ems/add-employee/{empId}", async (int empId, SqlConnection db) =>


//оп 1


await db.ExecuteAsync("ВСТАВИТЬ В ЗНАЧЕНИЯ ЗАРПЛАТЫ(@EmployeeId, @PayRateInUSD)",


новая платежная ведомость {EmployeeId = empId, PayRateInUSD = 100});


// Моделирование вызова службы


// Имитация задержки сетевого вызова


Thread.Sleep(1000);


//оп 2


ожидание db.ExecuteAsync(


«ВСТАВИТЬ В Значения хронометража (@EmployeeId, @ProjectId, @WeekClosingDate, @HoursWorked)»,


новый хронометраж


{EmployeeId = empId, HoursWorked = 0, ProjectId = 1, WeekClosingDate = DateTime.Today});


вернуть Результаты.Ок(); })


.WithName("ДобавитьСотрудника")


.Produces(StatusCodes.Status201Created);


приложение.Выполнить();


публичный класс Хронометраж


публичный идентификатор сотрудника {получить; набор; }


общественный интервал ProjectId { получить; набор; }


общественное DateTimeWeekClosingDate { получить; набор; }


общественность int HoursWorked { получить; набор; }


публичный класс


публичный идентификатор сотрудника {получить; набор; }


общественная десятичная PayRateInUSD { получить; набор; }


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


Добавление инструментария


Мы оснастили приложение [OpenTelemetry SDK] (https://github.com/open-telemetry/opentelemetry-dotnet/tree/main/src/OpenTelemetry.Instrumentation.SqlClient) и [библиотекой инструментов SqlClient для .NET] (https ://github.com/open-telemetry/opentelemetry-dotnet/tree/main/src/OpenTelemetry.Instrumentation.SqlClient). Во-первых, мы добавляем следующие ссылки на пакеты NuGet в файл проекта API:








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


Затем мы используем OpenTelemetry в нашем приложении. Это также заставит SqlClient выдавать подробную телеметрию. Эта телеметрия будет ключом к выявлению проблем с производительностью базы данных.


```csharp


// Настроить трассировку


builder.Services.AddOpenTelemetryTracing (строитель => строитель


// Настраиваем трассировки, собранные обработчиком HTTP-запросов


.AddAspNetCoreInstrumentation(параметры =>


// Захватите только промежутки, сгенерированные из конечных точек ems/*


options.Filter = context => context.Request.Path.Value?.Contains("ems") ?? ЛОЖЬ;


options.RecordException = истина;


// Добавьте метаданные для запроса, такие как метод HTTP и длина ответа


options.Enrich = (активность, eventName, rawObject) =>


переключатель (имя события)


случай «OnStartActivity»:


если (rawObject не является HttpRequest httpRequest)


возврат;


Activity.SetTag("Протокол запроса", httpRequest.Protocol);


Activity.SetTag («Метод запроса», httpRequest.Method);


перемена;


случай "OnStopActivity":


если (rawObject является HttpResponse httpResponse)


Activity.SetTag("responseLength", httpResponse.ContentLength);


перемена;


// Настраиваем телеметрию, сгенерированную SqlClient


.AddSqlClientInstrumentation(параметры =>


options.EnableConnectionLevelAttributes = true;


options.SetDbStatementForStoredProcedure = true;


options.SetDbStatementForText = true;


options.RecordException = истина;


options.Enrich = (активность, x, y) => activity.SetTag("db.type", "sql");


.AddSource("my-corp.ems.ems-api")


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


.SetResourceBuilder(ResourceBuilder.CreateDefault().AddService("ems-api")


.AddAttributes(new[] { new KeyValuePair("service.version", "1.0.0.0")}))


// Гарантирует, что все действия записываются и отправляются экспортеру


.SetSampler(новый AlwaysOnSampler())


// Экспорт отрезков в Lightstep


.AddOtlpExporter(otlpOptions =>


otlpOptions.Endpoint = новый Uri("https://ingest.lightstep.com:443/traces/otlp/v0.9");


otlpOptions.Headers = $"lightstep-access-token={lsToken}";


otlpOptions.Protocol = OtlpExportProtocol.HttpProtobuf;


Хотя в текущем состоянии нам достаточно инструментария, давайте еще больше обогатим данные, добавив соответствующие трассировки.


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


```csharp


var activitySource = новый ActivitySource("my-corp.ems.ems-api");


Затем мы создаем диапазон и добавляем соответствующие детали — атрибуты и события:


```csharp


app.MapGet("/ems/pay/{empId}", async (int empId, SqlConnection db) =>


используя var activity = activitySource.StartActivity("Операция Chatty db", ActivityKind.Server);


Activity?.SetTag(nameof(Timekeeping.EmployeeId), empId);


// оп 1


вар платежная ведомость =


await db.QuerySingleOrDefaultAsync("SELECT EmployeeId,PayRateInUSD FROM Payroll WHERE EmployeeId=@EmpId",


новый {EmpId = empId});


// оп 2


var project = await db.QueryAsync("SELECT EmployeeId, ProjectId, WeekClosingDate, HoursWorked FROM Timekeeping WHERE EmployeeId=@EmpId",


новый {EmpId = empId});


var moneyEarned = Projects.Sum(p => p.HoursWorked) * payroll.PayRateInUSD;


вернуть Results.Ok (заработанные деньги);


.WithName("ПолучитьПлатеж")


.Produces(StatusCodes.Status200OK);


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


Отправка инструментальных данных в Lightstep


Чтобы подключить наше приложение к Lightstep для приема данных, нам понадобится ключ API. Сначала мы создаем учетную запись в Lightstep. Затем со страницы настроек проекта мы копируем токен, который является нашим ключом API.


Ваш ключ API в Lightstep


Мы вставляем этот токен в наш файл appsettings.


```json


"Логирование": {


«Уровень журнала»: {


"По умолчанию": "Информация",


"Microsoft.AspNetCore": "Предупреждение"


"Разрешенные хосты": "*",


«Строки подключения»: {


"EmployeeDbConnectionString": "Сервер=localhost;База данных=EMSDb;Идентификатор пользователя=sa;Пароль=Str0ngPa$$w0rd;"


"LsToken": "<токен Lightstep>"


Поиск распространенных проблем с базой данных


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


Болтливое/последовательное взаимодействие с базой данных


Вернемся к конечной точке /ems/pay/{empId}`. Изучив приведенный выше код, вы увидите, что эта конечная точка выполняет два обращения к базе данных один за другим.


Неоптимальные, болтливые вызовы базы данных замедляют пользовательские транзакции. Конечно, вы столкнетесь со случаями, когда вам нужно прочитать запись, принять решение на основе состояния записи, а затем обновить запись. В таких случаях неизбежны множественные вызовы базы данных. Однако для извлечения записей почти всегда можно использовать один запрос. Мы запускаем приложение (dotnet run) и отправляем несколько запросов на конечную точку /ems/pay/{empId}. Вот пример запроса, который я отправил на конечную точку:


Запрос к конечной точке оплаты


Затем мы переходим на [портал наблюдения Lightstep] (https://app.lightstep.com/) и нажимаем на вкладку «Операции», чтобы увидеть все промежутки, полученные Lightstep из приложения.


Список операций


Мы нажимаем на операцию /ems/pay/{employeeId}`, чтобы просмотреть ее сквозную трассировку. Просматривая промежутки, мы можем установить последовательность операций для любого запроса, включая взаимодействие с базой данных. Нажатие на промежутки вызывает его события и атрибуты, что дает нам более глубокое понимание операции.


Последние два диапазона, видимые в трассировке, — это EMSDb, сгенерированные нашим инструментальным SQL-клиентом. Мы нажимаем на промежутки, чтобы просмотреть их атрибуты и события. Они выглядят так:


Span захвачен из первой операции с базой данных


Span получен из второй операции с базой данных


Из этих деталей мы можем сделать некоторые важные выводы:


  1. Название базы данных

  1. Оператор SQL, используемый в операции с базой данных

  1. Тип оператора SQL (текст или хранимая процедура)

  1. Имя хоста службы, которая сделала запрос

  1. Продолжительность работы базы данных

Болтливое поведение конечной точки очень заметно из трассировки. Мёртвой раздачей является наличие двух операций с базой данных в операции чтения и отсутствие настраиваемых трассировок между ними для записи какой-либо бизнес-логики. Чтобы решить эту проблему, нам нужно объединить запросы, чтобы они давали единый набор результатов.


Неоптимизированные запросы


Производительность запросов — важная метрика, за которой нужно следить. Если запросы запрашивают большой объем данных (например, с использованием подхода SELECT * или фильтрации данных в неиндексированных полях), производительность пользовательских транзакций будет страдать. Вернемся к конечной точке /emp/billing/pay-raise:


```csharp


app.MapPost("/ems/billing/pay-raise/", async (SqlConnection db) =>


используя var activity = activitySource.StartActivity("Неоптимизированный запрос", ActivityKind.Server);


var recordsAffected = await db.ExecuteAsync ("ОБНОВЛЕНИЕ расчета заработной платы SET PayRateInUSD = 300, ГДЕ PayRateInUSD < 300");


вернуть Results.Ok (затронутые записи);


.WithName("Повышение заработной платы")


.Produces(StatusCodes.Status200OK);


Запрос фильтрует записи в поле PayRateInUSD , которые мы не проиндексировали. Хотя эта проблема не будет проявляться в базе данных с небольшим количеством записей, такие запросы в больших базах данных будут занимать много времени. Время запроса отображается в трассировках, собранных для конечной точки, как показано ниже:


Span представляет продолжительность запроса


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


Блокировки базы данных — одна из самых сложных проблем, потому что они влияют только на операцию, ожидающую снятия блокировки. Между тем, операция получения блокировки продолжается без изменений. Однако OpenTelemetry упрощает обнаружение блокировок базы данных, поскольку мы можем просмотреть операцию-виновника и операцию, ожидающую блокировку, в одной и той же трассировке. Давайте обсудим код нашей проблемной конечной точки в /ems/payroll/remove/{empId}:


```csharp


app.MapPost("/ems/payroll/remove/{empId}", async (int empId, SqlConnection db) =>


используя var activity = activitySource.StartActivity("Блокировка базы данных", ActivityKind.Server);


Activity?.SetTag(nameof(Timekeeping.EmployeeId), empId);


Запись платежной ведомости = new();


асинхронная задача DeleteRecord()


БД.Открыть();


await с использованием var tr = await db.BeginTransactionAsync();


await db.ExecuteAsync("УДАЛИТЬ ИЗ Расчета заработной платы, ГДЕ EmployeeId=@EmpId", new {EmpId = empId}, tr);


Thread.Sleep(5000);


ждать tr.CommitAsync();


асинхронная задача GetRecord()


ждите, используя var db1 =


новое SqlConnection(builder.Configuration.GetConnectionString("EmployeeDbConnectionString"));


Нить.Сон(100);


db1.Открыть();


Запись платежной ведомости =


await db1.QuerySingleOrDefaultAsync<Зарплата>(


"ВЫБЕРИТЕ EmployeeId,PayRateInUSD FROM Payroll WHERE EmployeeId=@EmpId", new {EmpId = empId});


ожидание db1.CloseAsync();


await Task.WhenAll(DeleteRecord(), GetRecord());


вернуть Results.Ok(payrollRecord);


.WithName("RemoveEmployeeFromPayroll")


.Produces(StatusCodes.Status200OK);


Операция `DELETE начинает транзакцию, но не фиксирует ее в течение некоторого времени. Эта операция блокирует запись, которая будет удалена после фиксации транзакции. Дав транзакцию DELETE небольшое преимущество, мы выполняем операцию SELECT для чтения той же записи. Операция SELECT не может быть продолжена, пока операция DELETE не снимет блокировку. Это ясно видно из трассировки OpenTelemetry этой операции:


Span представляет продолжительность запроса


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


Решение проблемы — немедленно зафиксировать транзакцию, не дожидаясь завершения каких-либо длительных операций.


Бизнес-транзакции, охватывающие несколько сервисов


Если транзакции вашего пользователя охватывают несколько сервисов, вам следует измерить время отклика различных частей приложения и сети. Конечная точка /ems/add-employee/{empId}` имитирует бизнес-транзакцию, распределенную по двум службам, следующим образом:


```csharp


app.MapPost("/ems/add-employee/{empId}", async (int empId, SqlConnection db) =>


используя переменную активность =


ActivitySource.StartActivity("Несколько операций в бизнес-транзакции", ActivityKind.Server);


Activity?.SetTag(nameof(Timekeeping.EmployeeId), empId);


//оп 1


await db.ExecuteAsync("ВСТАВИТЬ В ЗНАЧЕНИЯ ЗАРПЛАТЫ(@EmployeeId, @PayRateInUSD)",


новая платежная ведомость {EmployeeId = empId, PayRateInUSD = 100});


// Имитация вызова службы путем создания другого диапазона


используя var innerActivity = activitySource.StartActivity("Вторая операция бизнес-транзакции", ActivityKind.Server);


// Имитация задержки сетевого вызова


Thread.Sleep(1000);


//оп 2


ожидание db.ExecuteAsync(


«ВСТАВИТЬ В Значения хронометража (@EmployeeId, @ProjectId, @WeekClosingDate, @HoursWorked)»,


новый хронометраж


{EmployeeId = empId, HoursWorked = 0, ProjectId = 1, WeekClosingDate = DateTime.Today});


вернуть Результаты.Ок();


.WithName("ДобавитьСотрудника")


.Produces(StatusCodes.Status201Created);


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


Бизнес-операции, требующие связи между двумя службами


Решение таких вопросов может быть разным по сложности. В качестве простого решения вы можете оптимизировать производительность сети, разместив службы физически близко друг к другу (например, в одном центре обработки данных). Более сложный подход заключается в том, чтобы переделать приложение, чтобы оно в конечном итоге было согласованным, чтобы вы могли асинхронно выполнять транзакцию.


Исключения базы данных


Наш клиент SQL предназначен для захвата исключений. Lightstep понимает, когда промежутки содержат сведения об исключении, выделяя такие операции на панели инструментов. Мы используем конечную точку /ems/add-employee/{empId}`, чтобы вставить дубликат записи в базу данных, что вызывает исключение.


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


Исключения выделены в окне проводника


При нажатии на операцию отображаются сведения об исключении, захваченные как событие.


Подробности об исключениях зафиксированы как событие


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


Вывод


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


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


Первоначально опубликовано [здесь] (https://thecloudblog.net/post/opentelemetry-in-action-optimizing-database-operations/)



Оригинал
PREVIOUS ARTICLE
NEXT ARTICLE