Как реализовать алгоритм на основе множеств / UDF

13

У меня есть алгоритм, который мне нужно запустить для каждой строки в таблице с 800K строк и 38 столбцов. Алгоритм реализован в VBA и выполняет математические вычисления, используя значения из некоторых столбцов для манипулирования другими столбцами.

В настоящее время я использую Excel (ADO) для запроса SQL и использую VBA с курсорами на стороне клиента, чтобы применить алгоритм по циклам через каждую строку. Работает, но работает 7 часов.

Код VBA является достаточно сложным, поэтому для его перекодировки в T-SQL потребуется много работы.

Я читал об интеграции CLR и UDF как возможных маршрутах. Я также подумал о том, чтобы поместить код VBA в задачу сценария SSIS, чтобы приблизиться к базе данных, но я уверен, что существует экспертная методология для такого типа проблем с производительностью.

В идеале я мог бы запустить алгоритм для максимально возможного числа строк (всех?) В параллельном множестве.

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

--Редактировать

Спасибо за комментарии, я использую MS SQL 2014 Enterprise, вот еще несколько деталей:

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

Мой вопрос больше о методологии, чем о реальном алгоритме: если я хочу добиться параллельных вычислений сразу для нескольких строк, какие у меня варианты?

Я вижу, что рекомендуется перекодировать в T-SQL, но это большая работа, но возможная, однако разработчик алгоритма работает в VBA и часто меняется, поэтому мне нужно синхронизироваться с версией T-SQL и повторно проверять каждый сдача.

Является ли T-SQL единственным способом реализации функций на основе множеств?

medwar19
источник
3
SSIS может предложить некоторое собственное распараллеливание, если вы хорошо спланируете поток данных. Это задача, которую вы будете искать, так как вам нужно выполнить этот построчный расчет. Но это говорит о том, что, если вы не можете дать нам конкретику (схему, используемые вычисления и то, что эти вычисления надеются выполнить), вам невозможно помочь оптимизировать. Говорят, что написание чего-либо на ассемблере может привести к самому быстрому коду, но если, как и я, вы ужасно отстой, он не будет эффективным вообще
billinkc
2
Если вы обрабатываете каждую строку независимо, то вы можете разбить 800К строк на Nпакеты и запустить Nэкземпляры вашего алгоритма на Nотдельных процессорах / компьютерах. С другой стороны, каково ваше основное узкое место - перенос данных из SQL Server в Excel или фактические вычисления? Если вы измените функцию VBA для немедленного возврата фиктивного результата, сколько времени займет весь процесс? Если это все еще занимает часы, узкое место в передаче данных. Если это занимает секунды, то вам нужно оптимизировать код VBA, который выполняет вычисления.
Владимир Баранов
Это фильтр, который вызывается как хранимая процедура: SELECT AVG([AD_Sensor_Data]) OVER (ORDER BY [RowID] ROWS BETWEEN 5 PRECEDING AND 5 FOLLOWING) as 'AD_Sensor_Data' FROM [AD_Points] WHERE [FileID] = @FileID ORDER BY [RowID] ASC в Management Studio эта функция, которая вызывается для каждой строки, занимает 50 мсек
medwar19
1
Таким образом, запрос, который занимает 50 мс и выполняется 800000 раз (11 часов), занимает много времени. Является ли @FileID уникальным для каждой строки или есть дубликаты, чтобы вы могли минимизировать количество раз, необходимое для выполнения запроса? Вы также можете предварительно рассчитать скользящее среднее для всех файловых файлов в промежуточную таблицу за один раз (использовать раздел по FileID), а затем запросить эту таблицу без необходимости использования функции окна для каждой строки. Лучшая настройка для промежуточной таблицы выглядит так, как будто она должна быть с кластерным индексом (FileID, RowID).
Микаэль Эрикссон
1
Лучше всего было бы, если бы вы как-то избавились от необходимости касаться БД для каждой строки. Это означает, что вам нужно либо перейти на TSQL и, возможно, присоединиться к скользящему среднему запросу, либо получить достаточно информации для каждой строки, поэтому все, что нужно алгоритму, находится прямо в строке, возможно, закодировано каким-либо образом, если задействовано несколько дочерних строк (xml) ,
Микаэль Эрикссон

Ответы:

8

Что касается методологии, я полагаю, что вы лаете не на то б-дерево ;-).

Что мы знаем:

Сначала давайте обобщим и рассмотрим, что мы знаем о ситуации:

  • Несколько сложные вычисления должны быть выполнены:
    • Это должно происходить в каждой строке этой таблицы.
    • Алгоритм часто меняется.
    • Алгоритм ... [использует] значения из некоторых столбцов для манипулирования другими столбцами
    • Текущее время обработки: 7 часов
  • Таблица:
    • содержит 800 000 строк.
    • имеет 38 столбцов.
  • Бэкэнд приложения:
  • База данных - SQL Server 2014, Enterprise Edition.
  • Существует хранимая процедура, которая вызывается для каждой строки:

    • Это займет 50 мс (на avg, я полагаю), чтобы работать.
    • Возвращает примерно 4000 строк.
    • Определение (по крайней мере частично):

      SELECT AVG([AD_Sensor_Data])
                 OVER (ORDER BY [RowID] ROWS BETWEEN 5 PRECEDING AND 5 FOLLOWING)
                 as 'AD_Sensor_Data'
      FROM   [AD_Points]
      WHERE  [FileID] = @FileID
      ORDER BY [RowID] ASC

Что мы можем догадаться:

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

Текущее направление мысли в комментариях заключается в том, что основной проблемой является передача данных между SQL Server и Excel. Это действительно так? Если хранимая процедура вызывается для каждой из 800 000 строк и занимает 50 мс на каждый вызов (то есть на каждую строку), это добавляет до 40000 секунд (не мс). И это эквивалентно 666 минутам (ччмм ;-) или чуть более 11 часов. Все же весь процесс, как говорили, занял всего 7 часов. У нас уже 4 часа больше общего времени, и мы даже добавили время, чтобы выполнить вычисления или сохранить результаты обратно на SQL Server. Так что что-то здесь не так.

Глядя на определение хранимой процедуры, есть только входной параметр для @FileID; там нет никакого фильтра @RowID. Поэтому я подозреваю, что происходит один из следующих двух сценариев:

  • Эта хранимая процедура фактически вызывается не для каждой строки, а для каждой строки @FileID, которая, по-видимому, охватывает приблизительно 4000 строк. Если указанные 4000 возвращенных строк - это достаточно постоянная сумма, то в 800 000 строк есть только 200 из них. И 200 исполнений, каждый из которых занимает 50 мс, составляют всего 10 секунд из этих 7 часов.
  • Если эта хранимая процедура действительно вызывается для каждой строки, то первый раз, когда новая @FileIDпередача передается в, занимает немного больше времени, чтобы вытянуть новые строки в буферный пул, но тогда следующие 3999 выполнений обычно возвращаются быстрее из-за того, что уже кэшируется, верно?

Я думаю, что сосредоточение внимания на этой «фильтрующей» хранимой процедуре или любой передаче данных из SQL Server в Excel - это красная сельдь .

На данный момент, я думаю, наиболее важными показателями слабой производительности являются:

  • Есть 800 000 строк
  • Операция работает по одному ряду за раз
  • Данные сохраняются обратно на SQL Server, поэтому «[использует] значения из некоторых столбцов для манипулирования другими столбцами » [моя эм- фаза ;-)]

Я подозреваю, что:

  • хотя есть некоторые возможности для улучшения поиска данных и расчетов, их улучшение не приведет к значительному сокращению времени обработки.
  • основным узким местом является выдача 800 000 отдельных UPDATEзаявлений, что составляет 800 000 отдельных транзакций.

Моя рекомендация (на основе имеющейся информации):

  1. Ваша самая большая область усовершенствования должна была бы обновить несколько строк за один раз (то есть в одной транзакции). Вы должны обновить свой процесс, чтобы работать с точки зрения каждого FileIDвместо каждого RowID. Так:

    1. прочитать все 4000 строк конкретного FileIDв массив
    2. массив должен содержать элементы, представляющие обрабатываемые поля
    3. цикл по массиву, обрабатывая каждую строку, как вы в настоящее время
    4. как только все строки в массиве (т.е. для этого конкретного FileID) были вычислены:
      1. начать транзакцию
      2. вызывать каждое обновление для каждого RowID
      3. если нет ошибок, совершите транзакцию
      4. если произошла ошибка, откатитесь и обработайте соответственно
  2. Если ваш кластеризованный индекс еще не определен как, (FileID, RowID)то вы должны учитывать это (как предложено @MikaelEriksson в комментарии к Вопросу). Это не поможет этим одиночным ОБНОВЛЕНИЯМ, но, по крайней мере, немного улучшит агрегированные операции, например, то, что вы делаете в этой хранимой процедуре «фильтра», поскольку они все основаны FileID.

  3. Вы должны рассмотреть возможность перемещения логики на скомпилированный язык. Я бы предложил создать приложение .NET WinForms или даже консольное приложение. Я предпочитаю консольное приложение, так как его легко планировать с помощью агента SQL или запланированных задач Windows. Не должно иметь значения, делается ли это в VB.NET или C #. VB.NET может быть более естественным для вашего разработчика, но все равно будет некоторая кривая обучения.

    На данный момент я не вижу причин переходить на SQLCLR. Если алгоритм часто меняется, это будет раздражать, придется постоянно переустанавливать сборку. Перестройка консольного приложения и размещение .exe-файла в соответствующей общей папке в сети, так что вы просто запускаете одну и ту же программу, и она всегда обновляется, должно быть довольно легко сделать.

    Я не думаю, что полное перемещение обработки в T-SQL поможет, если проблема в том, что я подозреваю, и вы просто делаете одно ОБНОВЛЕНИЕ за раз.

  4. Если обработка перемещается в .NET, вы можете использовать табличные параметры (TVP), чтобы передать массив в хранимую процедуру, которая будет вызывать UPDATEметод JOINs для табличной переменной TVP и, следовательно, представляет собой одну транзакцию. , TVP должен быть быстрее, чем 4000 INSERTс, сгруппированных в одну транзакцию. Но выигрыш от использования TVP более 4000 INSERTс в 1 транзакции, скорее всего, не будет таким значительным, как улучшение, которое наблюдается при переходе от 800 000 отдельных транзакций к 200 транзакциям по 4000 строк в каждой.

    Опция TVP изначально не доступна для VBA, но кто-то предложил обходной путь, который может стоить протестировать:

    Как повысить производительность базы данных при переходе с VBA на SQL Server 2008 R2?

  5. ЕСЛИ фильтр proc использует только FileIDв WHEREпредложении, и ЕСЛИ этот proc действительно вызывается для каждой строки, вы можете сэкономить некоторое время обработки, кэшируя результаты первого запуска и используя их для остальных строк FileID, право?

  6. После того, как вы получите обработку сделаны в FILEID , то мы можем начать говорить о параллельной обработке. Но в этом нет необходимости :). Учитывая, что вы имеете дело с 3 довольно крупными неидеальными частями: транзакции Excel, VBA и 800k, любые разговоры об SSIS или параллелограммы, или кто-то знает, что является преждевременной оптимизацией / типом "корзина перед лошадью" , Если мы сможем сократить этот 7-часовой процесс до 10 или менее минут, подумаете ли вы о дополнительных способах его ускорения? Есть ли у вас запланированное время завершения? Имейте в виду, что как только обработка выполняется для каждого идентификатора файла Таким образом, если бы у вас было консольное приложение VB.NET (то есть из командной строки .EXE), ничто не помешало бы запускать несколько из этих FileID одновременно :), независимо от того, был ли это шаг SQL Agent CmdExec или запланированные задачи Windows, и т.п.

И вы всегда можете использовать «поэтапный» подход и вносить несколько улучшений одновременно. Например, начинать с обновлений по одной FileIDи, следовательно, использовать одну транзакцию для этой группы. Затем посмотрите, сможете ли вы заставить работать TVP. Затем посмотрите, как взять этот код и переместить его в VB.NET (и TVP работают в .NET, поэтому он будет хорошо переноситься).


Что мы не знаем, что еще может помочь:

  • Хранимая процедура «фильтра» выполняется для RowID или FileID ? Есть ли у нас полное определение этой хранимой процедуры?
  • Полная схема таблицы. Насколько широк этот стол? Сколько существует полей переменной длины? Сколько полей NULLable? Если какие-либо из них NULLable, сколько из них содержат NULL?
  • Индексы для этой таблицы. Это разделено? Используется сжатие ROW или PAGE?
  • Насколько велика эта таблица в МБ / ГБ?
  • Как ведется обслуживание индекса для этой таблицы? Насколько фрагментированы индексы? Насколько актуально обновление статистики?
  • Записывают ли какие-либо другие процессы в эту таблицу, пока идет этот 7-часовой процесс? Возможный источник раздора.
  • Читают ли какие-либо другие процессы из этой таблицы, пока идет этот 7-часовой процесс? Возможный источник раздора.

ОБНОВЛЕНИЕ 1:

** Кажется, существует некоторая путаница в отношении того, что такое VBA (Visual Basic для приложений) и что с этим можно сделать, так что это просто для того, чтобы убедиться, что мы все на одной веб-странице:


ОБНОВЛЕНИЕ 2:

Еще один момент для рассмотрения: как обрабатываются соединения? Код VBA открывает и закрывает Соединение для каждой операции, или он открывает соединение в начале процесса и закрывает его в конце процесса (т.е. через 7 часов)? Даже с пулом соединений (который по умолчанию должен быть включен для ADO), все равно должно быть значительное влияние между открытием и закрытием один раз, а не открытием и закрытием либо 800 200, либо 1600 000 раз. Эти значения основаны как минимум на 800 000 ОБНОВЛЕНИЙ плюс 200 или 800 КБ EXEC (в зависимости от того, как часто выполняется хранимая процедура фильтра).

Эта проблема слишком большого количества соединений автоматически смягчается рекомендацией, которую я изложил выше. Создавая транзакцию и делая все ОБНОВЛЕНИЯ в этой транзакции, вы будете держать это соединение открытым и повторно использовать его для каждого UPDATE. Независимо от того, остается ли соединение открытым после начального вызова, чтобы получить 4000 строк для указанного FileID, или закрыто после этой операции «get» и снова открывается для ОБНОВЛЕНИЙ, это гораздо менее важно, так как мы сейчас говорим о разнице либо Всего 200 или 400 соединений по всему процессу.

ОБНОВЛЕНИЕ 3:

Я сделал несколько быстрых испытаний. Пожалуйста, имейте в виду, что это довольно небольшой тест, а не та же самая операция (чистый INSERT против EXEC + UPDATE). Однако различия во времени, относящиеся к тому, как обрабатываются соединения и транзакции, по-прежнему актуальны, поэтому информацию можно экстраполировать, чтобы иметь здесь сравнительно похожее влияние.

Параметры теста:

  • SQL Server 2012 Developer Edition (64-разрядная версия), SP2
  • Стол:

     CREATE TABLE dbo.ManyInserts
     (
        RowID INT NOT NULL IDENTITY(1, 1) PRIMARY KEY,
        InsertTime DATETIME NOT NULL DEFAULT (GETDATE()),
        SomeValue BIGINT NULL
     );
  • Операция:

    INSERT INTO dbo.ManyInserts (SomeValue) VALUES ({LoopIndex * 12});
  • Всего вставок за каждый тест: 10000
  • Сброс для каждого теста: TRUNCATE TABLE dbo.ManyInserts;(учитывая природу этого теста, выполнение FREEPROCCACHE, FREESYSTEMCACHE и DROPCLEANBUFFERS, похоже, не принесло особой пользы.)
  • Модель восстановления: ПРОСТО (и, возможно, 1 ГБ свободно в файле журнала)
  • Тесты, которые используют Транзакции, используют только одно Соединение независимо от того, сколько Транзакций.

Результаты:

Test                                   Milliseconds
-------                                ------------
10k INSERTs across 10k Connections     3968 - 4163
10k INSERTs across 1 Connection        3466 - 3654
10k INSERTs across 1 Transaction       1074 - 1086
10k INSERTs across 10 Transactions     1095 - 1169

Как вы можете видеть, даже если соединение ADO с БД уже используется всеми операциями, группировка их в пакеты с использованием явной транзакции (объект ADO должен уметь это обрабатывать) гарантированно значительно (т. Е. Улучшение более чем в 2 раза) сократить общее время процесса.

Соломон Руцкий
источник
Существует хороший подход «среднего человека» к тому, что предлагает srutzky, а именно использование PowerShell для извлечения необходимых данных из SQL Server, вызов сценария VBA для обработки данных, а затем вызов обновления SP в SQL Server. , передав ключи и обновленные значения обратно на сервер SQL. Таким образом, вы комбинируете подход, основанный на множестве, с тем, что у вас уже есть.
Стив Мангиамели
@SteveMangiameli Привет Стив и спасибо за комментарий. Я бы ответил раньше, но был бы болен. Мне интересно, насколько ваша идея сильно отличается от того, что я предлагаю. Все признаки того, что Excel по-прежнему требуется для запуска VBA. Или вы предлагаете, чтобы PowerShell заменил ADO, и если бы он был намного быстрее при вводе / выводе, он того стоил бы, даже если бы он заменял только ввод / вывод?
Соломон Руцки
1
Не беспокойтесь, рад, что вы чувствуете себя лучше. Я не знаю, что будет лучше. Мы не знаем, чего не знаем, и вы провели отличный анализ, но все же должны сделать некоторые предположения. Ввод / вывод может быть достаточно значительным, чтобы заменить его самостоятельно; мы просто не знаем Я просто хотел представить другой подход, который может быть полезен для вещей, которые вы предложили.
Стив Мангиамели
@ SteveMangiameli Спасибо. И спасибо за разъяснение этого. Я не был уверен в вашем точном направлении и решил, что лучше не предполагать. Да, я согласен, что лучше иметь больше вариантов, так как мы не знаем, какие существуют ограничения на то, какие изменения можно внести :).
Соломон Руцки
Привет srutzky, спасибо за подробные мысли! Я проходил тестирование на стороне SQL, оптимизируя индексы и запросы и пытаясь найти узкие места. Теперь я инвестировал в надлежащий сервер, 36-ядерный, 1 ТБ, лишенный PCIe SSD, поскольку ввод-вывод падал. Теперь перейдем к вызову кода VB непосредственно в SSIS, который, по-видимому, открывает несколько потоков для параллельного выполнения.
medwar19
2

ИМХО и исходя из предположения, что невозможно перекодировать сабвуфер VBA в SQL, рассматривали ли вы вопрос о том, чтобы позволить сценарию VBA завершить оценку в файле Excel и затем записать результаты обратно на сервер SQL через SSIS?

Можно запустить подпрограмму VBA и завершить ее, щелкнув индикатор либо в объекте файловой системы, либо на сервере (если вы уже настроили соединение для обратной записи на сервер), а затем используйте выражение SSIS, чтобы проверить этот индикатор на наличие disableсвойство данной задачи в вашем решении SSIS (так что процесс импорта ожидает, пока сабвуфер VBA завершится, если вы беспокоитесь о том, что он превысил свой график).

Кроме того, вы можете запустить программный скрипт VBA (немного странно, но workbook_open()в прошлом я использовал это свойство для запуска задач « запускай и забывай»).

Если время оценки сценария VB начинает становиться проблемой, вы можете увидеть, хочет ли ваш VB-разработчик перенести свой код в задачу сценария VB в рамках решения SSIS - по моему опыту приложение Excel тянет много ресурсов, когда работа с данными на этом томе.

Питер Вандивье
источник