Возврат уведомления 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 запустите ваши запросы помощника по настройке ядра СУБД.

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

Другие вопросы по тегам