AXForum  
Вернуться   AXForum > Microsoft Dynamics AX > DAX: Программирование
All
Забыли пароль?
Зарегистрироваться Правила Справка Пользователи Сообщения за день Поиск

 
 
Опции темы Поиск в этой теме Опции просмотра
Старый 18.12.2015, 12:58   #1  
Pokersky09 is offline
Pokersky09
Участник
 
43 / 60 (3) ++++
Регистрация: 15.11.2012
Адрес: Turkey
? Пакетные задания, SPID/Session
Доброго дня!

Существует проблема в нехватке количества 32 767 одновременных соединений к SQL серверу.
Обрабатываемая задача: загрузка данных из файлов csv.


Текущая конфигурация DAX:
• Ax2012 R3 (Системная версия: 6.3.1000.1928 Версия приложения: 6.3.1000.473)
• AOS x6
• SQL 2014
• Пользователей нет

Текущие настройки:
• Макс кол-во сессий: по 500 на AOS, итого 500*6 = 3000 сессий в пакетных заданиях (в настройках Администрирования Ах пишется как кол-во потоков).
• Макс кол-во подключений на SQL = не ограничено (макс системный порог 32 767)

Наблюдения:
При работе некоторых классов загрузки, кол-во соединений около 2,5 на 1 сессию. Иногда кол-во соединений достигает 80-100 на 1 поток, что приводит к превышению макс.кол-ва подключений и вылету всех AOS, с выдачей соотв. сообщений в журналы.

В любой момент времени работы пакетов, запросом sp_who на sql сервере видно что 99% SPID в состоянии sleeping_AwaitingCommand.

По завершении пакетного задания (точнее каждого Batch), подключения к sql исчезают, однако активные сессии, созданные пакетным заданием, по прежнему остаются на некоторое время, затем сами по себе исчезают.
Блокировок на sql сервере не наблюдается в любой момент.

Временным решением является сокращение кол-ва доступных потоков на АОСах до 100, в этом случае кол-во соединений остается в рамках квоты sql. Однако скорость загрузок резко ухудшается.

Алгоритм загрузки
-Обращение к таблице, для выявления существования ранее загруженной строки
-Заполнение курсора (в т.ч. обращение к справочникам для получения RecId)
-Update/Insert или пропуск строки, если существующая строка идентична.

Загрузка возможна одновременно в несколько таблиц, например SalesAgreementHeader, LogisticsLocation, LogisticsPostalAddress.

Влияние кол-ва конечных таблиц, куда идет запись, а также кол-во связанных справочников (куда идут запросы на получение RecId курсоров), не обязательно приводит к увеличению кол-ва подключений к SQL, относительно простейшей загрузки в 1 таблицу без Relation. Однако значение количества SPID/Session всегда примерно одинаково для каждого класса.

Вопросы:

1) По какому принципу формируется минимальное кол-во подключений к sql серверу, и есть ли четкая связь с элементами кода Ах?

2) Когда подключение прекращается, и в каких случаях переводится в sleeping?

3) Предусмотрены ли способы управления/контроля/ограничения максимальным кол-ом соединений (не сессий) со стороны Ах, чтобы исключить данную проблему?

4) Каков механизм снятия сессий, после завершения пакетного задания?

Последний раз редактировалось Pokersky09; 18.12.2015 в 13:02.
Старый 18.12.2015, 15:41   #2  
gl00mie is offline
gl00mie
Участник
MCBMSS
Most Valuable Professional
Лучший по профессии 2017
Лучший по профессии 2015
Лучший по профессии 2014
Лучший по профессии AXAWARD 2013
Лучший по профессии 2011
Лучший по профессии 2009
 
3,684 / 5798 (201) ++++++++++
Регистрация: 28.11.2005
Адрес: Москва
Записей в блоге: 3
Посмотрите Application Object Server (AOS) configuration commands, раздел Database tuning options, там указаны описания настроек, которые можно увидеть в конфигурационной утилите сервера, в том числе:
  • Leave the connection running when idle
  • Maximum idle time before closing
  • Maximum open cursors
А еще рассмотрите вариант обновления ядра, последняя сборка вроде - 6.3.3000.660, весьма вероятно, что в ранних сборках были какие-нибудь утечки ресурсов.
За это сообщение автора поблагодарили: alex55 (1), Pokersky09 (1).
Старый 22.12.2015, 12:57   #3  
Pokersky09 is offline
Pokersky09
Участник
 
43 / 60 (3) ++++
Регистрация: 15.11.2012
Адрес: Turkey
->
Цитата:
Сообщение от gl00mie Посмотреть сообщение
Посмотрите Application Object Server (AOS) configuration commands, раздел Database tuning options, там указаны описания настроек, которые можно увидеть в конфигурационной утилите сервера, в том числе:
  • Leave the connection running when idle
  • Maximum idle time before closing
  • Maximum open cursors
А еще рассмотрите вариант обновления ядра, последняя сборка вроде - 6.3.3000.660, весьма вероятно, что в ранних сборках были какие-нибудь утечки ресурсов.

Вопрос решен, помогло явное указание параметра AOS:
  • Maximum idle time before closing = 0
Старый 24.12.2015, 15:14   #4  
b_nosoff is offline
b_nosoff
Читатель
Аватар для b_nosoff
MCP
MCBMSS
 
197 / 143 (5) +++++
Регистрация: 01.12.2004
Адрес: Msk
Записей в блоге: 13
Цитата:
Сообщение от Pokersky09 Посмотреть сообщение
4) Каков механизм снятия сессий, после завершения пакетного задания?
Делайте connection.Close() перед выходом
__________________
Axapta non erubescit
Старый 29.02.2016, 17:12   #5  
Pokersky09 is offline
Pokersky09
Участник
 
43 / 60 (3) ++++
Регистрация: 15.11.2012
Адрес: Turkey
Вопрос вновь открыт
Выявил источник проблемы:

В DimensionStorage:SavePrivate()
X++:
 new userConnection()
Притом данный коннект создается только в случае необходимости создания новой строки DimensionAttributeValueCombination , при повторной необходимости использования аналитик берется уже ранее созданная, поэтому ошибочно тему закрыли.

При создании открывается новое соединение UserConnection, и почему то соединение не снимается, а остается в пуле на некоторое время (более 10минут), настройки аосов выполнили согласно описанию выше постом.

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

Пробовал добавлять в конце метода UserConnection.Finalize(), однако результатов не принесло, соединения копятся гораздо быстрее, чем снимаются. Подсмотрено в методах NumberSeq, там аналогично используются userConnection, и проблем не возникает.

X++:
/// <summary>
///    Saves the current information and retrieves the record ID of the persisted combination.
/// </summary>
/// <returns>
///    The record ID of the combination.
/// </returns>
private recId savePrivate()
{
    #OCCRetryCount
    #LedgerSHA1Hash
    DimensionSHA1Hash hash;

    DimensionAttributeLevelValue                dimAttrLevelValue;
    DimensionAttributeValueGroup                dimAttrValueGroup;
    DimensionAttributeValueGroupCombination     dimAttrValueGroupCombo;
    DimensionAttributeValueCombination          dimAttrValueCombo;
    DimensionHierarchy                          accountStructure;
    DimensionHierarchyLevel                     mainAccountSegment;
    DimensionAttributeValue                     mainAccountDimAttrValue;
    int                                         hierarchyIndex;
    int                                         segmentIndex;
    int                                         previousSegmentIndex;
    int                                         i;
    int                                         hierarchyCount;
    int                                         segmentCountForHierarchy;
    DimensionStorageSegment                     segment;
    XDSServices                                 xdsServices;
    UserConnection                              userConnection;
    LedgerDimensionBase                         savedComboId;

    if (Debug::debugMode())
    {
        Debug::assert(this.hierarchyCount() > 0);
        Debug::assert(segments != null);
        Debug::assert(totalSegmentCount > 0);
        DimensionStorage::validateCombinationIntegrity(this);
    }

    // Calculate the overall super-combo hash
    hash = this.getComboHash();

    if (hash == connull())
    {
        Debug::printDebug(strfmt('Warning: LedgerDimension of %1 is not created as no segments with DimensionAttributeValues exist (Is the backing entity instance missing?).', dimAttrValueCombo.DisplayValue));

        // Don't save combinations without level values
        initialComboId = 0;
        initialHash = connull();
        return 0;
    }
    else if (hash == initialHash)
    {
        // Nothing to do if nothing's changed
        Debug::assert(DimensionAttributeValueCombination::exist(initialComboId));
        return initialComboId;
    }

    // Turn off XDS to ensure the reference can be found.
    xdsServices = new XDSServices();
    xdsServices.setXDSState(0);

    savedComboId = DimensionStorage::getSavedComboRecIdByHash(hash);

    if (savedComboId)
    {
        initialComboId = savedComboId;
        initialHash = hash;

        return savedComboId;
    }

    // Create the main combination and group link in a separate transaction for smaller transaction scope to prevent blocking
    userConnection = new userConnection();
    userConnection.ttsBegin();
    dimAttrValueCombo.setConnection(userConnection);
    dimAttrValueGroupCombo.setConnection(userConnection);

    try
    {
        // A matching was not found, so now insert a new combination. This will link to any existing sub-groups found or new sub-groups are inserted and linked
        dimAttrValueCombo.DisplayValue = this.getComboDisplayValue();
        dimAttrValueCombo.LedgerDimensionType = ledgerDimensionType;
        dimAttrValueCombo.Hash = hash;

        // Look up the account structure and main account if applicable
        accountStructure = DimensionHierarchy::find(this.getHierarchyId(1));
        //Debug::assert(accountStructure.RecId);  // TODO: Enable once the unit test setup data correctly

        if (accountStructure && (accountStructure.StructureType == DimensionHierarchyType::AccountStructure))
        {
            // Denormalize the account structure and main account
            dimAttrValueCombo.AccountStructure = accountStructure.RecId;

            // Look up the main account segment, which uses the hierarchy/dimension attribute
            // index so this will be a cached lookup
            mainAccountSegment = DimensionHierarchyLevel::findByDimensionHierarchyAndDimAttribute(
                accountStructure.RecId,
                DimensionAttribute::getMainAccountDimensionAttribute());

            // If the hierarchy is an account structure, then the dimension hierarchy level
            // will be indexed the same in the storage segment collection as in the account
            // structure
            if (mainAccountSegment && this.segmentCount() >= mainAccountSegment.Level)
            {
                // Look up the main account recid from the sepecified DAV. This will
                // normally be a cached lookup since it was recently used to create the
                // DAV that was passed to the storage object.
                mainAccountDimAttrValue = DimensionAttributeValue::find(this.getSegment(mainAccountSegment.Level).parmDimensionAttributeValueId());
                dimAttrValueCombo.MainAccount = mainAccountDimAttrValue.EntityInstance;
            }
        }
        else if (accountStructure && (accountStructure.StructureType == DimensionHierarchyType::DefaultAccount))
        {
            // Denormalize the main account
            Debug::assert(this.segmentCount() == 1);

            // Look up the main account recid from the sepecified DAV. This will
            // normally be a cached lookup since it was recently used to create the
            // DAV that was passed to the storage object.
            mainAccountDimAttrValue = DimensionAttributeValue::find(this.getSegment(1).parmDimensionAttributeValueId());
            dimAttrValueCombo.MainAccount = mainAccountDimAttrValue.EntityInstance;
        }

        // Create the header value
        dimAttrValueCombo.insert();

        savedComboId = dimAttrValueCombo.RecId;
        initialComboId = savedComboId;
        initialHash = hash;
        hierarchyCount = this.hierarchyCount();

        for (hierarchyIndex = 1; hierarchyIndex <= hierarchyCount; hierarchyIndex++)
        {
            segmentCountForHierarchy = this.segmentCountForHierarchy(hierarchyIndex);
            hash = this.getGroupHash(hierarchyIndex);
            previousSegmentIndex = segmentIndex;

            try
            {
                // Try to find an existing value group that matches what we need for the specified group - use the default connection (as it would have been saved by another process's connection anyway)
                select firstOnly RecId from dimAttrValueGroup where dimAttrValueGroup.Hash == hash;
                if (dimAttrValueGroup.RecId)
                {
                    segmentIndex += segmentCountForHierarchy;
                }
                else
                {
                    // Create the group and levels in the same separate transaction
                    dimAttrValueGroup.setConnection(userConnection);
                    dimAttrLevelValue.setConnection(userConnection);

                    dimAttrValueGroup.DimensionHierarchy = this.getHierarchyId(hierarchyIndex);
                    dimAttrValueGroup.Hash = hash;
                    dimAttrValueGroup.Levels = segmentCountForHierarchy;
                    dimAttrValueGroup.insert();

                    for (i = 0; i < segmentCountForHierarchy; i++)
                    {
                        segmentIndex++;
                        segment = this.getSegment(segmentIndex);
                        if (!segment.isEmpty())
                        {
                            dimAttrLevelValue.DimensionAttributeValueGroup = dimAttrValueGroup.RecId;
                            dimAttrLevelValue.DimensionAttributeValue = segment.parmDimensionAttributeValueId();
                            dimAttrLevelValue.DisplayValue = segment.parmDisplayValue();
                            dimAttrLevelValue.Ordinal = i + 1;
                            dimAttrLevelValue.insert();
                        }
                    }
                }

                dimAttrValueGroupCombo.DimensionAttributeValueGroup = dimAttrValueGroup.RecId;
                dimAttrValueGroupCombo.DimensionAttributeValueCombination = savedComboId;
                dimAttrValueGroupCombo.Ordinal = hierarchyIndex;
                dimAttrValueGroupCombo.insert();
            }
            catch (Exception::DuplicateKeyException)    // Finding or creating the DAVG
            {
                if (xSession::currentRetryCount() < #RetryNum)
                {
                    segmentIndex = previousSegmentIndex;   // Revert to previous position, since insert failed
                    retry;
                }

                Debug::assert(dimAttrValueGroupCombo.RecId != 0);

                // Failed to insert a new group -- restart the transaction and try to find the full combination again (by outer catch)
                userConnection.ttsAbort();
                userConnection.ttsBegin();
                throw Exception::DuplicateKeyException;
            }
        }

        // Successfully inserted a completely new combination
        userConnection.ttsCommit();
    }
    catch (Exception::DuplicateKeyException)    // Finding or creating the DAVC
    {
        // Attempt to re-read the DAVC as another one was apparently inserted - use the default connection (as it would have been saved by another process's connection anyway)
        savedComboId = DimensionStorage::getSavedComboRecIdByHash(hash);

        if (savedComboId)
        {
            initialComboId = savedComboId;
            initialHash = hash;
        }
        else if (xSession::currentRetryCount() < #RetryNum)
        {
            // The retry will be a rare case where we didn't find it, tried to insert, got dup, tried to reread and didn't find it again so will attempt to re-insert
            retry;
        }
        else
        {
            // Record level security may have prevented the record from being found even with XDS off.
            Debug::assert(savedComboId != 0);
        }

        // Failed to insert a new combination
        userConnection.ttsAbort();
    }

    userConnection().finalize();//Добавлено для проверки

    return savedComboId;
}


Напомню, текущая задача является импорт строк LedgerJournalTrans в количестве более 50млн, кол-во строк в CustTable составляет порядка 2х Млн. Соответственно при импорте каждой строки вызывается метод
X++:
DimensionStorage::getDynamicAccount(custAccount , LedgerJournalACType::Cust);

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

X++:
static server void DimAttributeValueCombCREATE_AXFORUM(Args _args)
{
    #define.maxRowCount(100000)
    
    int64           tick1 = WinAPI::getTickCount64();
    int             countCurrent;
    int             countCreate;
    int             countMax    = #maxRowCount;
    int             countCatch;
    CustAccount     custAccount;
    CustTable                           custTable;
    DimensionAttributeValueCombination  dimComb;

    return;//Снять в случае необходимости исполнения кода

    while select AccountNum from  custTable
    order by custTable.AccountNum asc
    notexists join RecId from dimComb
        where custTable.AccountNum == dimComb.DisplayValue
    {
        countCurrent++;
        if(countCurrent >= countMax)
            break;

        custAccount = custTable.AccountNum;

        if(custAccount)
        {
            try
            {
                DimensionStorage::getDynamicAccount(custAccount , LedgerJournalACType::Cust);
                countCreate++;
            }
            catch
            {
                    countCatch++;
            }
        }
    }

    info(strFmt('countCurrent: (%1)',   countCurrent));
    info(strFmt('countCreate: (%1)',    countCreate));
    info(strFmt('countCatch: (%1)',     countCatch));

    info(strFmt("%1", time2str(ms2TimeOfDay(WinAPI::getTickCount64() - tick1),1,1)));
}
За это сообщение автора поблагодарили: trud (2), Logger (5), Kabardian (2).
Старый 29.02.2016, 23:33   #6  
b_nosoff is offline
b_nosoff
Читатель
Аватар для b_nosoff
MCP
MCBMSS
 
197 / 143 (5) +++++
Регистрация: 01.12.2004
Адрес: Msk
Записей в блоге: 13
Немного смущает то, что в цикле по иерархиям каждый раз выполняется
X++:
    dimAttrValueGroup.setConnection(userConnection);
    dimAttrLevelValue.setConnection(userConnection);
да еще и курсор dimAttrValueGroup при первом проходе селектится в дефолтном соединении.

Попробуйте вынести эти две строчки перед циклом, и select делать для отдельной переменной (только надо не забыть при нахождении записи скопировать ее RecId в dimAttrValueGroup, она дальше используется)

И еще, попробуйте добавить
X++:
 userConnection = null;
после
X++:
userConnection.finalize();
кстати, у вас описка там (userConnection со скобками)?
__________________
Axapta non erubescit

Последний раз редактировалось b_nosoff; 29.02.2016 в 23:37.
Теги
batch, session, spid, пакетное задание

 

Похожие темы
Тема Автор Раздел Ответов Посл. сообщение
Почему могут слетать пакетные задания? AXcons DAX: Администрирование 5 16.06.2015 13:09
emeadaxsupport: Capturing an AX User’s SQL SPID over multiple AOS servers in AX 2009 Blog bot DAX Blogs 0 16.05.2013 19:11
dynamicsaxbi: How To: Addressing SSRS Session Timeouts Blog bot DAX Blogs 0 15.02.2013 00:13
Настройка Axapta 3.0 (Array fetch ahead, Buffer size, событие ожидания в oracle 10G SQL*Net message to/from client ). Долго выполняются пакетные задания. NemoFF DAX: Администрирование 4 13.01.2011 11:01
daxis: Dynamics Ax 4.0 Session Types Blog bot DAX Blogs 0 01.04.2009 18:05

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

BB коды Вкл.
Смайлы Вкл.
[IMG] код Вкл.
HTML код Выкл.
Быстрый переход

Рейтинг@Mail.ru
Часовой пояс GMT +3, время: 09:25.