3 PostgreSQL -- мощная современная СУБД со свободным и открытым исходным кодом.
4 В отличие от предыдущих двух исследуемых проектов то как устроен внутри себя PostgreSQL я знаю не по наслышке.
5 Поэтому у нас будет возможность сравнить результаты которые покажет Natch с фактическим знанием о том как распространяются данные через подсистемы проекта
6 В PostgreSQL поддерживаются различные сложные, составные, типы данных.
7 В рамках данного исследования предлагается взять строковое представление одного их таких типов данных и проследить как строковые константы из этого типа перейдут из текстового представление в представление внутреннее и как это внутреннее представление будет сохранено на диск.
11 * Соберем PostgreSQL с отладочной информацией
12 * В качестве исследуемого типа данных, выберем тип данных `[tsvector](https://www.postgresql.org/docs/current/datatype-textsearch.html)`, так как он с одной стороны содержит в себе набор отдельных строковых констант, за которыми будет удобно наблюдать в режиме помеченных данных, а с другой стороны тип данных сам по себе не является излишне сложным.
13 * Сохраним строковое представление константы типа `tsvector` в отдельный файл. Данные из этого файла в нашем эксперименте Natch будет отслеживать как "помеченные"
14 * Напишем несложный скрипт заворачивающий строку из вышеупомянутого файла в SQL-запрос осуществляющий вставку константы типа `tsvector` в ранее созданную таблицу
15 * Под контролем Natch, запустим вышеупомянутый скрипт, осуществим вставку, после чего дадим команду `CHECKPOINT`, чтобы принудить postgres сбросить все закешированные страницы хранилища на диск.
17 ### Сборка и установка
20 Перед началом работы убеждаемся что системный PostgreSQL остановлен:
23 $ sudo service postgresql stop
27 #### Установка зависимостей
29 Как и в пошлый раз установим зависимости используя зависимости сборки пакета `postgres` из debain и добавим несколько пакетов которые необходимы при сборке dev-ветки.
33 $ sudo apt-get build-dep postgresql
34 $ sudo apt-get install libicu-dev bison flex libreadline-dev zlib1g-dev
37 #### Получение исходников
40 $ git clone https://git.postgresql.org/git/postgresql.git -b REL_17_STABLE ~/postgres/REL_17_STABLE
44 #### Сборка и установка
46 cd ~/postgres/REL_17_STABLE
47 $ ./configure --enable-debug --prefix=$HOME/postgres/.install/REL_17_STABLE
52 #### Создание управляющих скриптов
54 Скрипты для запуска и подключения к базе данных размещаем в директории `~/postgres/.install/REL_17_STABLE-scripts`
56 `base._sh` - скрипт определяющий глобальные переменные:
59 export MY_PG_BIN=$HOME/postgres/.install/REL_17_STABLE/bin
60 export MY_PG_DATA=$HOME/postgres/.install/REL_17_STABLE-data
63 `initdb.sh` - скрипт для инициализации кластера БД:
72 $MY_PG_BIN/initdb -D $MY_PG_DATA
76 `restart.sh` - скрипт для запуска/перезапуска нашего экземпляра PostgreSQL
86 $MY_PG_BIN/pg_ctl start -D $MY_PG_DATA -l $MY_PG_DATA/log
89 `psql.sh` - скрипт для подключения к БД через коммандно-строчный интерфейс psql
96 $MY_PG_BIN/psql postgres
99 `psql_con.sh` - скрипт перенаправляющий поток стандартного вывода на вход нашего psql
106 cat - | $MY_PG_BIN/psql postgres
109 #### Инициализация кластера БД и создание тестовой таблицы
111 При помощи скрипта `initdb.sh` создаем кластер БД:
114 $ cd ~/postgres/.install/REL_17_STABLE-scripts
124 далее подключаемся к базе данных с помощь скрипта `psql.sh`
130 создаем таблицу для вставки помеченных данных
133 create table test (t tsvector);
142 ### Подготовка помеченных данных
144 В качестве основы для наших помеченных данных, возьмем основной [пример из документации](https://www.postgresql.org/docs/current/datatype-textsearch.html) расширив его дополнительными синтаксическими конструкциями описанными ниже по тексту документации.
145 В результате получим файл вида
148 a:1A fat:2B,4C cat:5D sat:4 on:5 a:6 mat:7 and:8 ate:9 a:10 fat:11 rat:12
151 #### Тестирование вставки тестовых данных
153 Перед записью трассы следует убедиться, что мы умеем правильно вставлять в таблицу данные из файла IN.
154 Вставку предполагается делать командой
157 $ cd ~/postgres/.install/REL_17_STABLE-scripts
158 $ echo INSERT INTO test VALUES \( \'`cat IN`\'::tsvector\)\; CHECKPOINT | ./psql_con.sh
162 Как вы можете видеть в этой команде на стандартный вывод печатается SQL-запрос вставки, в который добавляется содержимое файла с помеченными данными.
163 За запросом вставки следует команда `CHECKPOINT`, которая инициирует принудительный сброс на диск кэша страниц хранилища PostgreSQL.
164 Полученная строка направляется в `psql`.
165 Таким образом помеченными оказываются только данные из файла `IN`, но не сам текст запроса.
167 Убедитесь, что команда отрабатывает без ошибок и данные действительно попадают в таблицу `test`
169 #### Очистка окружения перед проведением эксперимента
171 Перед проведением эксперимента таблицу `test` рекомендуется очистить от старых данных, чтобы они не смущали нас своим видом.
177 ### Проведение исследования
179 #### Создание проекта natch
181 При создании проекта natch, уже на host-машине следуем штатной инструкции.
182 В качестве источника помеченных данных указываем файл `IN` о котором мы говорили выше.
183 В моем случае `/home/nataraj/postgres/.install/REL_17_STABLE-scripts/IN`.
184 В качестве директории содержащий дополнительные модули с отладочной информацией указать место в которое были установлены бинарные файлы нашей сборки PostgreSQL: `/home/nataraj/postgres/.install/REL_17_STABLE/bin` и `/home/nataraj/postgres/.install/REL_17_STABLE/lib/`
188 - Запускаем штатным образом запись трассы используя команду `natch record`;
189 - В появившемся окне виртуальной машины логинимся в виртуалку;
190 - Останавливаем системный PostgreSQL: `sudo service postgresql stop`;
191 - Переходим в каталог с нашей скриптовой обвязкой: `cd ~/postgres/.install/REL_17_STABLE-scripts`;
192 - Запускаем нашу сборку PostgreSQL: `./restart.sh`;
193 - В консоли Natch делаем первый снапшот (см. штатный tutorial);
194 - Запускаем команду вставки помеченных данных в базу: `echo INSERT INTO test VALUES \( \'`cat IN`\'::tsvector\) | ./psql_con.sh`
195 - Запускаем утилиту `psql` в командно-строчном режиме: `./psql.sh`;
196 - Даем в ней команду `CHECKPOINT;` для гарантированного сброса страничного кеша на диск;
197 - Получаем содержимое таблицы `test` выполнив запрос: `SELECT * FROM test;`;
198 - В консоли Natch делаем второй снапшот;
199 - Завершаем работу Natch.
201 #### Генерация отчета
203 Далее следуя штатной инструкции при помощи команды `natch replay` снимаем трассу между первым и вторым снепшотом и загружаем ее в snatch.
205 ### Анализ результатов
207 #### Ввод/вывод помеченных данных
209 Помеченные данные должны через стандартный ввод попасть в `psql`, после чего попасть в бэкенд PostgreSQL через unix-сокет, через который `psql` по умолчанию подключается к базе.
210 После вставки мы не перезагружали сервер, данные все еще будут закешированы, и следовательно останутся помеченными.
211 Поэтому запросив данные из таблицы мы по факту должны получить значение из страничного кеша, которое все еще сохранит пометку.
213 Посмотрим как это выглядит на практике
217 
219 На картинке выше мы видим, что процесс `psql` получил помеченные данные из сокета `pipe` (это наше перенаправление вывода) и произвел какие-то операции с сокетом `unix` (видимо передал данные в backend).
220 Посмотрим на содержание этих обменов.
222 В `pipe` мы видим наш SQL запрос содержащий помеченные данные
224 
226 В `unix` сокете мы видим как тот же самый запрос с помеченными данными отправляется на запись в сокет, через который `psql` подключен к PostgreSQL
228 
233 В процессе получения запроса вставки принимает участие и процесс `postgresql`
235 
237 На картинке выше мы видим что процесс обменивается помеченными данными через unix сокет (спойлер: он их чиатет) и кроме этого производит запись этих данных на диск (об этом ниже).
239 Данные принимаемые процессом `postgres` полностью (что неудивительно) совпадают с данными которые отправлял процесс `psql`, с точностью до направления движения данных.
240 То что `psql` писал `postgres` читает.
242 
244 #### Вывод помеченных данных
246 Вывод в нашем случае работает похожим образом что и ввод.
247 `psql` получает запрос из эмуляротра терминала, пренаправляет запрос бэкенду `postgres` через unix-сокет, получает обратно ответ и выводит его в консоль.
251 
253 На этом скриншоте мы видим что помеченные данные проходят через unix-сокет и устройство виртуального терминала
255 Ниже мы видим как происходит обмен данных через unix socket.
256 Видим отправленный запрос `select` и до этого более ранний `checkpoint`.
257 Видим как `psql` получает через сокет помеченные данные в ответ на отправленный запрос запрос
259 
261 Далее мы видим как помеченные данные полученные от backend'а в человекочитаемом виде отправляются на консоль:
263 
265 ##### Вывод `postgres`
267 В процессе `postgres` обмен данными через unix-сокет ожидаемо происходит полностью аналогично с `pslq`, с точностью до направления:
269 
271 
275 С точки зрения операций чтения/записи помеченные данные должны быть записаны на диск дважды.
276 Первый раз в момент фиксации изменений, вносимая правка должна быть записана в т.н. Write Ahead Log (WAL), файл содержащий все последние изменения данных в базе, по которому в случае аварии данные базы могут быть восстановлены до актуального состояние.
277 Второй раз, данные будут записаны в хранилище в котором они будут храниться на постоянной основе.
278 Гарантированной записи в хранилище мы и добивались передавая базе данных команду `CHECKPOINT`.
280 ##### Запись в Write Ahead Log
282 Запись WAL происходит в процессе выполнения транзакции.
283 Поэтому запись в WAL мы уже видели в том процессе `postgres` который принимал запрос на вставку помеченных данных:
285 
287 Можем теперь посмотреть на то, какие данные были в него записаны, и как в этом случае выглядят наши помеченные данные:
289 
291 Мы явно видим помеченные данные, теперь уже во внутреннем представлении.
292 Строковые константы перемешаны и местами визуально склеены.
293 Так же мы видим, что в записанном образце WAL-лога внутреннее представление вставляемых данных повторено несколько раз, при этом помеченным оказывается один комплект.
295 Излишне экземпляры в логе происходят из-за того, что мы были не аккуратны при проведении эксперимента, и производили вставку в не "девственную" базу в которой мы до этого создавали и удаляли аналогичные записи.
296 Записи удаленные с точки зрения MVCC, с точки зрения хранилища данных продолжают существовать, требуют обслуживания и все эти действия отражены в логах операций чтения и записи.
297 При этом следует обратить внимание, что помеченным остается только один экземпляр, только что вставленный, а следы оставшиеся от предыдущих вставок остаются не помеченными.
300 ##### Запись в основное хранилище.
302 Финальная запись в основное хранилище происходит в отдельном процессе.
303 Этот процесс так же как и другие сервисные процессы (воркеры) является форком основного процесса `postgres`, только он выполняет особую роль и называется в соответствии с этой ролью checkpointer.
304 Поэтому в результатах нашего анализа запись в основное хранилище происходит в отдельном процессе так же именуемом `postgres`, но мы знаем, что он судя по всему является воркером checkpointer.
306 
308 Здесь мы видим, что запись помеченных данных происходит в файл `/base/5/16384`, это один из файлов основного хранилища.
309 Если мы загляним внутрь, то увидим следующую картину:
311 
313 Здесь мы во-первых вдимем внутреннее представление вставленного tsvector'а, данные которого отмечены как помеченные.
314 Во вторых мы наблюдаем еще один экземпляр наших данных уже не помеченный.
315 Этот экземпляр остался от предыдущих экспериментов и попал в записанные данные так как страница была сброшена из памяти на диск целиком.
316 Можно так же обратить внимание на то, что "старый" экземпляр находится после нового.
317 Это происходит от того, что в PostgreSQL записи добавляются на страницу с конца, это штатное, ожидаемое поведение.
321 Далее рассмотрим как помеченные данные передаются внутри процессов.
322 Процесс `psql` мы из рассмотрения исключим, с формулировкой "это тема для отдельного исследования".
323 Если посмотреть на историю стека вызовов, то становиться видно, что с помеченными данными происходит много манипуляций вроде копирования и какой-то валидации.
324 Однако как-то содержательно прокомментировать происходящее я не в состоянии, поэтому оставим эту часть реальности за скобками.
325 Поэтому сосредоточимся на том, что происходит с помеченными данными в процессе `postgresql` при добавлении новой записи, и при выводе содержимого таблицы с помеченными данными на экран.
327 ##### Стек вызовов процесса `postgres`, ввод
329 История стека вызовов `postgres` при вставке помеченных данных в таблицу делится на три принципиальные части.
331 В первой части происходит общий "парсинг" запроса вставки:
333 
335 На этом скриншоте мы видим как помеченные данные попадают в backed вместе с запросом, проходят предварительную проверку и после чего производится синтаксический разбор самих помеченных данных.
336 Сами помеченные данные при этом никак отдельно не обрабатываются, только туда-сюда копируются и принимают участие в других групповых операциях.
338 За синтаксическим разбором следует разбор константных аргументов входящих в состав запроса.
339 Это как раз то место в котором наши помеченные данные будут парситься.
341 
343 И действительно, в глубине стека вызовов мы наблюдаем функцию `tsvectorin`, которая занимается переводом констант типа `tsvector` из внешнего, текстового, представления в представление в котором она будет обрабатываться внутри СУБД.
344 Внутри этой функции происходит какая-то достаточно развесистая обработка, тип данных-то сложный.
346 Далее идет блок вызовов посвященный выполнению запроса:
348 
350 В первой подветке идет формирование записи (`heap_fill_tuple`).
351 Помеченные данные судя по стеку вызовов копируются куда-то в формируемую запись.
353 Во второй подветке производится вставка сформированной записи в таблицу (`heapam_tuple_insert`).
354 Тут тоже помеченные данные просто копируются из одного места в другое
356 И в третьей, последней подветке происходит запись произведенных изменений в WAL-лог (`XLogInsert`).
357 Помеченные данные принимают участие в вычислении контрольной суммы, и копируются вместе со всей остальной дельтой произошедшей во время транзакции.
361 Не смотря на то, что эксперимент был проведен не очень чисто, и наши данные оказались замусорены артефактами от предыдущих запусков, нам удалось отследить маршрут движения помеченных данных данных, и наблюдаемая картина совпала с нашими теоретическими представлениями.
363 В качестве развития темы читателю предлагается во-первых повторить предложенный эксперимент полностью пересоздав кластер базы данных перед проведением эксперимента, и убедиться что мы в этом случае артефакты предыдущих вставок в отчете natch не наблюдаются.
365 Так же читателю предлагается повторить эксперимент с перезагрузкой базы данных между вставкой помеченных данных и их получением.
366 Предположительно, при прохождении через дисковое хранилище данные должны утратить пометку.
367 Однако в случае, если они останутся в дисковом кеше уровня ОС, пометка может и сохраниться.
368 Интересно будет выяснить каково будет фактическое поведение, и как поставить эксперимент, чтобы дисковый кеш тоже оказался бы гарантированно сброшен.
373 echo "deb-src http://deb.debian.org/debian trixie main" >>/etc/apt/sources.list