Внутренние компоненты ESENT: ожидаемое поведение JetPrereadKeys ()

У меня есть приложение, работающее со значительным объемом данных (более 100 ГБ), хранящихся в ESENT. Схема таблицы: 12-байтовые ключи JET_bitColumnFixed и значения JET_coltypLongBinary с типичным размером около 2 КиБ. Размер страницы установлен на 32 КиБ. Я не изменяю порог размера 1024 байта по умолчанию для внешних длинных значений, поэтому я думаю, что эти значения в основном хранятся снаружи.

Я заинтересован в улучшении производительности холодного поиска и извлечения кэша , потому что операции выполняются пакетами, а ключи известны заранее. Насколько я понимаю, API JetPrereadKeys () предназначен для повышения производительности в таких случаях, но, как оказалось, я не вижу никаких изменений в реальном поведении с этим вызовом или без него.

Более подробная информация приведена ниже:

  • В моем случае JetPrereadKeys () всегда сообщает о достаточном количестве предварительно прочитанных ключей, равном количеству ключей, которые я отправил при вызове API. Представленные ключи соответствующим образом отсортированы, как указано в документации.

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

  • Я испробовал оба доступных режима кэширования ESENT, где он использует MMAP или выделенный кеш страниц, попробовав все доступные комбинации параметров JET_paramEnableViewCache и JET_paramEnableFileCache .

  • За небольшим исключением, я не вижу никакой разницы в зарегистрированных операциях ввода-вывода с предварительным чтением и без него. То есть я ожидаю, что эта операция приведет к (предпочтительно асинхронному) извлечению необходимых внутренних узлов B-дерева. Но единственное, что я вижу, - это случайное синхронное маленькое чтение, возникающее из стека самого JetPrereadKeys (). Размер чтения невелик, в том смысле, что я не думаю, что он мог бы предварительно извлечь всю необходимую информацию.

  • Если я отлаживаю службу поиска Windows, я могу прерывать различные вызовы JetPrereadKeys (). Таким образом, существует, по крайней мере, один реальный пример, где этот API вызывается, предположительно по причине.

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


Вопросов:

  1. Каково ожидаемое поведение JetPrereadKeys () в описанном случае?

  2. Стоит ли ожидать увидеть другой шаблон ввода-вывода и лучшую производительность, если я использую этот API? Стоит ли ожидать синхронного или асинхронного предварительного чтения данных?

  3. Есть ли другой подход, который я мог бы попытаться улучшить производительность ввода-вывода, намекая ESENT о предстоящей партии?

Всего 1 ответ


API-интерфейс JetPrereadKeys () выполняет синхронизацию чтения с родителем конечного уровня, а затем ставит в очередь асинхронные операции ввода-вывода для всех конечных страниц, необходимых для требуемых ключей / записей ... Я думаю, что это ответ №2. Если записи в основной таблице (обратите внимание, что длинные значения пакета / LV хранятся в отдельном дереве) неглубокие или полностью кэшированы, этот JetPrereadKeys () может не помочь. Однако, если ваше основное дерево в таблице большое и глубокое, то этот API может существенно помочь ... он зависит только от формы и распространения ваших данных, которые вы получаете. Вы можете рассказать некоторые основные сведения о своей таблице, освободив место, взглянув на глубину деревьев и получив представление о страницах «Данные», могу я предложить:

esentutl /ms Your.Db /v /fName,Depth,Internal,Data

Перечисляет имя таблицы, глубину, количество внутренних страниц и количество страниц данных конечного уровня. Отдельные строки будут перечислены для основного дерева записей по имени таблицы, а затем под LV указаны «/ Long Values]».

Также обратите внимание, что эти предварительно прочитанные ключи не распространяются и на пакетные LV, так что, опять же, если вы сразу же прочитаете столбец пакетного LV - вы, к сожалению, прикрепитесь к IO.

Режим по умолчанию для ESE - выделять и контролировать только свой собственный буфер базы данных / кеш страниц. JET_paramEnableFileCache в первую очередь предназначен для (обычно меньших) клиентских процессов, которые выходят (или, по крайней мере, JetTerm / JetDetach их БД) и многократно перезапускаются ... так, где частный буферный кэш ESE будет потерян при каждом выходе ... но JET_paramEnableFileCache параметр, так что данные могут все еще находиться в файловом кеше, если они недавно выходили. Однако это не рекомендуется для больших БД, поскольку это приводит к двойному кешированию данных в буферном кеше ESE и в файловом кеше NTFS / ReFS. JET_paramEnableViewCache улучшает предыдущий параметр и несколько улучшает этот двойной кеширование ... но он может только сэкономить память / не двойной буфер на чистых / неизмененных буферах страниц. Для больших БД оставьте оба этих параметра выключенными / ложными. Кроме того, если вы не используете эти параметры, тогда проще протестировать «холодный перфоманс» ... просто скопируйте большой файл (100 МБ, может быть 1 или 2 ГБ) пару раз на вашем HD после того, как ваше приложение выйдет (очистить HD кеш), и ваши данные будут холодными. ;-)

Итак, теперь, когда мы упомянули о кешировании ... последнее, что я думаю, вероятно, является вашей реальной проблемой (если это не «форма ваших данных», о которой я упоминал выше) ... откройте perfmon и найдите «Database» и / или объекты Perf «Database ==> Instances» (это для ESENT) и посмотрите, насколько велик ваш размер кэша [«Размер кэша базы данных» или «Размер кэша базы данных (МБ)»), и посмотрите, насколько велик ваш доступный пул is / ["Database Cache% Available"] ... вам, конечно, придется взять этот% и сделать математику с размером кэша базы данных, чтобы понять ... НО, если он низкий, это может быть вашей проблемой. .. это потому, что JetPrereadKeys будет использовать только доступные буферы, поэтому у вас должен быть здоровый / достаточно большой доступный пул. Либо увеличьте JET_paramCacheSizeMin, чтобы он был больше, либо установите JET_paramStartFlushThreshold / JET_paramStopFlushThreshold, чтобы размер доступного кеша был больше% от общего размера кеша ... обратите внимание, что они установлены пропорционально JET_paramCacheSizeMax, например, так:

paramCacheSizeMin = 500
paramCacheSizeMax = 100000
paramStartFlush.. =   1000
paramStopFlushT.. =   2000

будет означать, что ваши пороги запуска и остановки составляют 1% и 2% соответственно от вашего текущего размера кэша, каким бы он ни был. Таким образом, если кэш находится в 500 буферах (мин), 5 и 10 будут вашими порогами начала / остановки - то есть диапазон, в котором будет находиться ваш доступный пул, если позже он увеличится до 10000 буферов, то ваш доступный пул будет в диапазоне от 100 до 200 буферы. В любом случае, вы хотите, чтобы эти числа были достаточно хорошим диапазоном, чтобы у вас было достаточно буферов для всех конечных страниц, которые могут понадобиться JetPrereadKeys.

Я не объяснил каждый термин в этом письме, потому что вы выглядели довольно продвинутыми - говорите о внутренних узлах B-дерева и т. Д., Но если что-то не понятно, просто спросите, и я проясню это.

Благодарность,

Бретт Ширли [MSFT]

Разработчик расширяемого хранилища

Эта публикация предоставляется "КАК ЕСТЬ" без каких-либо гарантий и не дает никаких прав.

PS - Последнее, что вам может понравиться: JetGetThreadStats / JET_THREADSTATS, оно говорит вам о некоторых наших внутренних операциях, которые мы выполняем в рамках API. Вы в основном читаете значения до и после и JET API и вычитаете их, чтобы получить количество операций для этого JET API. Таким образом, вы увидите cPagePreread там ... это будет хороший способ проверить, отправляет ли JetPrereadKeys с асинхронных операций ввода-вывода, которые должны помочь перфекту. Обратите внимание, что в старых ОС, к сожалению, был сломан конкретный счетчик, но я не помню, когда он был сломан и исправлен ... от win7 до win8, от win8 до win8.1. Если у вас Win10, то никаких проблем к тому времени это точно не решило. ;-) А также cPageRead - это синхронизация страниц чтения (которые могут подняться для внутренних узлов) ... Я думаю, вы найдете их очень полезными для затрат на различные JET API.


Есть идеи?

10000