Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
MUMPS СУБД. Практика применения и опыт программирования.pdf
Скачиваний:
0
Добавлен:
07.09.2026
Размер:
2 Мб
Скачать
2.2. КОДИРОВАНИЕ ИНДЕКСОВ 191
int count = 0;
char buf[ 64];
for( d1 = 10.0; d1 < 20.0; d1 += 0.00001) {
sprintf( buf, "%.15G", d1); d2 = atof( buf); if( d1 != d2) {
printf( "%.15G\n", d1); count++; if( count >= 5) {
break;
}
}
}
return 0;
};
Первые 5 найденных чисел:
10.00003
10.00004
10.00005
10.00006
10.00007
Для решения проблемы могут быть применены различные решения про лексикографическому кодированию целых и дробных чисел и строк так, чтобы при сортировке результата как массива байт значения сорти­ровались по стандарту MUMPS.
Как один из вариантов может быть использован вариант лидирующе­го байта для разделения пустых строк, чисел и строк, после которого следует либо само число, либо строка:
Лидирующий байт 0x00 пустая строка иное число 0xFF строка
Для чисел выполняется приведение к виду мантисса + порядок, на­пример:
192 ГЛАВА 2. ГЛОБАЛЫ
Значение Кодированное 123456 .123456 * E+6
0.0078 .78 * E-2
123.456 .123456 * E+3
Для отрицательных чисел используются дополнения знаков до 10 для
обеспечения сортировки отрицательных чисел:
Значение Кодированное
-123456 .987654 * E+6
-0.0078 .32 * E-2
-123.456 .987654 * E+3
Для корректности сортировки используем первые байты для записи порядка, сами байты порядка также формируем так, чтобы отрицатель­ные числа сортировались перед положительными, используя дополнение. Сами мантиссы могут быть также представлены как в десятичной, так и в любой другой системе счисления.
Различные М системы могут использовать различные основания для предствления порядка и мантиссы чисел и различные дополнения для отрицательных. Поэтому у разных М систем может варьироваться число байт, требуемых для кодирования чисел. Отдельной интересной задачей может стать исследование оптимальных параметров для дополнений и основания при лексикографическом кодировании.
Для определения длины байтовой последовательности, сколько зани­мает кодированное представление индекс, используются либо байты, за­дающие длину, либо байты - терминаторы, как в случае системы GT.M. В качестве терминатора GT.M использует нулевой байт. Поскольку он зарезервирован, то для кодирования двух специальных байт использует­ся кодирование в виде не одного, а двух байт:
0 -> 1 1 1 -> 1 2
Соответственно, при декодировании значений эти последовательно­сти заменяются на обратные им.
Различные М системы и другие СУБД используют различные алго­ритмы и схемы кодирования чисел для обеспечения сортировки чисел. Современные СУБД скрывают сложности кодирования и представления информации так, что результатом разработчики могут пользоваться со­вершенно не замечая внутренних трудностей и особенностей.
Использование кодирования национальных алфавитов имеет своими следствиями то, что разработчики должны применять при чтении дан­ных то же самое определение символов, которое было использовано при
2.3. РАЗМЕР БЛОКА 193
их записи. В случае экспорта и импорта данных в стандартных форма­тах СУБД используют для формирования внешних файлов кодирование данных как есть, в том виде в котором они попали в базу. В случае ис­пользования бекапов или блочного экспорта и импорта используются не отдельные логические записи, а блоки целиком. Поэтому для чтения вос­становленных баз и для импорта блочного экспорта нужно использовать то же самое определение символов. Если требуется сменить определение символов, то необходимо экспортировать данные в стандартном перено­симом формате и импортировать на другой системе или базе данных с другим определением символов.

2.3 Размер блока

Различные СУБД, и системы класса MUMPS в том числе, управляя данными в базе данных, оперируют блоками данных. И у каждого из блоков имеется размер. Обычно он указывается справочно в описании системы и для программистов обычно не имеет особенного значения, чему он равен.
При этом для тех, кто хочет изучить и понимать работу с базами данных более углубленно, стоит обратить внимание на этот параметр и на факторы, влияющие на производительность.
Первым пунктом, который на первый взгляд вообще не упоминается, но подразумевается и является важным, это равенство размеров блоков. Каким бы ни был выбран размер, у разных блоков он одинаковый. Вооб­ще говоря, СУБД алгоритмически может использовать и блоки разных размеров, это вопрос техники, и даже вообще не использовать блочное строение. Но если все блоки одинакового размера, то это позволяет су­щественно и упростить и ускорить операции вычисления положения и адресацию нужного блока, а также использовать заранее зарезервиро­ванные пространства в кеше блоков вместо постоянного динамического распределения памяти.
Размер блока технически может быть любым, но в целях оптимиза­ции операций ввода - вывода с дисками, физически являющимися блоч­ными устройствами, используются размеры, кратные степени 2, обычно начиная с размера 512 байт. 512 байт - это обычно минимальный размер сектора в байтах. Впоследствии размер блоков опирали не на размер сектора, а на размер кластера, а размер кластеров у современных сред­нестатистических компьютеров составляет обычно 4 - 8 килобайт.
Современные СУБД используют блоки размером, конечно, не такие маленькие, как один сектор или просто кластер, а начиная с размера
194 ГЛАВА 2. ГЛОБАЛЫ
хотя бы 1 килобайт. Типовые размеры блоков - 1, 2, 4, 8, 16, 32, 64 килобайт. Б´ольшие размеры используются редко. Кроме фиксированно­го для выбора размера блоков некоторые СУБД дополнительно могут предлагать индивидуально задаваемые значения для размера блока.
Изменение размера блока имеет своими последствиями улучшение или ухудшение эффективности. Если выполняется чтение блока, а это самая тормозящая работу СУБД операция, то привод диска физически возвращает операционной системе в действительности порцию размером в один кластер, и ему нет особенной разницы, был запрошен один байт или сто, или весь кластер целиком.
Несложно провести эксперимент, определяющий производительность чтения при различных размерах читаемых порций. Пусть есть файл с размером мегабайт и программа, использующая чтение разными порци­ями:
#include <stdio.h> #include <stdlib.h> #include <windows.h>
char* filename = "test.dat"; char* openmode = "rb"; int total_size = 1024 * 1024;
int read_size[] = {
1, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768, 65536
};
__int64 GetCurrentMilliseconds() {
SYSTEMTIME system_time; FILETIME file_time; __int64 ret; GetLocalTime( &system_time);
2.3. РАЗМЕР БЛОКА 195
SystemTimeToFileTime( &system_time, &file_time); ret = *(__int64*)&file_time; ret /= 10000; return ret;
};
#pragma argsused int main(int argc, char* argv[]) {
static char buffer[ 1024 * 100]; for( int i = 0; i < sizeof( read_size) /
{
sizeof( read_size[ 0]); i++)
int size = read_size[ i]; printf( "size: %6d ", size); __int64 start = GetCurrentMilliseconds();
for( int j = 0; j < 100; j++) {
FILE* file = fopen( filename, openmode); fseek( file, 0, SEEK_SET); for( int pos = 0; pos < total_size; pos += size) {
fread( buffer, size, 1, file); } fclose( file);
}
__int64 end = GetCurrentMilliseconds();
int ms = (int)( end - start);
printf( "time: %6d\n", ms); } return 0;
};
Здесь собственно чтение повторяется многократно, чтобы получались значения времени заметно больше нуля и можно было сделать оценку. При работе такого теста получается следующий отчет:
size: 1 time: 2856 size: 16 time: 385 size: 32 time: 307 size: 64 time: 268 size: 128 time: 252 size: 256 time: 240 size: 512 time: 220 size: 1024 time: 115 size: 2048 time: 63
196 ГЛАВА 2. ГЛОБАЛЫ
size: 4096 time: 35 size: 8192 time: 23 size: 16384 time: 17 size: 32768 time: 14 size: 65536 time: 12
Здесь выполнялось чтение файла, размещенного в файловой системе с кластером 4096 байт. Можно проанализировать соотношение времени выполнения и порции чтения, например размеры чтений 4096 и 32768 отличаются в 8 раз, но времена выполнения для них отличаются в 2, 5 раза. При этом размеры чтений 4096 и 512 также отличаются в 8 раз, но времена чтения отличаются уже в 6, 2 раз. Соотношение показывает, что используется весьма неплохой диск и контроллер диска.
Дополнительно оценим, каково соотношение времен при шаге чтения, отличающемся в 2 раза:
size: 32 time: 307
size: 64 time: 268
size: 128 time: 252
size: 256 time: 240
size: 512 time: 220
size: 1024 time: 115
size: 2048 time: 63
size: 4096 time: 35
size: 8192 time: 23
size: 16384 time: 17
size: 32768 time: 14
size: 65536 time: 12
1,1
1,1
1,0
1,0
1,1
1,9
1,8
1,8
1,5
1,4
1,2
Отношения показывают, то относительная эффективность чтения в данном случае группируется вокруг размера, соответствующего размеру кластера.
Таким образом, с точки зрения эффективности чтения с диска нет смысла делить работу с базой на блоки по размеру существенно меньше или существенно больше чем кластер.
2.3. РАЗМЕР БЛОКА 197
Следующим фактором для оценки ценности чтения является потен­циальное кеширование. Один блок содержит целый набор порций. Даже если нам необходима только одна из них, мы читаем весь блок. При этом, поскольку блок помещается в кеше, то также в кеше оказываются остальные порции, размещенные в этом же блоке. И, если у нас высока вероятность обращения к данным, находящихся далее в сортированном порядке в том же блоке, то высока вероятность их получения уже из ке­шированного блока без чтения с диска. Таким образом, увеличивая раз­мер блока, мы, не сильно ухудшая относительную эффективность физи­ческого времени доступа, улучшаем коэффициент кеширования данных, или вероятность получения следующей порции без чтения.
Другим фактором для оценки эффективности работы в зависимости от размера блока является применяемое структурирование самих дан­ных. Если наши порции (ключи в случае MUMPS) маленькие, то для поиска нужного ключа в одном блоке надо перебрать (пусть и в сортиро­ванном виде) несколько ключей, находящихся в блоке. При увеличении размера блока мы статистически увеличиваем число ключей в блоке и время поиска в одном блоке. При этом, увеличивая число ключей в блоке, мы одновременно увеличиваем число ссылок, выдаваемых этим блоком и увеличиваем коэффициент ветвления этого блока, что очень важно для быстроветвящихся деревьев, к которым и относятся B* дере­вья.
Грубо говоря, имея блок большего размера, мы сильнее отсекаем об­ласть поиска среди дочерних блоков, и два соседних ключа вырезают в базе меньшее число дочерних ключей, и в идеале каждая ссылка с блока ссылок должна вести на блок данных. И обратно, если в блоке ключи большие, то их в блоке помещается меньше, и их быстрее найти при поиске в блоке, но такой блок дает меньшее отсечение области по­иска. В самом крайнем и экстремальном случае, если в блоке всего один ключ и две ссылки, то такой блок делит область базы всего на 2 части, и относительная эффективность такого блока (необходимо целое чтение блока для деления всего на 2) минимальна.
Есть и обратная сторона размера блока - если он слишком маленький, то на нем может вообще не поместиться три ключа (логический минимум для делящегося дерева), и не стоит применять блоки такого размера, что в них СУБД не сможет записать что-то осмысленное.
Несложно видеть, что, в действительности, вопрос размера блока непрост. Для того, чтобы СУБД работала эффективно, надо как-то вы­брать такой размер. И, действительно, крупными фирмами проводи­лись соответствующие исследования, при каких размерах блоков данных СУБД работают более эффективно.
198 ГЛАВА 2. ГЛОБАЛЫ
Общее резюме таких исследований, проводимых в том числе целе­направленно фирмой Oracle, вылилось в набор простых эмпирических правил:
1. Для применяемых в настоящее время компьютеров, жестких дис­ков, файловых систем и среднестатистических характеристик дан­ных оптимум размера находится примерно между 6 и 12 килобайт.
2. Если данные по размеру стремятся к малым порциям, то размер блока оптимальнее принять 2 или 4 килобайт.
3. Если данные стремятся к увеличению порций, то размер блока оптимальнее увеличить до 16 или 32 килобайт.
4. Если данные изначально составляют очень большие порции, то раз­мер блока оптимальнее использовать в 64 килобайт или использо­вать отдельные файлы не блочного формата.
В настоящее время большинство СУБД класса MUMPS используют
размер блока 8 килобайт в качестве основного.

2.4 Кеширование блоков

Формально говоря, системе базы данных, работающей с цепочками бло­ков, по каждому обращению к глобалу требуется несколько чтений с диска. Для того, чтобы доступ к глобалам был не настолько медленным, СУБД применяют механизм кеширования.
Основной принцип оптимизации состоит в том, чтобы по возможно­сти не делать то, что уже было сделано и возможно использовать повтор­но. Кеширование блоков данных позволяет не читать блоки повторно, если блок уже был прочитан и его можно использовать повторно.
Современные СУБД придерживаются одного из ключевых принципов серверного программирования - возможность работать на ограниченной памяти и никогда не выходить за административно устновленные преде­лы. Такое же ограничение устанавливается на кеши СУБД. Их может быть несколько. М системы поддерживают кеширование различных дан­ных - блоков файлов баз данных, байткода рутин, каталог глобалов, и другие.
Общие принципы кеширования одинаковы для разных алгоритмов кеширования. Если база данных читает блок, то помещает его в кеш блоков. Если в кеше есть еще не занятое место, то оно занимается. Если
2.4. КЕШИРОВАНИЕ БЛОКОВ 199
свободных мест в кеше нет, то механизм кеширования должен решить, какое место освободить, или вытеснить из кеша. Различные алгоритмы кеширования, собственно говоря, и различаются по стратегии вытесне­ния из кеша. Существуют самые различные стратегии вытеснения, есть общераспространенные и общеприменяемые, есть специализированные, учитывающие специфику доступа к кешируемым элементам, и для от­дельных проектов могут быть разработаны собственные стратегии.
Для кеширования блоков B-деревьев достаточно хорошо подходит ал­горитм LRU. У него есть несколько модификаций (например, K-LRU, 2S­LRU), но уже применение стандартного алгоритма LRU дает очень вы­сокий коэффициент попадания в кеш большинства блоков дерева. Боль­шинство современных М систем для кеширования блоков использует именно стратегию LRU или его модификацию.
Название LRU означает Least Recently Used, или "наименее недав­но используемый", или вытеснение элемента к которому наиболее давно не было обращений. В этой стратегии для элементов кеша ведется оче­редность обращений к ним таким образом, что любой, к которому про­изошло обращение, помещается в начало очереди. Помещается конечно не сам элемент, а его индикатор (указатель, номер). При этом послед­ний элемент всегда оказывается наиболее долго неиспользуемым и при необходимости вытеснения вытесняется именно этот элемент.
Стратегия LRU приводит к тому, что при двух обращениях к раз­личным веткам глобала повторно используется некоторая часть начала дерева, или его корневая часть. При достаточно большом кеше в нем мо­гут оказаться большинство используемых блоков. Одновременно с тем, при чтении второй ветки глобала может вообще не произойти ни одного чтения с диска, если вторая ветка хранится в уже прочитанных блоках.
32-битные системы ограничены размером используемой памяти в 2 гигабайта рабочего пространства, в котором также должны разместить­ся и другие области памяти, кроме кеша глобалов. В случае же исполь­зования 64-битных систем СУБД может использовать огромные объемы кеша, столько сколько может быть предоставлено аппаратно.
Администратору СУБД обычно рекомендуется использовать размер кеша, достаточный для комфортной работы сервера, но не стоит пола­гать, что увеличением кеша всегда можно добиться роста производи­тельности. При увеличении кеша до какого-то предела действительно можно увеличить общую производительность доступа к глобалам, но нужно понимать, что другим программам работающим на сервере также может понадобиться память и операционная система может вытеснить часть кеша СУБД в файл подкачки, что обесценит кеширование.
Минусом кеширования является обратная сторона его плюса - если
200 ГЛАВА 2. ГЛОБАЛЫ
блоки отыскиваются в кеше, то производительность сервера стабильна и относительно предсказуема, чего и добиваются применением кеширо­вания. С другой стороны, если один или несколько процессов начнут выполнять совершенно нехарактерные для выбранной стратегии кеши­рования глобалов операции с блоками, то кеш вымывается или обесце­нивается. Или, другими словами, из кеша вытесняются блоки, ценные для большинства других процессов.
К таким характерным операциям выхода за пределы стратегии мо­гут быть отнесены работа с большими данными в условиях недостаточ­но большого кеша, бекап, экспорт. При активной работе таких отдель­ных процессов кеш, несмотря на относительную стабильность алгоритма LRU, все же может обесцениться так, что это станет заметно для других процессов. Это проявляется в том, что общая производительность сер­вера при обычном выполнении большинства процессов начинает непред­сказуемо падать и восстанавливаться. Для решения этой проблемы ад­министраторы систем либо выполняют такие нестандартные операции, обесценивающие кеш, в определенное регламентное время, либо добав­ляют объем кеша, согласуясь с аппаратными возможностями.
Что интересно, система Cach´e в отношении стратегии кеширования допускает применение особого режима для процесса, так называемый пакетный режим. При переводе процесса в такой режим ему ограничи­вается использование кеша таким образом, чтобы он вытеснял не все блоки, а только в определенных пределах, не затрагивая б´ольшую часть общего кеша. Такое решение весьма перспективно, и позволяет предо­ставить процессам общего назначения общую стратегию, а процессам нестандартного выполнения отдельную стратегию.

2.5 Структуры

Системы MUMPS на физическом уровне оперируют только парами вида ключ-значение. Сам ключ может быть составным, и состоять из последо­вательности значений индексов. Значение, записанное по этому ключу, рассматривается как последовательность байт. В зависимости от реа­лизации М системы количество байт в значении, количество байт в значении каждого индекса и количество индексов может отличаться.
Вообще говоря, этим и исчерпывается определение хранения данных в М системах. С одной стороны, определение простое, с другой стороны, определение оставляет разработчикам огромный выбор.
Пары ключ-значение в М системах во-первых, не декларированы и, во-вторых, не типизированы. По полному ключу мы можем записать в