https://github.com/postgrespro/demodb
Demonstration Database
https://github.com/postgrespro/demodb
Last synced: about 2 months ago
JSON representation
Demonstration Database
- Host: GitHub
- URL: https://github.com/postgrespro/demodb
- Owner: postgrespro
- License: mit
- Created: 2025-09-22T15:09:08.000Z (3 months ago)
- Default Branch: main
- Last Pushed: 2025-09-25T14:22:33.000Z (3 months ago)
- Last Synced: 2025-10-01T23:57:07.290Z (3 months ago)
- Language: PLpgSQL
- Size: 1.55 MB
- Stars: 2
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# Генератор демобазы
## Назначение
Программа-генератор создает базы данных с той же структурой, что и [демобаза](https://postgrespro.ru/education/demodb), но иным наполнением. Она имеет ряд настроек, позволяющих изменить характеристики данных, есть возможность поменять маршрутную сеть и флот авиакомпании и т. п.
Программу также можно использовать как генератор нагрузки, например для демонстрации систем мониторинга или тренировки оптимизации запросов.
## Установка
Подключитесь в psql к любой базе, кроме `demo`, убедитесь, что находитесь в каталоге репозитория, и выполните установку:
```sql
\! pwd
\i install
```
При необходимости смените текущий каталог командой `\cd`.
В ходе установки будет заново создана база данных `demo`. Если она существовала, то все данные в ней будут потеряны! В этой базе данных будут созданы две схемы: `gen` для объектов генератора и `bookings` для создаваемой демобазы.
Устанавливаются расширения `btree_gist` (для реализации темпорального ключа), `earthdistance` и `cube` (для расчета расстояний на сфере), а также `dblink` (для запуска параллельных процессов). Эти расширения входят в стандартный набор, но убедитесь, что они присутствуют в вашей установке PostgreSQL.
## Быстрый старт
Генерация запускается процедурой `generate`, которой передаются начальное и конечное _модельное_ время (которое будет фигурировать в таблицах демобазы). Например:
```sql
CALL generate( now(), now() + interval '1 year' );
```
Такая команда выполнит необходимую инициализацию, создаст очередь событий и начнет генерацию демобазы за один год.
Чтобы ускорить работу, можно запустить генерацию в нескольких параллельных процессах:
```sql
CALL generate( now(), now() + interval '1 year', 4 );
```
Работа в любом случае выполняется в параллельных процессах (даже если такой процесс один), поэтому команда `CALL` немедленно возвращает управление.
Быстро проверить состояние генерации можно запросом:
```sql
SELECT busy();
```
Истинное значение будет означать, что генерация еще в процессе.
Более полное представление о процессе можно получить, наблюдая за журналом сообщений генератора:
```sql
SELECT * FROM gen.log ORDER BY at DESC LIMIT 30 \watch 60
```
Разрыв соединения не завершает работу генератора. Прервать генерацию досрочно можно командой:
```sql
CALL abort();
```
После успешного окончания генерации полезно ознакомиться с получившимися характеристиками:
```sql
\i check.sql
```
Если все в порядке, полученную демобазу можно выгрузить в виде SQL-скрипта. Для этого в операционной системе выполните команду, находясь в каталоге репозитория:
```sh
./export.sh > `date +%Y%m%d`.sql
```
Генерацию можно продолжить с того момента, на котором она остановилась. Например, сгенерировать данные еще за три месяца:
```sql
CALL continue( now() + interval '1 year 3 month', 4 );
```
## Настройка
Генерация настраивается рядом конфигурационных параметров. Доступ к значениям параметров должны иметь параллельные процессы, поэтому установка на уровне сеанса (`SET`) не сработает. Удобнее всего устанавливать параметры на уровне базы данных:
```sql
ALTER DATABASE demo SET gen.имя-параметра = значение;
```
(Внутренние настройки не вынесены на уровень пользователя, но присутствуют в исходном коде в виде immutable-функций; неудачные значения могут привести к ошибкам генерации или к неожиданным результатам. Часть настроек и вовсе никуда не вынесена и присутствует в коде только в виде констант.)
### gen.connstr
Строка подключения к базе данных `demo`.
Значение по умолчанию `dbname=demo`, что соответствует подключению к локальному серверу. При необходимости укажите любые параметры, допускаемые libpq.
### gen.airlines\_name
Название авиакомпании. Появляется только в значении, возвращаемом функцией `bookings.version`.
Значение по умолчанию `PostgresPro`.
### gen.airlines\_code
Код авиакомпании. Используется как префикс номеров билетов `bookings.tickets.ticket_no`.
Значение по умолчанию `PG`.
### gen.traffic\_coeff
Коэффициент для пересчета относительного пассажиропотока `gen.airports_data.traffic` в количество бронирований из данного аэропорта в неделю.
### gen.domestic\_frac
Доля рейсов, которая должна приходиться на внутренние перелеты в пределах одной страны. Это целевое значение; оно играет роль при формировании графа перелетов, но граф в любом случае будет связным.
Значение по умолчанию `0.9`.
### gen.roundtrip\_frac
Доля бронирований, при которых билеты покупаются «туда и обратно». Это целевое значение; в реальности доля будет меньше из-за того, что обратные билеты не всегда доступны.
Значение по умолчанию `0.9`.
### gen.delay\_frac
Доля задержанных рейсов.
Значение по умолчанию `0.05`.
### gen.cancel\_frac
Доля отмененных рейсов.
Значение по умолчанию `0.005`.
### gen.exchange
Коэффициент пересчета минут полета в стоимость билета в предпочитаемой валюте. В зависимости от класса обслуживания применяется дополнительный повышающий коэффициент (функция `get_price`).
Значение по умолчанию `50`.
### gen.max\_pass\_per\_booking
Максимальное количество пассажиров в одном бронировании.
Значение по умолчанию `5`.
### gen.min\_transfer
Минимальное время в часах между пересадками. Уменьшение запаса на пересадку увеличивает количество рейсов, доступных для формирования маршрута, но и увеличивает шанс опоздать на стыковочный рейс при задержке предыдущего.
Значение по умолчанию `2`.
### gen.max\_transfer
Максимальное время в часах между пересадками. Увеличение этого порога увеличивает количество рейсов, доступных для формирования маршрута, но где взять таких терпеливых пассажиров?
Значение по умолчанию `48`.
### gen.max\_hops
Максимальное количество пересадок в одном билете. Чем больше допускается пересадок, тем больше вероятность, что пассажир сумеет воспользоваться нашей авиакомпанией, чтобы добраться до желаемого пункта.
Значение по умолчанию `4`.
### gen.log\_severity
Приоритет сообщений, попадающих в журнал `gen.log`. Наиболее важные сообщения имеют приоритет 0.
Значение по умолчанию `0`, что означает, что записываются только важные сообщения. Большее значение имеет смысл устанавливать только для отладки.
### bookings.lang
Язык, на котором в демобазе будут выводиться названия моделей самолетов, стран, городов и аэропортов. Это значение будет устанавливаться при развертывании демобазы, но впоследствии может быть изменено. Поддерживаются русский (`ru`) и английский (`en`) языки.
Значение по умолчанию `en`.
## Программный интерфейс
Ряд процедур и функций образуют API, с помощью которого пользователь взаимодействует с генератором.
### generate
Подпрограмма `generate` начинает генерацию демобазы. Ее параметры:
* `start_date` (`timestamptz`) — модельное время начала генерации;
* `end_date` (`timestamptz`) — модельное время окончания генерации;
* `jobs` (`integer`) — количество параллельных процессов (по умолчанию 1).
Пример вызова:
```sql
CALL generate(
start_date => date_trunc( 'day', now() ),
end_date => date_trunc( 'day', now() + interval '1 year' ),
jobs => 4
);
```
### continue
Подпрограмма `continue` продолжает генерацию демобазы после останова прошлого вызова `generate` или `continue`. Ее параметры:
* `end_date` (`timestamptz`) — модельное время окончания генерации;
* `jobs` (`integer`) — количество параллельных процессов (по умолчанию 1).
В качестве начальной даты будет взято время предыдущего останова генерации.
Пример вызова:
```sql
CALL continue(
end_date => date_trunc( 'day', now() + interval '2 years' ),
jobs => 4
);
```
### busy
Функция `busy` возвращает текущее состояние генерации: `true` — работает, `false` — завершилась.
Пример вызова:
```sql
SELECT busy();
```
### abort
Процедура `abort` досрочно прерывает генерацию. После прерывания можно начать генерацию заново с помощью `generate`, но вызов `continue` не гарантирует корректного продолжения.
Пример вызова:
```sql
CALL abort();
```
### get\_passenger\_name
Функция `get_passenger_name` выдает случайное имя пассажира из указанной страны.
Параметр:
* `country` (`text`) — код страны.
Справочники генератора содержат имена, записанные латиницей, для следующих стран: Россия (`RU`), Китай (`CN`), Индия (`IN`), США (`US`), Канада (`CA`), Япония (`JP`), Франция (`FR`), Германия (`DE`), Италия (`IT`), Великобритания (`GB`), Чили (`CL`), Швеция (`SE`), Непал (`NP`), Финляндия (`FI`), Новая Зеландия (`NZ`), Австрия (`AT`) и Чехия (`CZ`).
Функцию не требуется вызывать отдельно, но эта часть генератора может быть полезной для наполнения других баз данных, в которых требуется случайные имена с правдоподобным распределением.
Пример вызова, выводящего десять случайных непальских имен:
```sql
CALL calc_names_cume_dist(); -- необходимо выполнить один раз
SELECT get_passenger_name('NP') FROM generate_series(1,10);
```
## Журнальная таблица
Генератор записывает сообщения в журнальную таблицу `gen.log`, которую можно исследовать после генерации или использовать в процессе генерации для мониторинга:
```sql
SELECT * FROM gen.log ORDER BY at DESC LIMIT 30 \watch 60
```
В журнальную таблицу попадают все сообщения с приоритетом не ниже, чем значение параметра `gen.severity`. Для целей отладки набор сообщений можно расширить, указав ненулевой приоритет.
Ниже перечислены основные типы сообщений и их значение.
* Job _N_ (connname=_соединение_): _результат_
Запуск рабочего процесса номер _N_, используя соединение с именем _соединение_.
* _день_: one day in _время_
Выводится раз в день модельного времени и сообщает, сколько реального времени занял расчет этого модельного дня. За этим сообщением следует несколько дополнительных.
* New bookings: _B_ (forceoneway _F_), nopath _P_, noseat _S_
За день модельного времени было создано _B_ бронирований. Из них _F_ штук планировались как бронирования «туда и обратно» (с учетом значения параметра `gen.roundtrip_frac`), но из-за отсутствия нужных билетов бронь была сделана только в одну сторону. При этом _P_ попыток создать бронирование полностью не удались из-за отсутствия подходящих рейсов, а _S_ — из-за того, что на одном из выбранных рейсов не оказалось свободных мест.
* Book-ref retries = _R_ (_F_ retries/booking)
За день модельного времени потребовалось _R_ раз повторно выбирать случайный номер бронирования из-за того, что выбранный номер уже занят. Значение _F_ показывает количество повторных попыток в пересчете на одно бронирование.
Чем дольше работает генератор, тем больше номеров будет занято и тем больше времени будет уходить на поиск свободного значения. Емкость номера бронирования составляет около 2 млрд значений; за один модельный год при настройках по умолчанию генерируется около 5 млн бронирований, поэтому на горизонте нескольких модельных лет эта причина не вызывает замедления.
* New boarding passes: _BP_
За день модельного времени было создано _BP_ посадочных талонов.
* Building routes, range _период_
Выполняется перестроение маршрутов с периодом действия _период_. После этого сообщения идет перечисление новых маршрутов.
* _А1_ -> _A2_: _модель_ x _N_, traffic = _P_ pass/week
Построен маршрут из аэропорта _А1_ в аэропорт _А2_. Он выполняется самолетом _модель_ _N_ раз в неделю, предполагаемый пассажиропоток составляет _P_ пассажиров в неделю.
* Cannot choose an aircraft for _А1_ -> _А2_ (out of range?)
Маршрут из аэропорта _А1_ в аэропорт _А2_ не удалось построить. Обычная причина состоит в том, что расстояние между аэропортами превышает дальность всех самолетов компании. Само по себе такое сообщение не является ошибкой, но может указывать на неудачно выбранный флот, если повторяется многократно.
* Vacuum
Выполнение очистки и анализа.
* End date reached, exiting
Рабочий процесс завершил свою работу.
## Скрипт проверки
После того как демобаза сгенерирована, имеет смысл выполнить стандартные проверки, чтобы убедиться в качестве подготовленных данных.
```sql
\i check.sql
```
Скрипт выполняет несколько запросов, описанных ниже.
### Generation errors in log
Число, отличное от нуля, означает, что в процессе генерации произошли ошибки. Ошибки будут записаны в журнальную таблицу `gen.log` и будут начинаться со слова Error.
### Generation stats
Запросы этой секции показывают количество сгенерированных бронирований, билетов, перелетов, рейсов и маршрутов.
### Generation speed
Скорость генерации в событиях в секунду.
Рассчитанная скорость включает и время простоя между вызовами `generate` и `continue`.
### Airplanes utilization
Первый запрос показывает среднюю заполненность салонов самолетов.
Второй запрос показывает количество рейсов, выполненных с пустым салоном. Небольшое количество пустых рейсов может выглядеть правдоподобно.
Третий запрос показывает список моделей самолетов и количество маршрутов, которые эта модель обслуживала. Учтите, что в разные периоды времени один и тот же маршрут может обслуживаться разными самолетами, поэтому выведенные числа не стоит складывать. Вердикт `NOT USED` означает, что модель не выполнила ни одного рейса — такую модель, скорее всего, стоит заменить. Вердикт `WRONGLY USED` свидетельствует об ошибке в алгоритме и не должен появляться.
### Roundtrips to overall tickets
Отношение количества билетов «туда и обратно» к общему количеству билетов. Второе число показывает целевое значение (заданное параметром `gen.roundtrip_frac`). Реальное значение всегда будет меньше целевого из-за того, что не для каждого прямого билета удается купить обратный.
### Passengers per booking
Вердикт `ERROR: max_pass_per_bookings not satisfied` говорит о том, что в бронированиях бывает более `gen.max_pass_per_booking` пассажиров и свидетельствует об ошибке в алгоритме.
Второй запрос показывает распределение бронирований по количеству пассажиров в них. Например, строка с `npass`, равным 2, и `cnt`, равным 100, говорит о том, что сто бронирований включают двух пассажиров.
### Frequent flyers
Запрос показывает распределение пассажиров по количеству выполненных ими бронирований. Например, строка с `nbook`, равным 3, и `cnt_pass`, равным 1000, говорит о том, что тысяча пассажиров сделали по три бронирования.
### Segments per ticket
Запрос показывает распределение билетов по количеству перелетов в них. Например, строка с `segments`, равным 3, и `cnt`, равным 1000, говорит о том, что тысяча билетов включает три перелета.
### Flight statuses
Количество рейсов в разных статусах.
Три статуса, в которых рейсов немного, независимо от размера базы, — это `On Time` (вылет по расписанию), `Departed` (вылетел и находится в воздухе) и `Boarding` (посадка пассажиров). Если таких рейсов нет, возможно стоит продолжить генерацию на несколько модельных часов, чтобы обеспечить разнообразие данных.
### Flight durations
Вердикт `ERROR: route and flight discrepancy` в первом запросе говорит о рассогласовании данных о продолжительности полетов между таблицами `routes` и `flights` и свидетельствует об ошибке в алгоритме.
Второй запрос показывает минимальную, среднюю и максимальную продолжительность полетов — как запланированную, так и реальную.
### Flight delays
Минимальная, средняя и максимальная задержка вылетов и прибытий.
### Overbookings
Вердикт `ERROR: overbooking` говорит о том, что посадочных талонов выдано больше, чем мест в самолете, и свидетельствует об ошибке в алгоритме.
### Cancelled flights fraction
Доля отмененных рейсов и целевое значение (параметр `gen.cancel_frac`). Должны совпадать с хорошей точностью.
### Adjacency of segments
Вердикт `ERROR: non-adjacent segments` говорит о том, что аэропорт прибытия одного перелета не совпадает с аэропортом вылета следующего перелета из того же билета. Это свидетельствует об ошибке в алгоритме.
### Routes validity ranges
Вердикт `ERROR: validity ranges have holes` говорит о наличии пропусков между периодами действия маршрутов. Это свидетельствует об ошибке в алгоритме.
### Flights consistency with routes
Вердикт `ERROR: absent flights` говорит о том, что в таблице `flights` отсутствуют рейсы, которые должны быть согласно таблице `routes`.
Вердикт `ERROR: excess flights` говорит о том, что в таблице `flights` есть рейсы, не соответствующие расписанию в таблице `routes`.
Возможны оба сообщения одновременно (`absent and excess flights`). Любая ошибка свидетельствует об ошибке в алгоритме.
### Timings
Вердикт первого запроса `ERROR: flights timing discrepancy` говорит о рассогласовании времен в информации о рейсе.
Вердикт второго запроса `ERROR: boarding after takeoff` говорит о том, что посадка пассажиров продолжалась после взлета.
Вердикт третьего запроса `ERROR: booking after boarding` говорит о том, что бронирование на рейс продолжалось после начала посадки пассажиров.
Любая ошибка в этой секции свидетельствует об ошибке в алгоритме.
### Miss the flight
Пассажир может опоздать на рейс из-за задержки предыдущего стыковочного рейса. Генератор отслеживает такие ситуации, чтобы не допустить опоздавшего пассажира к следующим рейсам в билете.
Первый запрос выводит фактическое количество пассажиров, опоздавших на рейс, а также количество некорректно зарегистрированных генератором опозданий и количество незарегистрированных генератором опозданий. Вердикт `ERROR: incorrect missed flights` свидетельствует об ошибке в алгоритме.
Вердикт второго запроса `ERROR: boarding after miss` говорит о том, что пассажир садился в самолет после опоздания и свидетельствует об ошибке в алгоритме.
### Interlaced flights
Вердикт `ERROR: interlaced flights` говорит о том, что один и тот же пассажир совершал более одного путешествия одновременно. Это свидетельствует об ошибке в алгоритме.
## Экспорт демобазы
Сгенерированную демобазу можно выгрузить в виде SQL-скрипта, аналогичного результату работы команды `pg_dump`. Находясь в каталоге репозитория выполните скрипт, перенаправляя его результат в файл, например:
```sh
./export.sh | gzip > `date +%Y%m%d`.sql.gz
```
Скрипту можно передать любые параметры подключения, принимаемые утилитой `pg_dump`.
В SQL-скрипт будут входить объекты схемы `booking`, включая определения функций `bookings.now` (значение конечной модельной даты, указанной при генерации) и `bookings.version` (версия сгенерированной демобазы). Также будут включены команды для установки параметров `bookings.lang` и `search_path` на уровне базы данных.
## Внутреннее устройство
Генератор имитирует работу авиакомпании, создавая и обрабатывая поток случайных событий: бронирование авиабилетов пассажирами, регистрация и посадка, отправления и прибытия самолетов и т. п. Обработчик события может добавлять в очередь новые события, реализуя, в частности, конечный автомат состояний рейса.
Все таблицы генератора располагаются в схеме `gen`. Они используются при генерации и хранят ее текущее состояние. Далее, если имя схемы не указано, предполагается схема `gen`.
Таблицы демобазы находятся в схеме `bookings` и описаны в документации по демобазе.
### events
Очередь событий. Очередь обрабатывается процедурой `process_queue`, а отдельное событие - процедурой `process_event`.
Ниже перечислены типы событий.
#### INIT
Это «затравочное» событие, обработка которого (процедура `do_init`) приводит к инициализации состояния всех таблиц и вставке начального набора событий: `BUILD ROUTES`, `BOOKING`, `VACUUM`, `MONITORING`.
#### BUILD ROUTES
Событие перестройки маршрутов. При его обработке (процедура `build_routes`) создается случайный, но связный граф маршрутов. В маршрутной сети участвуют аэропорты, имеющие непустое значение `traffic` в таблице `airports_data`. Перелеты между несколькими аэропортами одного города никогда не создаются.
Добавляемые ребра графа записываются в таблицу `directions`. Вначале от каждого аэропорта, начиная с наименее загруженного, добавляются ребра к двум другим случайным аэропортам. Вероятность добавления ребра пропорциональна трафику аэропорта назначения и обратно пропорциональна расстоянию между аэропортами. При этом с вероятностью `gen.domestic_frac` выбирается аэропорт той же страны (при наличии в маршрутной сети хотя бы двух городов этой страны).
Связность графа отслеживается с помощью таблицы `directions_connect`. Если построенный граф не является связным, в него по тем же правилам добавляются другие случайные ребра, пока связность не будет достигнута.
Новые маршруты переносятся из `directions` в таблицу `bookings.routes` с соответствующим интервалом действия `validity`. Самолет, обслуживающий маршрут, и количество рейсов в неделю подбираются исходя из прогнозируемого трафика между аэропортами (таблица `week_traffic`).
Первое событие `BUILD ROUTES` добавляется при обработке `INIT` и порождает расписание на месяц, начиная с даты начала генерации. Далее маршруты перестраиваются каждый месяц, для чего в конце обработки события в очередь вставляется следующее событие `BUILD ROUTES`. Также для каждого построенного маршрута добавляется событие `FLIGHT`, отмечающее начало бронирований на этот маршрут за месяц до его вступления в силу.
#### BOOKING
Событие бронирования из заданного аэропорта. При его обработке (процедура `make_booking`) совершается попытка бронирования путешествия в случайный аэропорт. Вероятность выбора целевого аэропорта пропорциональна его трафику и обратно пропорциональна расстоянию между аэропортами, а также учитывает параметр `gen.domestic_frac` (как и при создании расписания).
Маршрут до выбранного аэропорта определяется (функция `get_path`) с учетом следующих факторов:
* предпочтению отдается маршруту с наименьшим количеством пересадок, причем их количество не должно превышать `gen.max_hops`;
* рассматриваются рейсы, на которые в продаже есть билеты и осталось достаточно времени до отправления (еще не открыта регистрация на рейс);
* между стыковочными рейсами должно быть как минимум `gen.min_transfer` часов (иначе велик риск пропустить рейс из-за возможной задержки) и не должно быть больше `gen.max_transfer` часов (иначе слишком долго ждать);
* среди возможных маршрутов выбирается тот, чей первый перелет начинается раньше.
Количество пассажиров в бронировании определяется случайно с учетом параметра `gen.max_pass_per_booking`.
Если не удается найти подходящий маршрут или если на выбранный маршрут не удается забронировать билеты на всех пассажиров, попытка бронирования считается неудачной и отменяется.
С вероятностью `gen.roundtrip_frac` выполняется также попытка забронировать полет в обратном направлении через случайный интервал времени (до месяца, в среднем — около недели). Маршрут обратного следования может не совпадать с исходным маршрутом. Неуспешная попытка не отменяет бронирования в прямом направлении.
В результате бронирования создается строка в таблице `bookings.bookings`, строки для каждого билета в `bookins.tickets` (по одной на каждого пассажира и направления следования), строки в таблице перелетов `bookings.segments`.
Следующее событие `BOOKING` для данного аэропорта-источника создается так, чтобы события образовывали пуассоновский поток с частотой, соответствующей пассажиропотоку аэропорта. В среднем в неделю будет генерироваться `airports_data.traffic` умноженное на значение параметра `gen.traffic_coeff` событий бронирования.
#### FLIGHT
Событие добавления рейса в расписание за месяц до отправления.
Обработчик (процедура `open_booking`) вставляет строку в таблицу `bookings.flights` для рейса, открывая возможность бронирования (статус `Scheduled`). С вероятностью `gen.cancel_frac` рейс отменяется (статус `Cancelled`). Отмена всегда происходит заранее: не бывает ситуаций, когда рейс отменяется уже после того, как на него куплены какие-либо билеты.
За день до отправления неотмененного рейса на него должна открыться регистрация, за это отвечает добавляемое событие `REGISTRATION`.
#### REGISTRATION
Событие открытия регистрации на рейс за день до отправления.
Обработчик (процедура `registration`) меняет статус рейса на `On Time` и определяет фактическое время начала регистрации (допустима небольшая задержка на несколько минут). С вероятностью `gen.delay_frac` рейс задерживается (статус `Delayed`) на время от часа до 12 часов.
Создает события регистрации `CHECK-IN` для каждого билета, проданного на данный рейс (регистрация заканчивается за 40 минут до отправления). Также создает событие начала посадки `BOARDING` на этот рейс.
#### CHECK-IN
Событие регистрации на рейс для конкретного билета.
Пассажир проходит регистрацию только один раз, на первый рейс в билете. Обработчик (процедура `check_in`) создает строки в таблице `bookings.boarding_passes` для посадочных талонов на каждый рейс в билете (мы считаем, что все рейсы в билете являются стыковочными).
#### BOARDING
Событие начала посадки на рейс за полчаса до вылета.
Обработчик (процедура `boarding`) меняет статус рейса на `Boarding` и создает события посадки `GET IN` для каждого билета, проданного на рейс. Посадка завершается в течение 20 минут.
Если посадка заканчивается до конца периода генерации, события `GET IN` не создаются, а вместо этого номер и время посадки проставляются в посадочных талонах (таблица `bookings.boarding_passes`) немедленно. Эта оптимизация позволяет существенно сократить количество событий и ускорить генерацию.
Также создает событие взлета `TAKEOFF`, определяя фактическое время вылета (допускается небольшая задержка на несколько минут).
#### GET IN
Событие посадки в самолет для конкретного билета.
Обработчик (процедура `get_in`) проставляет номер и время посадки в посадочном талоне (таблица `bookings.boarding_passes`).
#### TAKEOFF
Событие взлета.
Обработчик (процедура `takeoff`) меняет статус рейса на `Departed` и создает событие приземления `LANDING`, определяя фактическую продолжительность полета (возможно отклонение от плановой продолжительности на несколько процентов как в одну, так и в другую сторону).
#### LANDING
Событие приземления.
Обработчик (процедура `landing`) меняет статус рейса на `Arrived`. На этом рейс считается отработанным; новых событий не генерируется.
#### VACUUM
Событие очистки.
Обработка очереди событий происходит хотя и в разных транзакциях, но в одном операторе CALL. Из-за этого статистика изменения таблиц не передается сборщику статистики: автоочистка не имеет представления, что содержимое таблиц меняется, и не срабатывает.
Поэтому раз в неделю модельного времени очистка и анализ запускаются принудительно с помощью данного события (процедура `vacuum`). Запуск происходит в отдельном процессе с помощью расширения `dblink`.
#### MONITORING
Событие мониторинга.
Событие срабатывает раз в день модельного времени и заносит в журнальную таблицу `log` сведения о прошедшем дне (процедура `monitoring`).
### event\_history
В эту таблицу переносятся отработанные строки из `events` для возможности отладки.
### airplanes\_data
Данные о моделях самолетов. В таблицу `bookings.airplanes_data` переносятся все строки и все столбцы, за исключением столбца `in_use`, который определяет, надо ли назначать данную модель на рейсы.
### seats
Данные о конфигурации салонов самолетов. Все данные переносятся без изменений в таблицу `bookings.seats`.
### seats\_remain
Используется генератором для отслеживания остатка свободных мест на рейсах.
### airports\_data
Данные об аэропортах. В таблицу `bookings.airports_data` переносятся все строки и все столбцы, за исключением столбцов:
* `country_code` — двухсимвольный код страны в соответствии с ISO 3166-1 alpha-2 (попадает в номер билета `bookings.tickets.ticket_no`);
* `traffic` — относительный пассажиропоток авиакомпании в данном аэропорту (абсолютное количество попыток бронирований в неделю из данного аэропорта вычисляется умножением этой величины на значение параметра `gen.traffic_coeff`).
### directions
Используется генератором при составлении графа перелетов. В эту таблицу добавляются пары аэропортов, которые будет связаны прямыми рейсами.
### directions\_connect
Используется для определения связности графа перелетов.
### airports\_to\_prob
Хранит предварительно вычисленные накопленные вероятности полета из одного аэропорта в другой (не обязательно связанных прямым рейсом) и используется при составлении графа перелетов и для выбора аэропорта назначения при бронировании.
### week\_traffic
Прогноз пассажиропотока между двумя аэропортами, соединенными прямым рейсом.
### firstnames
Данные о популярности имен (given names) в разбивке по странам. Таблица позволяет разделить имена одной страны на группы, например, на мужские и женские для тех языков, в которых фамилия зависит от пола. При инициализации в столбец `cume_dist` заносится накопленная вероятность выбора данного имени.
### lastnames
Данные о популярности фамилий (family names) в разбивке по странам. Таблица позволяет разделить фамилии одной страны на группы. При инициализации в столбец `cume_dist` заносится накопленная вероятность выбора данной фамилии.
### passengers
Перечень уникальных пассажиров. Таблица используется генератором, чтобы гарантировать, что одному номеру документа `passenger_id` соответствует одно имя, а также что один пассажир не участвует в нескольких одновременных поездках. Информация из этой таблицы не переносится в демобазу, в которой нет отдельной сущности «пассажир».
### missed\_flights
Используется генератором для отслеживания билетов тех пассажиров, которые опоздали на очередной перелет из-за задержки предыдущего рейса.
### stat\_bookings
Статистика по бронированиям. Используется для мониторинга генерации.
### stat\_jobs
Статистика по параллельным процессам. В норме счетчики обработанных событий для каждого процесса должны быть примерно равны. Если счетчик какого-либо процесса не увеличивается во время генерации, следует искать сообщение об ошибке в журнале `log`.
### stat\_bookrefs
Статистика по количеству повторов, которые потребовались для генерации уникального номера бронирования `book\_ref`.
## Вопросы и ответы
### Как добавить перевод названий на новый язык?
В демобазе язык перевода выбирается параметром `bookings.lang`. Переводятся названия моделей самолетов (представление `airplanes`), а также названия аэропортов, городов и стран (представление `airports`). В настоящий момент имеются переводы на два языка: русский (значение параметра `ru`) и английский (`en`).
Чтобы добавить новый язык, необходимо добавить переводы в JSON-столбцы `airplanes_data.model`, `airports_data.airport_name`, `airports_data.city` и `airports_data.country`. Это непростая задача, поскольку в таблице аэропортов содержится около 5500 строк. Как минимум нужно перевести хотя бы строки с непустым значением пассажиропотока (`traffic`), участвующие в маршрутной сети.
### Как изменить флот авиакомпании?
Желаемые модели самолетов и конфигурацию их салонов необходимо поместить в таблицы `airplanes_data` и `seats`. Все они будут перенесены в аналогичные `bookings`-таблицы. В рейсах будут участвовать только самолеты с признаком `in_use`.
Необходимо учитывать, что дальность полета выбранного набора самолетов должна покрывать расстояния между городами, иначе некоторые перелеты могут оказаться нереализуемыми.
Вместимость самолетов должна соответствовать планируемому пассажиропотоку: рейс выполняется минимум раз в неделю и максимум семь раз в неделю. Без должного разнообразия вместимостей все рейсы могут оказаться переполненными или недозаполненными.
### Каков критерий выбора стран для демобазы?
Были выбраны страны ведущих разработчиков PostgreSQL (major contributors), а также несколько других стран, имеющих определенное отношение к PostgreSQL.
Вы можете добавить и другие страны (см. ниже).
### Каков критерий выбора количества городов?
Количество городов в демобазе пропорционально квадрату суммы логарифмов площадей стран и численности их населения:
$$ 0,5 ( log( площадь, км^2 ) + log( население, млн ) - 5 )^2 $$
Вы можете сделать другой выбор (см. ниже).
### Как изменить маршрутную сеть в пределах существующих стран и городов?
Установите непустое значение пассажиропотока `traffic` для желаемых аэропортов в таблице `airports_data`. Эти аэропорты и будут использоваться в маршрутах.
### Как добавить новую страну?
При добавлении новой страны недостаточно просто выбрать аэропорты в таблице `airports_data`. Потребуется также определить справочники имен для новой страны.
В настоящее время справочники генератора содержат имена, записанные латиницей, для следующих стран: Россия (`RU`), Китай (`CN`), Индия (`IN`), США (`US`), Канада (`CA`), Япония (`JP`), Франция (`FR`), Германия (`DE`), Италия (`IT`), Великобритания (`GB`), Чили (`CL`), Швеция (`SE`), Непал (`NP`), Финляндия (`FI`), Новая Зеландия (`NZ`), Австрия (`AT`) и Чехия (`CZ`).
Полное имя пассажира формируется из имени (given name) и фамилии (family name), поэтому в качестве исходных данных необходимо иметь список имен и фамилий и их относительную частоту. В качестве такого списка можно, например, использовать данные переписи населения. В простом случае, если по правилам языка страны фамилия не склоняется в зависимости от рода имени (как, например, в германских и романских языках, таких как английский или французский), таких исходных данных будет достаточно.
Имена необходимо добавить в таблицу `firstname`:
* `country` — двухсимвольный код страны в соответствии с ISO 3166-1 alpha-2;
* `name` — имя;
* `qty` — целое число, отражающее популярность имени (например, зарегистрированное количество людей, носящих данное имя).
В столбец `grp` поместите значение `-`.
Фамилии добавляются в таблицу `lastnames` с той же структурой.
Чтобы проверить результат, можно выполнить такой запрос, где `XX` — код страны:
```sql
CALL calc_names_cume_dist(); -- рассчитывает накопленную вероятность
SELECT get_passenger_name('XX') FROM generate_series(1,10);
```
Для языков, в которых фамилии зависят от рода имени (например, славянские и балтийские языки, такие как русский), потребуется разделить все имена и все фамилии на мужские и женские с помощью столбца `grp`. В этот столбец можно поместить любое текстовое значение, но оно должно совпадать в обеих таблицах (например, можно использовать `m` и `f`).
Имена, относящиеся как к мужским, так и к женским, должны быть помещены в обе группы (возможно, с разной популярностью).
Тот же принцип можно использовать для разделения и на другие группы, например, чтобы смоделировать этнические группы с обособленными системами имен.
При генерации имени сначала из всех групп выбирается случайная фамилия, а затем выбирается случайное имя из той группы, к которой принадлежит фамилия. При этом полные имена, в которых имя (given name) и фамилия (family name) одинаковы, не выбираются.
### Как добавить новый аэропорт?
Добавьте строку в таблицу `airports_data`. Для этого необходимо знать:
* Код ИАТА;
* Английское название аэропорта;
* Английское название города;
* Часовой пояс;
* Координаты аэропорта (указываются в формате «долгота, широта»).
Полной актуальной базы аэропортов нет в свободном доступе. Существует несколько открытых проектов ([OpenFlights](https://openflights.org/data.php), [OurAirports](https://ourairports.com/data/), [DataHub](https://datahub.io/core/airport-codes)), но полнота и актуальность их информации под вопросом. Конкретный аэропорт [можно проверить](https://www.iata.org/en/publications/directories/code-search/) на официальном сайте ИАТА.
Название аэропорта и города необходимо перевести на русский язык (и другие языки, если они должны поддерживаться настройкой многоязычности `bookings.lang`).
Чтобы новая запись была согласована с существующими, стоит придерживаться некоторых правил.
В качестве города (`city`) выбирается город, который обслуживается этим аэропортом. Часто это ближайший к аэропорту крупный населенный пункт, а не тот пункт, в котором расположен аэропорт. Например, для IAR городом является Ярославль, а не село Туношна.
Официальное название аэропорта часто состоит из названия населенного пункта, в котором расположен аэропорт, посвящения какому-либо видному деятелю и других частей. Для краткости имя сокращается по следующим правилам:
* Название города не повторяется в названии аэропорта (за исключением случаев, когда официальное название состоит только из названия города, например, AAC «El Arish»);
* В качестве названия аэропорта используется либо название населенного пункта, где расположен аэропорт, либо имя человека, причем предпочтение отдается населенному пункту (например, SVO «Шереметьево имени А. С. Пушкина» сокращается до «Шереметьево»).
* Если используется посвящение человеку, титулы и воинские звания отбрасываются (например, YAI «General Bernardo O'Higgins» сокращается до «Bernardo O'Higgins»);
* Отбрасываются слова «международный», «региональный» и т. п. (например, ROA «Roanoke–Blacksburg Regional» сокращается до «Blacksburg», учитывая, что Roanoke — название города).
* В русском языке промежуточные инициалы убираются (например, JFK «John F. Kennedy» переводится как «Джон Кеннеди»).