]> Untitled Git - articles.git/blob
4c199cb584b2eb7615f27907eb0948a29a7e57ac
[articles.git] /
1 ## PostgreSQL
2
3 PostgreSQL -- мощная современная СУБД со свободным и открытым исходным кодом.
4 В отличие от предыдущих двух исследуемых проектов то как устроен внутри себя PostgreSQL я знаю не по наслышке.
5 Поэтому у нас будет возможность сравнить результаты которые покажет Natch с фактическим знанием о том как распространяются данные через подсистемы проекта
6 В PostgreSQL поддерживаются различные сложные, составные, типы данных.
7 В рамках данного исследования предлагается взять строковое представление одного их таких типов данных и проследить как строковые константы из этого типа перейдут из текстового представление в представление внутреннее и как это внутреннее представление будет сохранено на диск.
8
9 ### План эксперимента
10
11 * Соберем PostgreSQL с отладочной информацией
12 * В качестве исследуемого типа данных, выберем тип данных `[tsvector](https://www.postgresql.org/docs/current/datatype-textsearch.html)`, так как он с одной стороны содержит в себе набор отдельных строковых констант, за которыми будет удобно наблюдать в режиме помеченных данных, а с другой стороны тип данных сам по себе не является излишне сложным.
13 * Сохраним строковое представление константы типа `tsvector` в отдельный файл. Данные из этого файла в нашем эксперименте Natch будет отслеживать как "помеченные"
14 * Напишем несложный скрипт заворачивающий строку из вышеупомянутого файла в SQL-запрос осуществляющий вставку константы типа `tsvector` в ранее созданную таблицу
15 * Под контролем Natch, запустим вышеупомянутый скрипт, осуществим вставку, после чего дадим команду `CHECKPOINT`, чтобы принудить postgres сбросить все закешированные страницы хранилища на диск.
16
17 ### Сборка и установка
18
19
20 Перед началом работы убеждаемся что системный PostgreSQL остановлен:
21
22 ```bash
23 $ sudo service postgresql stop
24 ```
25
26
27 #### Установка зависимостей
28
29 Как и в пошлый раз установим зависимости используя зависимости сборки пакета `postgres` из debain и добавим несколько пакетов которые необходимы при сборке dev-ветки.
30
31
32 ```bash
33 $ sudo apt-get build-dep postgresql
34 $ sudo apt-get install libicu-dev bison flex libreadline-dev zlib1g-dev
35 ```
36
37 #### Получение исходников
38
39 ```bash
40 $ git clone  https://git.postgresql.org/git/postgresql.git -b REL_17_STABLE ~/postgres/REL_17_STABLE
41 ```
42
43
44 #### Сборка и установка
45 ```bash
46 cd ~/postgres/REL_17_STABLE
47 $ ./configure --enable-debug --prefix=$HOME/postgres/.install/REL_17_STABLE
48 $ make -j8
49 $ make install
50 ```
51
52 #### Создание управляющих скриптов
53
54 Скрипты для запуска и подключения к базе данных размещаем в директории `~/postgres/.install/REL_17_STABLE-scripts`
55
56 `base._sh` - скрипт определяющий глобальные переменные:
57
58 ```bash
59 export MY_PG_BIN=$HOME/postgres/.install/REL_17_STABLE/bin
60 export MY_PG_DATA=$HOME/postgres/.install/REL_17_STABLE-data
61 ```
62
63 `initdb.sh` - скрипт для инициализации кластера БД:
64
65 ```bash
66 #!/bin/sh
67
68 source ./base._sh
69
70 rm -r $MY_PG_DATA
71
72 $MY_PG_BIN/initdb -D $MY_PG_DATA
73
74 ```
75
76 `restart.sh` - скрипт для запуска/перезапуска нашего экземпляра PostgreSQL
77
78 ```bash
79 #!/bin/bash
80
81 pkill postgres
82 sleep 2
83
84 source ./base._sh
85
86 $MY_PG_BIN/pg_ctl start -D $MY_PG_DATA -l $MY_PG_DATA/log
87 ```
88
89 `psql.sh` - скрипт для подключения к БД через коммандно-строчный интерфейс psql
90
91 ```bash
92 #!/bin/bash
93
94 source ./base._sh
95
96 $MY_PG_BIN/psql postgres
97 ```
98
99 `psql_con.sh` - скрипт перенаправляющий поток стандартного вывода на вход нашего psql
100
101 ```
102 #!/bin/bash
103
104 source ./base._sh
105
106 cat - | $MY_PG_BIN/psql postgres
107 ```
108
109 #### Инициализация кластера БД и создание тестовой таблицы
110
111 При помощи скрипта `initdb.sh` создаем кластер БД:
112
113 ```bash
114 $ cd ~/postgres/.install/REL_17_STABLE-scripts
115 $ ./initdb.sh
116 ```
117
118 Запускаем СУБД
119
120 ```bash
121 $ ./restart.sh
122 ```
123
124 далее подключаемся к базе данных с помощь скрипта `psql.sh`
125
126 ```bash
127 $ ./psql.sh
128 ```
129
130 создаем таблицу для вставки помеченных данных
131
132 ```sql
133 create table test (t tsvector);
134 ```
135
136 выходим из psql
137
138 ```sql
139 exit
140 ```
141
142 ### Подготовка помеченных данных
143
144 В качестве основы для наших помеченных данных, возьмем основной [пример из документации](https://www.postgresql.org/docs/current/datatype-textsearch.html) расширив его дополнительными синтаксическими конструкциями описанными ниже по тексту документации.
145 В результате получим файл вида
146
147 ```
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
149 ```
150
151 #### Тестирование вставки тестовых данных
152
153 Перед записью трассы следует убедиться, что мы умеем правильно вставлять в таблицу данные из файла IN.
154 Вставку предполагается делать командой
155
156 ```
157 $ cd ~/postgres/.install/REL_17_STABLE-scripts
158 $ echo INSERT INTO test VALUES \( \'`cat IN`\'::tsvector\)\; CHECKPOINT | ./psql_con.sh
159
160 ```
161
162 Как вы можете видеть в этой команде на стандартный вывод печатается SQL-запрос вставки, в который добавляется содержимое файла с помеченными данными.
163 За запросом вставки следует команда `CHECKPOINT`, которая инициирует принудительный сброс на диск кэша страниц хранилища PostgreSQL.
164 Полученная строка направляется в `psql`.
165 Таким образом помеченными оказываются только данные из файла `IN`, но не сам текст запроса.
166
167 Убедитесь, что команда отрабатывает без ошибок и данные действительно попадают в таблицу `test`
168
169 #### Очистка окружения перед проведением эксперимента
170
171 Перед проведением эксперимента таблицу `test` рекомендуется очистить от старых данных, чтобы они не смущали нас своим видом.
172
173 ```sql
174 DELETE FROM test;
175 ```
176
177 ### Проведение исследования
178
179 #### Создание проекта natch
180
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/`
185
186 #### Запись трассы
187
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.
200
201 #### Генерация отчета
202
203 Далее следуя штатной инструкции при помощи команды `natch replay` снимаем трассу между первым и вторым снепшотом и загружаем ее в snatch.
204
205 ### Анализ результатов
206
207 #### Ввод/вывод помеченных данных
208
209 Помеченные данные должны через стандартный ввод попасть в `psql`, после чего попасть в бэкенд PostgreSQL через unix-сокет, через который `psql` по умолчанию подключается к базе.
210 После вставки мы не перезагружали сервер, данные все еще будут закешированы, и следовательно останутся помеченными.
211 Поэтому запросив данные из таблицы мы по факту должны получить значение из страничного кеша, которое все еще сохранит пометку.
212
213 Посмотрим как это выглядит на практике
214
215 ##### Ввод `psql`
216
217 ![...](3. PostgreSQL.imgs/in_psql_resources.png)
218
219 На картинке выше мы видим, что процесс `psql` получил помеченные данные из сокета `pipe` (это наше перенаправление вывода) и произвел какие-то операции с сокетом `unix` (видимо передал данные в backend).
220 Посмотрим на содержание этих обменов.
221
222 В `pipe` мы видим наш SQL запрос содержащий помеченные данные
223
224 ![...](3. PostgreSQL.imgs/in_psql_pipe-read.png)
225
226 В `unix` сокете мы видим как тот же самый запрос с помеченными данными отправляется на запись в сокет, через который `psql` подключен к PostgreSQL 
227
228 ![...](3. PostgreSQL.imgs/in_psql_unix-write.png)
229
230
231 #### Ввод `postgres`
232
233 В процессе получения запроса вставки принимает участие и процесс `postgresql`
234
235 ![...](3. PostgreSQL.imgs/in_postgres_resoruces.png)
236
237 На картинке выше мы видим что процесс обменивается помеченными данными через unix сокет (спойлер: он их чиатет) и кроме этого производит запись этих данных на диск (об этом ниже).
238
239 Данные принимаемые процессом `postgres` полностью (что неудивительно) совпадают с данными которые отправлял процесс `psql`, с точностью до направления движения данных.
240 То что `psql` писал `postgres` читает.
241
242 ![...](3. PostgreSQL.imgs/in_postgres_unix-read.png)
243
244 #### Вывод помеченных данных
245
246 Вывод в нашем случае работает похожим образом что и ввод.
247 `psql` получает запрос из эмуляротра терминала, пренаправляет запрос бэкенду `postgres` через unix-сокет, получает обратно ответ и выводит его в консоль.
248
249 ##### Вывод `psql`
250
251 ![...](3. PostgreSQL.imgs/out_psql_resources.png)
252
253 На этом скриншоте мы видим что помеченные данные проходят через unix-сокет и устройство виртуального терминала
254
255 Ниже мы видим как происходит обмен данных через unix socket.
256 Видим отправленный запрос `select` и до этого более ранний `checkpoint`.
257 Видим как `psql` получает через сокет помеченные данные в ответ на отправленный запрос запрос
258
259 ![...](3. PostgreSQL.imgs/out_psql_unix-read.png)
260
261 Далее мы видим как помеченные данные полученные от backend'а в человекочитаемом виде отправляются на консоль:
262
263 ![...](3. PostgreSQL.imgs/out_psql_tty1-write.png)
264
265 ##### Вывод `postgres`
266
267 В процессе `postgres` обмен данными через unix-сокет ожидаемо происходит полностью аналогично с `pslq`, с точностью до направления:
268
269 ![...](3. PostgreSQL.imgs/out_postgres_resources.png)
270
271 ![...](3. PostgreSQL.imgs/out_postgres_unix-write.png)
272
273 #### Запись на диск
274
275 С точки зрения операций чтения/записи помеченные данные должны быть записаны на диск дважды.
276 Первый раз в момент фиксации изменений, вносимая правка должна быть записана в т.н. Write Ahead Log (WAL), файл содержащий все последние изменения данных в базе, по которому в случае аварии данные базы могут быть восстановлены до актуального состояние.
277 Второй раз, данные будут записаны в хранилище в котором они будут храниться на постоянной основе.
278 Гарантированной записи в хранилище мы и добивались передавая базе данных команду `CHECKPOINT`.
279
280 ##### Запись в Write Ahead Log
281
282 Запись WAL  происходит в процессе выполнения транзакции.
283 Поэтому запись в WAL мы уже видели в том процессе `postgres` который принимал запрос на вставку помеченных данных:
284
285 ![...](3. PostgreSQL.imgs/in_postgres_resoruces.png)
286
287 Можем теперь посмотреть на то, какие данные были в него записаны, и как в этом случае выглядят наши помеченные данные:
288
289 ![...](3. PostgreSQL.imgs/disk_wal.png)
290
291 Мы явно видим помеченные данные, теперь уже во внутреннем представлении.
292 Строковые константы перемешаны и местами визуально склеены.
293 Так же мы видим, что в записанном образце WAL-лога внутреннее представление вставляемых данных повторено несколько раз, при этом помеченным оказывается один комплект.
294
295 Излишне экземпляры в логе происходят из-за того, что мы были не аккуратны при проведении эксперимента, и производили вставку в не "девственную" базу в которой мы до этого создавали и удаляли аналогичные записи.
296 Записи удаленные с точки зрения MVCC, с точки зрения хранилища данных продолжают существовать, требуют обслуживания и все эти действия отражены в логах операций чтения и записи.
297 При этом следует обратить внимание, что помеченным остается только один экземпляр, только что вставленный, а следы оставшиеся от предыдущих вставок остаются не помеченными.
298
299
300 ##### Запись в основное хранилище.
301
302 Финальная запись в основное хранилище происходит в отдельном процессе.
303 Этот процесс так же как и другие сервисные процессы (воркеры) является форком основного процесса `postgres`, только он выполняет особую роль и называется в соответствии с этой ролью checkpointer.
304 Поэтому в результатах нашего анализа запись в основное хранилище происходит в отдельном процессе так же именуемом `postgres`, но мы знаем, что он судя по всему является воркером checkpointer.
305
306 ![...](3. PostgreSQL.imgs/disk_checkpointer_resources.png)
307
308 Здесь мы видим, что запись помеченных данных происходит в файл `/base/5/16384`, это один из файлов основного хранилища.
309 Если мы загляним внутрь, то увидим следующую картину:
310
311 ![...](3. PostgreSQL.imgs/disk_checkpointer_file.png)
312
313 Здесь мы во-первых вдимем внутреннее представление вставленного tsvector'а, данные которого отмечены как помеченные.
314 Во вторых мы наблюдаем еще один экземпляр наших данных уже не помеченный.
315 Этот экземпляр остался от предыдущих экспериментов и попал в записанные данные так как страница была сброшена из памяти на диск целиком.
316 Можно так же обратить внимание на то, что "старый" экземпляр находится после нового.
317 Это происходит от того, что в PostgreSQL записи добавляются на страницу с конца, это штатное, ожидаемое поведение.
318
319 #### Стек вызовов
320
321 Далее рассмотрим как помеченные данные передаются внутри процессов.
322 Процесс `psql` мы из рассмотрения исключим, с формулировкой "это тема для отдельного исследования".
323 Если посмотреть на историю стека вызовов, то становиться видно, что с помеченными данными происходит много манипуляций вроде копирования и какой-то валидации.
324 Однако как-то содержательно прокомментировать происходящее я не в состоянии, поэтому оставим эту часть реальности за скобками.
325 Поэтому сосредоточимся на том, что происходит с помеченными данными в процессе `postgresql` при добавлении новой записи, и при выводе содержимого таблицы с помеченными данными на экран.
326
327 ##### Стек вызовов процесса `postgres`, ввод
328
329 История стека вызовов `postgres` при вставке помеченных данных в таблицу делится на три принципиальные части.
330
331 В первой части происходит общий "парсинг" запроса вставки:
332
333 ![...](3. PostgreSQL.imgs/callstack_in_1.png)
334
335 На этом скриншоте мы видим как помеченные данные попадают в backed вместе с запросом, проходят предварительную проверку и после чего производится синтаксический разбор самих помеченных данных.
336 Сами помеченные данные при этом никак отдельно не обрабатываются, только туда-сюда копируются и принимают участие в других групповых операциях.
337
338 За синтаксическим разбором следует разбор константных аргументов входящих в состав запроса.
339 Это как раз то место в котором наши помеченные данные будут парситься.
340
341 ![...](3. PostgreSQL.imgs/callstack_in_2.png)
342
343 И действительно, в глубине стека вызовов мы наблюдаем функцию `tsvectorin`, которая занимается переводом констант типа `tsvector` из внешнего, текстового, представления в представление в котором она будет обрабатываться внутри СУБД.
344 Внутри этой функции происходит какая-то достаточно развесистая обработка, тип данных-то сложный.
345
346 Далее идет блок вызовов посвященный выполнению запроса:
347
348 ![...](3. PostgreSQL.imgs/callstack_in_3.png)
349
350 В первой подветке идет формирование записи (`heap_fill_tuple`).
351 Помеченные данные судя по стеку вызовов копируются куда-то в формируемую запись.
352
353 Во второй подветке производится вставка сформированной записи в таблицу (`heapam_tuple_insert`).
354 Тут тоже помеченные данные просто копируются из одного места в другое
355
356 И в третьей, последней подветке происходит запись произведенных изменений в WAL-лог (`XLogInsert`).
357 Помеченные данные принимают участие в вычислении контрольной суммы, и копируются вместе со всей остальной дельтой произошедшей во время транзакции.
358
359 ### Итог
360
361 Не смотря на то, что эксперимент был проведен не очень чисто, и наши данные оказались замусорены артефактами от предыдущих запусков, нам удалось отследить маршрут движения помеченных данных данных, и наблюдаемая картина совпала с нашими теоретическими представлениями.
362
363 В качестве развития темы читателю предлагается во-первых повторить предложенный эксперимент полностью пересоздав кластер базы данных перед проведением эксперимента, и убедиться что мы в этом случае артефакты предыдущих вставок в отчете natch не наблюдаются.
364
365 Так же читателю предлагается повторить эксперимент с перезагрузкой базы данных между вставкой помеченных данных и их получением.
366 Предположительно, при прохождении через дисковое хранилище данные должны утратить пометку.
367 Однако в случае, если они останутся в дисковом кеше уровня ОС, пометка может и сохраниться.
368 Интересно будет выяснить каково будет фактическое поведение, и как поставить эксперимент, чтобы дисковый кеш тоже оказался бы гарантированно сброшен. 
369
370
371 **FIXME**
372 sudo su
373 echo "deb-src http://deb.debian.org/debian trixie main" >>/etc/apt/sources.list
374 apt-get update
375