Блоки. Внутреннее устройство файла базы данных Caché. Часть 3

Как и в двух предыдущих частях (часть 1, часть 2), я продолжаю знакомить вас с внутренним устройством баз данных Caché. На этот раз я расскажу о том, что еще можно узнать, и в чем может помочь мой проект Caché Blocks Explorer.

a77291a3d5a647ffa812a682453f226a.png
Думаю многим показалось знакомым то, что изображено на картинке (кликабельно). Когда я хотел изобразить фрагментацию глобалов, для меня прообразом были разные многочисленные утилиты по дефрагментации диска. И, надеюсь, у меня получилось сделать настолько же полезный продукт.

Данная утилита отображает карту блоков. Каждый квадратик представляет блок, и цвет его соответствует определенному глобалу, который указан в легенде. Сам блок так же показывает, насколько он заполнен данными, что позволяет составить визуальное представление о заполнении файла базы данных. Отображение блоков уровня глобала и карты блоков пока не реализовано — они, как и все пустые блоки, будут отображаться белым цветом.

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

Гифка
53c5de696d3448398b62b19c600e7a47.gif


Продолжим работать с базой данных, что мы использовали в предыдущей статье. Я удалил все глобалы, они нам не понадобятся. И сгенерировал новые данные на основе пакета классов Sample из области SAMPLES. Для этого я настроил маппинг пакета в мою область HABR.

Настройка маппинга
2431dea51fcc42d8b854fc0ec84d0025.png


Выполнил команду генерации данных.

d ##class(Sample.Utils).Generate(20000)


Результат на нашей карте получился таким:

00326fcdd5094c85af50f58c8ceccc63.png

Можно обратить внимание, что блоки начинают заполняться не с самого начала файла. С 16 блока начинаются блоки указателей верхнего уровня, а с 50-го блоки данных. 16 и 50 — это значения по умолчанию, при желании их можно изменить. За начало блока указателей отвечает свойство NewGlobalPointerBlock в классе SYS.Database, этим свойством задается значение по умолчанию для новых глобалов. Для уже созданных глобалов можно поменять его в к классе %Library.GlobalEdit через свойство PointerBlock. Блок, с которого будут начинаться блоки данных, указан в свойстве NewGlobalGrowthBlock класса SYS.Database, для отдельно взятого глобала это свойство GrowthBlock в классе %Library.GlobalEdit. Эти значения имеет смысл менять только для тех глобалов, которые пока не имеют данных, потому что изменение этого свойства не влияет на текущее расположение блока верхних указателей, либо блоков данных.

Здесь мы видим, что больше всего данных у нас в глобале ^Sample.PersonD с 989 блоками 83% заполнения. На втором месте ^Sample.PersonI 573 блоков, а заполнен этот глобал уже на 70%. Мы можем выбрать любой интересующий нас глобал, чтобы посмотреть точно, какие блоки задействованы под этот глобал. И для глобала ^Sample.PersonI, мы можем увидеть, что часть блоков почти не заполнена. Так же видно, что блоки этих двух глобалов перемешаны. Это и понятно, ведь при создании новых объектов наполняются оба глобала: один данными, другой индексами к таблице Sample.Person.

f46414e0b42c4be8a39be1c23e1dda65.png

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

    set id=""
    set first=$order(^Sample.PersonD(""),1)
    set last=$order(^Sample.PersonD(""),-1)
    for id=first:$random(5)+1:last {
        do ##class(Sample.Person).%DeleteId(id)
    }


После запуска данного кода получили результат, который можно увидеть ниже. Появилось несколько пустых блоков, блоки теперь заполнены на 64-67%.

cbbbbfdb32454d2b955a4573ac4d34d2.png

Для работы с базой данных у нас есть возможность пользоваться утилитой ^DATABASE области %SYS. Воспользуемся некоторыми её возможностями.

79f4d49ba79a4a41a7c1d470d98f94fa.png

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

987afe6d0a494b5b9f338d0126e94999.png

e6e35ff2286142a39b1fb0e71b06c90c.png

Как видите, в результате сжатия глобалов мы получили запрошенные 90% заполнения, или близкие к этому значения. В результате сжатия, так же получилось так, что ранее пустые блоки теперь стали заполнены перемещенными из других блоков данными. Сжатие всех глобалов в базе данных можно осуществить с помощью утилиты ^DATABASE (пункт 7), либо выполнением следующей команды, передав ей первым параметром путь к базе данных:

d ##class(SYS.Database).CompactDatabase("c:\intersystems\ensemble\mgr\habr\")


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

Пример кода
Здесь я удаляю случайные порции данных для того, чтобы образовались пустые блоки.
    set gn=$name(^Sample.PersonD)
    set first=$order(@gn@(""),1)
    set last=$order(@gn@(""),-1)
    for i=1:1:10 {
        set id=$random(last)+first
        write !,id
        set count=0
        for {
            set id=$order(@gn@(id))
            quit:id=""
            do ##class(Sample.Person).%DeleteId(id)
            quit:$increment(count)>1000
        }
    }


610a8e0b7f514b75b002a63c5f2703f0.png

Здесь мы видим появление пустых блоков. Caché предоставляет возможность переместить эти пустые блоки в конец файла базы данных, и дальше есть возможность уменьшить размер базы данных. Для того чтобы переместить пустые блоки, выполним метод FileCompact из класса SYS.Database в системной области, либо с помощью утилиты ^DATABASE пункт 13. Этот метод принимает три параметра: путь к базе данных, желаемый размер свободного места в конце файла (по умолчанию 0), и возвращаемый параметр – сколько места свободного получилось.

do ##class(SYS.Database).FileCompact("c:\intersystems\ensemble\mgr\habr\",999)


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

a012846f9cfa4c019df2524a768450cc.png

Дефрагментация


Теперь мы можем перейти к дефрагментации наших глобалов. Этот процесс переставит блоки каждого глобала по порядку. В процессе работы утилиты может потребоваться свободное место в конце файла базы данных, и при необходимости оно будет добавлено. Выполнить дефрагментацию можно с помощью утилиты ^DATABASE пункт 14, либо такой командой:

d ##class(SYS.Database).Defragment("c:\intersystems\ensemble\mgr\habr\")


a3865d87b0cd47d6ab9d06ff5d150630.png

Освобождение свободного места


Теперь мы видим как все наши глобалы выстроились по очереди. Но для дефрагментации все таки понадобилось увеличить свободное место в файле базы данных. И мы можем освободить это место, воспользовавшись пунктом 12 в утилите ^DATABASE или командой:

d ##class(SYS.Database).ReturnUnusedSpace("c:\intersystems\ensemble\mgr\habr\")


4f74b4efed0142759fb5f0b7820a777e.png

Теперь наша БД занимает намного меньше места, и осталось всего 1МБ свободного места в файле базы данных. Возможность дефрагментировать глобалы в базе данных и управлять свободным местом, перемещая и освобождая свободные блоки, появилась сравнительно недавно. Ранее при необходимости дефрагментировать и уменьшить размер файла базы данных, приходилось пользоваться утилитой ^GBLOCKCOPY. Она выполняла поблочное копирование из одной базы данных в другую вновь созданную, с возможностью выбора конкретных глобалов. Эта утилита по прежнему доступна.

© Habrahabr.ru