none
Странное поведение GC в SQL CLR RRS feed

  • Вопрос

  • Проблема: 

    Началось все с нехватки памяти в SQL CLR процедуре. Затем найдена была рекурсивная функция которая непонятным образом съедала 80 МБ. Нет никакого объяснения такому объему отъеденной памяти (память проверялась при помощи GC.GetTotalMemory). Причем GC.Collect() не освобождает ее. Но самое странное что та же самая функция запущенная не в SQL CLR а просто из приложения, съедает только 3МБ и те успешно освобождаются с помощью GC.Collect

    варианты:

     - Garbage Collector в рамках SQLCLR работает по другому (не так как в режиме Windows Application)
     - MS SQL Server забивает память какими-то дополнительными данными

    Очень странно! Буду признателен, если кто подскажет в чем дело! Александр.

    • Изменен тип I.Vorontsov 18 февраля 2010 г. 7:17
    • Изменен тип I.Vorontsov 5 марта 2010 г. 12:54
    • Изменен тип I.Vorontsov 5 марта 2010 г. 12:54
    • Перемещено Tagore Bandlamudi 2 октября 2010 г. 0:21 MSDN Forums consolidation (От:SQL Server для разработчиков)
    7 февраля 2010 г. 16:21

Ответы

  • в этом топике описывается ваш случай.
    а в этом, рекомендуют не вызывать вручную GC и оставить это на совесть сиквела, он должен разобраться.
    Поэтому разрастание ресурсов - может быть прямым следсвием того, что вы вмешиваетесь в работу SQL CLR.
    я думаю это ваш случай.

    • Помечено в качестве ответа I.Vorontsov 5 марта 2010 г. 12:55
    5 марта 2010 г. 6:00

Все ответы

  • Лучше бы все таки посмотреть как вызывается CLR функция, ее имплементация и в каком запросе используете.


    [Мой блог], [LinkedIn]
    7 февраля 2010 г. 21:27
  • Если это ваша функция, то скорее всего в SQL она работает с гораздо большим набором данных, что объясняет увеличенный размер занимаемой памяти. Память не освобождается, потому что на данные есть ссылки, значит ваша функция не успевает отработать, забивая доступную память, значит проблема в логике самой функции.

    16 февраля 2010 г. 13:58
  • Если это ваша функция, то скорее всего в SQL она работает с гораздо большим набором данных, что объясняет увеличенный размер занимаемой памяти. Память не освобождается, потому что на данные есть ссылки, значит ваша функция не успевает отработать, забивая доступную память, значит проблема в логике самой функции.

    Неверное предположение. В SQL функция работает с той же самой базой - с теми же таблицами и соответственно вся разница между двумя экспериментами (внутри SQLCLR  vs. вызов из внешнего приложения) только в задании Connection. Все остальное строго одинаково включая результат. Особо подчеркиваю, что из внесенных изменений - только способ задания Connection - он не может быть одинаковым. Затраченная внутри SQLCLR память проверялась с помощью:

    select single_pages_kb + multi_pages_kb + virtual_memory_committed_kb from sys.dm_os_memory_clerks wheretype = 'MEMORYCLERK_SQLCLR'  

    Результат: 80MB в SQLCLR против 3MB в приложении. На вызов GC.Collect() SQLCLR не реагирует никак, при вызове в приложении - моментальная реакция. Это просто ставит в тупик. Учитывая, что SQL Server не очищает память пока операционная система не потребует, процедуры в CLR становятся слабым звеном и в какой-то момент перестают работать просто из-за отсутствия памяти. Неужели нет возможности хотя бы вручную очистить выделенную для SQLCLR память. Или в худшем случае загрузить заново Assembly/AppDomain. Может быть проблема решена в новых версиях SQL Server? Не могу себе представить, что никто с этим не сталкивался.

    С Уважением, Александр.
    5 марта 2010 г. 4:37
  • в этом топике описывается ваш случай.
    а в этом, рекомендуют не вызывать вручную GC и оставить это на совесть сиквела, он должен разобраться.
    Поэтому разрастание ресурсов - может быть прямым следсвием того, что вы вмешиваетесь в работу SQL CLR.
    я думаю это ваш случай.

    • Помечено в качестве ответа I.Vorontsov 5 марта 2010 г. 12:55
    5 марта 2010 г. 6:00
  • "
    GC.Collect will cause otherwise efficient Gen-0 collections to be pushed into Gen-1 and Gen-2 buckets which are collected far less frequently. Ironically, this means your application will suffer from increased longer-term memory usage, defeating the original intention of reducing memory usage.
    "
    5 марта 2010 г. 6:23
  • Я видимо подумал что это очевидно и не очень подробно описал местами. Но... конечно мы не делали вызов GC.Collect() с самого начала. Наоборот - вызов GC.Collect() уже был попыткой исправить как-то проблему с памятью. То есть изначальный алгоритм не содержал вызова GC.Collect() - кстати в любом случае разница не была зафиксирована. 

    Насчет первого топика - я уже читал его и к сожалению не увидел никакого логического конца в дискуссии. Переживаю не придется ли оставить эту затею с SQLCLR и писать просто внешний сервис вокруг MS SQL. Жаль если так, потому что SQLCLR выглядит наиболее близким решением :(
    5 марта 2010 г. 20:17
  • ясно, будем искать дальше.
    чтобы поиск не был слепым, приведите пожалуйста подходы, которые вы уже успели попробывать(чтобы их не находили повтоно)

    для начала, знакомы ли вы с этой статьй(MemToLeave)?


    итого можно подвести итог, памятью SQL Server управляет сам. GC.Collect работает по-другому.(для повшения производительности SQL может не освобождать ресурсы занятые на вызов СLR процедур)
    5 марта 2010 г. 23:02
  • Хороший принцип - не вызывать методы GC.* без точного понимания механизма работы GC :)

    Однократное выделение 80 Mb - это не тот объем, при котором пора беспокоиться. Если функция ест по 80 Mb на каждый запуск, причем не освобождает их - то это уже причина для беспокойства. Тогда пора снимать дамп, брать профайлер и читать блог tess.
    5 марта 2010 г. 23:05
    Модератор
  • Не совсем ясно из постов, как именно проверялась выделенная память?
    В десктопном приложении только через GC.GetTotalMemory
    В SQL CLR только через 
    select single_pages_kb + multi_pages_kb + virtual_memory_committed_kb from sys.dm_os_memory_clerks wheretype = 'MEMORYCLERK_SQLCLR' 
    ?

    Если да, то вы сравниваете заведомо разные показатели. Первый - это память, которую выделил сам CLR под все созданные объекты. Второе - это память, которая выделена SQL Server для CLR. GC в серверном режиме может выделять под себя память блоками по 64M, и значение 3MB вообще нереально получить. 
    6 марта 2010 г. 10:18
    Модератор
  • ИМО, Для серверной процедуры даже утечка в 10kb страшна. Потому что это делает сервер не надежным.
    16 марта 2010 г. 11:43
  • Да теперь ясно что сервер управляет памятью иначе. Есть гипотеза почему происходят проблемы с памятью даже в случае -g512. Скорее всего дело в том что на сервере используются несколько тестовых серверных баз. И возможно каждая отъедает по 80MB для своих функций и не очищает. Добавим к этому фрагментацию и как результат в какой то момент вся память съедена. Думаю что в случае жесткого администрирования и одной базы, все будет работать без проблем с MemToLeave = 512. Всегда в запасе есть вариант поставить Server x64 и выделить 2GB для MemToLeave и забыть о проблеме. 

    Мы все таки решили использовать внешнюю EXE которую будем вызывать из CLR. Чтобы обеспечить транзакции будем использовать Microsoft Distributed Transaction Coordinator. Единственный плюс это то что после каждого вызова EXE будем закрываться и следовательно мы будем защищены на 100% от утечек добавляя надежности. 

    Буду признателен за ваши комментарии.
    16 марта 2010 г. 11:48
  • Минус - MSDTC - это медленно. Намного медленее родных транзакций.

    Я бы посоветовал не заморачиваться, особенно если на живой системе предполагается одна база не сервер.

    Постоянное выделение 80 мегабайт можно трактовать как экономию накладных расходов на выделении этих же 80 мегабайт для последующих запросов.
    16 марта 2010 г. 12:57
    Модератор
  • Утечка - это постоянное увеличение объема занимаемой памяти. Не постоянное - это кэширование :)
    16 марта 2010 г. 13:57
    Модератор