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



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

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

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

Гифка

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

Настройка маппинга

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

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

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



Можно обратить внимание, что блоки начинают заполняться не с самого начала файла. С 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.



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

    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%.



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



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





Как видите, в результате сжатия глобалов мы получили запрошенные 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
        }
    }



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

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

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


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


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

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



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


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

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



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

Комментарии (1)


  1. morisson
    01.11.2015 02:08

    Вживую на блоки можно посмотреть тут 82.196.15.114/blocks