Возврат уведомления SagePay (платежный шлюз) занимает много времени в приложении ASP.Net MVC
На нашем веб-сайте возникают проблемы с производительностью, связанные с высокой загрузкой процессора. При использовании профилировщика мы определили конкретный метод, для возврата из которого требуется ~35 секунд.
Это метод обратного вызова при использовании платежного шлюза под названием SagePay.
Я скопировал два метода, которые являются частью этого вызова ниже:
public void SagePayNotificationReturn()
{
string vendorTxCode = Request.Form["vendortxcode"];
var sagePayTransaction = this.sagePayTransactionManager.GetTransactionByVendorTxCode(vendorTxCode);
if (sagePayTransaction == null)
{
// Cannot find the order, so log an error and return error response
int errorId = this.exceptionManager.LogException(System.Web.HttpContext.Current.Request, new Exception(string.Format("Could not find SagePay transaction for order {0}.", vendorTxCode)));
ReturnResponse(System.Web.HttpContext.Current, StatusEnum.ERROR, string.Format("{0}home/error/{1}", GlobalSettings.SiteURL, errorId), string.Format("Received notification for {0} but the transaction was not found.", vendorTxCode));
}
else
{
// Store the response and respond immediately to SagePay
sagePayTransaction.NotificationValues = sagePayTransactionManager.FormValuesToQueryString(Request.Form);
this.sagePayTransactionManager.Save(sagePayTransaction);
ReturnResponse(System.Web.HttpContext.Current, StatusEnum.OK, string.Format("{0}payment/processtransaction/{1}", GlobalSettings.SiteURL, vendorTxCode), string.Empty);
}
}
private void ReturnResponse(HttpContext context, StatusEnum status, string redirectUrl, string statusDetail)
{
context.Response.Clear();
context.Response.ContentEncoding = Encoding.UTF8;
using (StreamWriter streamWriter = new StreamWriter(context.Response.OutputStream))
{
streamWriter.WriteLine(string.Concat("Status=", status.ToString()));
streamWriter.WriteLine(string.Concat("RedirectURL=", redirectUrl));
streamWriter.WriteLine(string.Concat("StatusDetail=", HttpUtility.HtmlEncode(statusDetail)));
streamWriter.Flush();
streamWriter.Close();
}
context.ApplicationInstance.CompleteRequest();
}
Метод GetTransactionByVendorTxCode - это простой вызов Entity Framework, поэтому я исключил это.
Кто-нибудь имеет опыт в этом или они могут увидеть что-то явно не так с кодом, который может вызвать такую проблему?
РЕДАКТИРОВАТЬ: Глядя на таблицу разбивки, предоставленную профилировщиком, он говорит, что 99,6% времени тратится в System.Web.Mvc.MvcHandler.BeginProcessRequest().
РЕДАКТИРОВАТЬ: Используя инструмент профилирования New Relic, он говорит, что 22% всего времени обработки тратится в методе this.sagePayTransactionManager.GetTransactionByVendorTxCode(vendorTxCode). Это просто содержит вызов EF6 в хранилище. Однако этот вызов содержит параметр предиката, а не предопределенное условие. Может ли быть так, что запрос не был предварительно скомпилирован?
2 ответа
Вот мой первый шаг к решению:
Перед запуском этого оператора вставьте таймер, а затем завершите его. Расскажите нам время.
var sagePayTransaction = this.sagePayTransactionManager.GetTransactionByVendorTxCode(vendorTxCode);
Вставьте другой таймер для этого блока кода: сообщите нам относительное время по сравнению с методом выше.
using (StreamWriter streamWriter = new StreamWriter(context.Response.OutputStream))
{
streamWriter.WriteLine(string.Concat("Status=", status.ToString()));
streamWriter.WriteLine(string.Concat("RedirectURL=", redirectUrl));
streamWriter.WriteLine(string.Concat("StatusDetail=", HttpUtility.HtmlEncode(statusDetail)));
streamWriter.Flush();
streamWriter.Close();
}
Наконец, установите здесь еще один таймер:
context.ApplicationInstance.CompleteRequest();
Отправьте нам информацию, и я направлю вас к следующему шагу. То, что мы делаем выше, это получение метрик, которые будут охватывать как локальный, так и удаленный доступ, чтобы найти основную проблему. Сначала мы это выберем, а затем продвинемся дальше, если потребуется. Просто скажите нам, что это за измерения.
Есть ряд вещей, которые вы, возможно, должны рассмотреть здесь.
Если GetTransactionByVendorTxCode отвечает за 22% общего времени обработки, то вам нужно будет упростить все, что касается метода, но затем все же продолжить поиск других узких мест в общем конвейере обработки.
Вы говорите, что метод абстрагирует вызов EF6 и передает ему предикатное выражение, которое используется в предложении Where для построения окончательного запроса.
Если запрос сложный, рассматривали ли вы делегирование в StoredProcedure? Поскольку вы возвращаете сущность, вы можете повесить ее на DbSet. (В случае DTO он будет зависать от свойства Database объекта DbContext).
Также вам нужно взглянуть на индексы столбцов, используемых в вашем предикате. Каков текущий счетчик записей? Ваши запросы приводят к поискам или сканированию? Вам нужно будет посмотреть на итоговые планы запросов; при использовании SQL Server запустите ваши запросы помощника по настройке ядра СУБД.
Возможно, чуть более подробная информация о вашей текущей настройке поможет вам лучше ориентироваться.