# demodb **Repository Path**: mirrors_postgrespro/demodb ## Basic Information - **Project Name**: demodb - **Description**: Demonstration Database - **Primary Language**: Unknown - **License**: MIT - **Default Branch**: main - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2025-09-23 - **Last Updated**: 2026-03-29 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # Генератор демобазы ## Назначение Программа-генератор позволяет создать базу данных с той же структурой, что и [демобаза](https://postgrespro.ru/education/demodb), но иным наполнением. Благодаря ряду настроек можно изменить характеристики данных, например поменять маршрутную сеть и флот авиакомпании. Программу также можно использовать как генератор нагрузки, например для демонстрации систем мониторинга или тренировки оптимизации запросов. ## Установка 1. Создайте локальную копию репозитория `demodb`. 2. В `psql` подключитесь к любой базе данных, кроме `demo`. > **Внимание** > > Если база `demo` уже существует, то в ходе установки она будет удалена и все данные в ней будут потеряны. 3. Убедитесь, что находитесь в каталоге репозитория `demodb`: ```sql \! pwd ``` При необходимости смените текущий каталог командой `\cd`. 4. Выполните установку: ```sql \i install.sql ``` В ходе установки будет создана база данных `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 ); ``` ### Проверка состояния генерации Чтобы быстро проверить состояние генерации, выполните команду: ```sql SELECT busy(); ``` Возможные значения: * `t` (true) — генерация в процессе; * `f` (false) — генерация завершена. Более полное представление о процессе можно получить из журнала сообщений генератора `gen.log`: ```sql SELECT * FROM gen.log ORDER BY at DESC LIMIT 30 \watch 60 ``` ### Досрочное завершение генерации Чтобы прервать генерацию, выполните команду: ```sql CALL abort(); ``` > **Примечание** > > Разрыв соединения не завершает работу генератора. ### Завершение генерации 1. Чтобы узнать о завершении генерации, проверьте статус процессов: ```sql SELECT busy(); ``` Если генерация завершена успешно, в выводе будет `f` (false). 2. Проверьте созданную демобазу данных: ```sql \i check.sql ``` ### Выгрузка демобазы Полученную демобазу можно выгрузить в виде SQL-скрипта. Чтобы создать SQL-скрипт, в каталоге репозитория от имени пользователя операционной системы выполните команду: ```sh ./export.sh > `date +%Y%m%d`.sql ``` ### Продолжение генерации Генерацию можно продолжить с того момента, на котором она остановилась. Например, чтобы сгенерировать данные ещё за три месяца, выполните команду: ```sql CALL continue( now() + interval '1 year 3 month', 4 ); ``` ## Настройка Чтобы настроить генерацию, измените необходимые конфигурационные параметры на уровне базы данных: ```sql ALTER DATABASE demo SET имя-параметра = значение; ``` > **Примечание** > > Установка на уровне сеанса (`SET`) не сработает, так как доступ к значениям параметров должны иметь параллельные процессы. Некоторые внутренние настройки не вынесены на уровень пользователя: они присутствуют в исходном коде в виде постоянных функций или только в виде констант (неудачные значения могут привести к ошибкам генерации или к неожиданным результатам). Доступные для изменения конфигурационные параметры описаны ниже. ### `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`. ## Программный интерфейс Ряд процедур и функций, с помощью которых пользователь взаимодействует с генератором. ### `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` Продолжить генерацию демобазы после останова прошлого вызова `generate` или `continue`. Параметры: * `end_date` (`timestamptz`) — модельное время окончания генерации; * `jobs` (`integer`) — количество параллельных процессов (значение по умолчанию: `1`). Модельное время начала генерации задаётся автоматически. Это время предыдущего останова генерации. Пример вызова: ```sql CALL continue( end_date => date_trunc( 'day', now() + interval '2 years' ), jobs => 4 ); ``` ### `busy` Показать текущее состояние генерации. Возможные значения: * `t` (true) — генерация в процессе; * `f` (false) — генерация завершена. Пример вызова: ```sql SELECT busy(); ``` ### `abort` Досрочно прервать генерацию. Пример вызова: ```sql CALL abort(); ``` После прерывания генерации её можно начать заново с помощью `generate`. Использовать `continue` не рекомендуется, так как корректное продолжение не гарантируется. ### `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); ``` > **Примечание** > > * Чтобы добавить страну, добавьте имена в справочники генератора — `firstnames.dat` и `lastnames.dat` (за подробностями обратитесь к разделу [«Вопросы и ответы»](#как-добавить-новую-страну)). > * Функция не требуется для работы с демобазой, но может быть полезной для наполнения других баз данных, в которых требуются случайные имена с правдоподобным распределением. ## Журнальная таблица Генератор записывает сообщения в журнальную таблицу `gen.log`, которую можно посмотреть после генерации или использовать для мониторинга в процессе генерации: ```sql SELECT * FROM gen.log ORDER BY at DESC LIMIT 30 \watch 60 ``` В журнальную таблицу попадают все сообщения с приоритетом не ниже значения параметра `gen.severity`. По умолчанию записываются только важные сообщения с приоритетом `0`. Для отладки набор сообщений можно расширить, указав ненулевой приоритет. Ниже перечислены основные сообщения и их значения: * 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 Количество сгенерированных бронирований (`num_bookings`), билетов (`num_tickets`), перелётов (`num_segments`), рейсов (`num_flights`) и маршрутов (`num_routes`). ### Generation speed Скорость генерации в событиях в секунду. Рассчитанная скорость включает и время простоя между вызовами `generate` и `continue`. ### Airplanes utilization Первый запрос (`avg_fill_ratio`) показывает среднюю заполненность салонов самолетов. Второй запрос показывает количество рейсов, выполненных с пустым салоном. Небольшое количество пустых рейсов выглядит правдоподобно. Третий запрос показывает список моделей самолетов и количество маршрутов, которые эта модель обслуживала. > **Примечание** > > * В разные периоды времени один и тот же маршрут может обслуживаться разными самолетами, поэтому выведенные числа не стоит складывать. > * Вердикт `NOT USED` означает, что модель не выполнила ни одного рейса. Такую модель следует заменить. > * Вердикт `WRONGLY USED` свидетельствует об ошибке в алгоритме. ### Roundtrips to overall tickets Отношение количества билетов «туда и обратно» к общему количеству билетов (`roundtrip_frac`) и целевое значение (`target_frac`), заданное параметром `gen.roundtrip_frac`. Реальное значение всегда будет меньше целевого из-за того, что не для каждого прямого билета удаётся купить обратный. ### Passengers per booking Первый запрос показывает, что в бронировании было не более `gen.max_pass_per_booking` пассажиров. Вердикт `ERROR: max_pass_per_bookings not satisfied` означает, что это правило было нарушено, и свидетельствует об ошибке в алгоритме. Второй запрос показывает распределение бронирований по количеству пассажиров в них. Например, строка с `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` в первом запросе говорит о рассинхронизации данных о продолжительности полётов между таблицами `bookings.routes` и `bookings.flights` и свидетельствует об ошибке в алгоритме. Второй запрос показывает минимальную (`min_..._duration`), среднюю (`avg_..._duration`) и максимальную (`max_..._duration`) продолжительность полётов — как запланированную (`..._sch_...`), так и реальную (`..._act_...`). ### Flight delays Минимальная (`min_..._delay`), средняя (`avg_..._delay`) и максимальная (`max_..._delay`) задержка оправлений (`..._dep_...`) и прибытий (`..._arr_...`). ### Overbookings Количество перебронирований. Вердикт `ERROR: overbooking` означает, что посадочных талонов выдано больше, чем мест в самолёте, и свидетельствует об ошибке в алгоритме. ### Cancelled flights fraction Доля отменённых рейсов (`actual_cancelled_frac`) и целевое значение (`target_cancelled_frac`), заданное параметром `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` означает, что в таблице `bookings.flights` отсутствуют рейсы, которые должны быть согласно таблице `bookings.routes`. Вердикт `ERROR: excess flights` означает, что в таблице `bookings.flights` есть рейсы, не соответствующие расписанию в таблице `bookings.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` — со служебными таблицами, которые используются при генерации и хранят её текущее состояние; * `bookings` — с таблицами создаваемой демобазы, которые описаны в [документации по демобазе](https://postgrespro.ru/education/demodb). Таблицы из схемы `gen` описаны ниже. ### `gen.events` Таблица, содержащая неотработанные события. Очередь событий обрабатывается процедурой `process_queue`, а отдельное событие — процедурой `process_event`. Типы событий перечислены ниже. #### INIT «Затравочное» событие. Обрабатывается процедурой `do_init`, которая инициализирует состояния всех таблиц и добавляет начальный набор событий: `BUILD ROUTES`, `BOOKING`, `VACUUM` и `MONITORING`. #### BUILD ROUTES Событие перестройки маршрутов. Обрабатывается процедурой `build_routes`, которая создаёт случайный, но связный граф маршрутов. Первое событие `BUILD ROUTES` добавляется при обработке `INIT`. Создаётся расписание на месяц, начиная с даты начала генерации. В конце обработки события вставляется следующее событие `BUILD ROUTES`, чтобы маршруты перестраивались каждый месяц. Для каждого построенного маршрута также добавляется событие `FLIGHT`, отмечающее начало бронирований на этот маршрут за месяц до его вступления в силу. ##### Как формируется маршрутная сеть? Для формирования связанного графа маршрутов: 1. Берутся аэропорты с непустым значением в столбце `traffic` таблицы `gen.airports_data`. 2. От каждого аэропорта добавляются рёбра (перелёты) к двум другим случайным аэропортам по следующим правилам: * Добавление рёбер начинается с наименее загруженного аэропорта. * Перелёты между несколькими аэропортами одного города никогда не создаются. * Вероятность добавления ребра пропорциональна трафику аэропорта назначения и обратно пропорциональна расстоянию между аэропортами. * При наличии в одной стране хотя бы двух городов вероятность выбора аэропорта той же страны определяется параметром `gen.domestic_frac`. 3. Рёбра графа записываются в таблицу `gen.directions`. 4. Рёбра добавляются, пока граф не становится связным. Связность графа отслеживается с помощью таблицы `gen.directions_connect`. 5. Новые маршруты переносятся из таблицы `gen.directions` в таблицу `bookings.routes` с соответствующим интервалом действия (`validity`). 6. Самолёт, обслуживающий маршрут, и количество рейсов в неделю подбираются исходя из прогнозируемого трафика между аэропортами (таблица `gen.week_traffic`). #### BOOKING Событие бронирования из заданного аэропорта. Обрабатывается процедурой `make_booking`, которая пытается забронировать путешествие в случайный аэропорт. Следующее событие `BOOKING` для данного аэропорта отправления создаётся так, чтобы события образовывали пуассоновский поток с частотой, соответствующей пассажиропотоку аэропорта. Среднее количество событий бронирования в неделю — значение `airports_data.traffic`, умноженное на `gen.traffic_coeff`. ##### Как выполняется бронирование? В ходе выполнения бронирования: 1. Выбирается аэропорт назначения по следующим правилам: * Вероятность выбора аэропорта пропорциональна трафику аэропорта и обратно пропорциональна расстоянию между аэропортами отправления и назначения. * При наличии в одной стране хотя бы двух городов вероятность выбора аэропорта той же страны определяется параметром `gen.domestic_frac`. 2. Определяется маршрут до выбранного аэропорта (функция `get_path`) по следующим правилам: * В выбранном маршруте меньше всего пересадок, причём их количество не превышает `gen.max_hops`. * В продаже есть билеты. * До отправления достаточно времени: рассматриваются рейсы, на которые ещё не открыта регистрация. * Между стыковочными рейсами как минимум `gen.min_transfer` часов (иначе велик риск пропустить рейс из-за возможной задержки) и не более `gen.max_transfer` часов (иначе ожидание слишком длительное). * Первый перелёт в выбранном маршруте начинается как можно раньше. 3. Случайно с учетом параметра `gen.max_pass_per_booking` определяется количество пассажиров в бронировании. 4. В результате бронирования создаются: * строка в таблице `bookings.bookings`; * строки для каждого билета в таблице `bookings.tickets` (по одной на каждого пассажира и направления следования); * строки в таблице перелётов `bookings.segments`. > **Примечание** > > Если не удаётся найти подходящий маршрут или на выбранный маршрут не удаётся забронировать билеты на всех пассажиров, попытка бронирования считается неудачной и отменяется. 5. С вероятностью, определяемой параметром `gen.roundtrip_frac`, выполняется попытка забронировать полёт в обратном направлении через случайный интервал времени (до месяца, в среднем — около недели). > **Примечание** > > * Маршрут обратного следования может не совпадать с исходным маршрутом. > * Неуспешная попытка не отменяет бронирования в прямом направлении. #### FLIGHT Событие добавления рейса в расписание за месяц до отправления. Обрабатывается процедурой `open_booking`, которая вставляет строку в таблицу `bookings.flights` для рейса, открывая возможность бронирования (статус `Scheduled`). С вероятностью `gen.cancel_frac` рейс отменяется (статус `Cancelled`). Отмена всегда происходит заранее: рейс не отменяется после того, как на него куплены какие-либо билеты. За день до отправления неотмененного рейса на него открывается регистрация, за что отвечает добавляемое событие `REGISTRATION`. #### REGISTRATION Событие открытия регистрации на рейс за день до отправления. Обрабатывается процедурой `registration`, которая меняет статус рейса на `On Time` и определяет фактическое время начала регистрации (допустима небольшая задержка на несколько минут). С вероятностью, определяемой параметром `gen.delay_frac`, рейс задерживается (статус `Delayed`) на время от 1 часа до 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 Событие очистки. Обрабатывается процедурой `vacuum`. Поскольку обработка очереди событий происходит в одном операторе `CALL` (хоть и в разных транзакциях), статистика изменения таблиц не передаётся сборщику статистики: автоочистка не имеет представления, что содержимое таблиц меняется, и не срабатывает. Поэтому раз в модельную неделю очистка и анализ запускаются принудительно. Запуск происходит в отдельном процессе с помощью расширения `dblink`. #### MONITORING Событие мониторинга. Обрабатывается процедурой `monitoring`, которая раз в модельный день заносит в журнальную таблицу `gen.log` сведения о прошедшем дне. ### `gen.events_history` Таблица, содержащая отработанные события из таблицы `gen.events` для возможности отладки. ### `gen.airplanes_data` Таблица с данными о моделях самолетов. Все строки и все столбцы, за исключением столбца `in_use`, который определяет, надо ли назначать данную модель на рейсы, переносятся в таблицу `bookings.airplanes_data`. ### `gen.seats` Таблица с данными о конфигурации салонов самолётов. Все данные без изменений переносятся в таблицу `bookings.seats`. ### `gen.seats_remain` Таблица, используемая генератором для отслеживания оставшихся свободных мест на рейсах. ### `gen.airports_data` Таблица с данными об аэропортах. В таблицу `bookings.airports_data` переносятся все строки и все столбцы, за исключением столбцов: * `country_code` — двухсимвольный код страны в соответствии с ISO 3166-1 alpha-2 (попадает в номер билета `bookings.tickets.ticket_no`); * `traffic` — относительный пассажиропоток авиакомпании в данном аэропорту. Абсолютное количество попыток бронирований в неделю из данного аэропорта — значение `traffic`, умноженное на значение `gen.traffic_coeff`. ### `gen.directions` Таблица, в которую добавляются пары аэропортов, которые будет связаны прямыми рейсами. Используется генератором при составлении графа перелётов. ### `gen.directions_connect` Таблица, которая используется генератором для определения связности графа перелётов. ### `gen.airports_to_prob` Таблица, которая хранит предварительно вычисленные накопленные вероятности полёта из одного аэропорта в другой (необязательно связанных прямым рейсом) и используется при составлении графа перелётов и для выбора аэропорта назначения при бронировании. ### `gen.week_traffic` Таблица, содержащая прогноз пассажиропотока между двумя аэропортами, соединенными прямым рейсом. ### `gen.firstnames` Таблица с данными о популярности имен в разбивке по странам. С помощью столбца `grp` можно разделить имена одной страны на группы, например мужские и женские для тех языков, в которых форма фамилии зависит от пола. При инициализации в столбец `cume_dist` заносится накопленная вероятность выбора данного имени. ### `gen.lastnames` Таблица с данными о популярности фамилий в разбивке по странам. С помощью столбца `grp` можно разделить фамилии одной страны на группы. При инициализации в столбец `cume_dist` заносится накопленная вероятность выбора данной фамилии. ### `gen.passengers` Таблица для отслеживания уникальных пассажиров. Используется генератором, чтобы гарантировать, что одному номеру документа (`passenger_id`) соответствует одно имя, а также что один пассажир не участвует в нескольких одновременных поездках. Информация из этой таблицы не переносится в демобазу, в которой нет отдельной сущности «пассажир». ### `gen.missed_flights` Таблица, используемая генератором для отслеживания билетов тех пассажиров, которые опоздали на перелёт из-за задержки предыдущего рейса. ### `gen.stat_bookings` Статистика по бронированиям. Используется для мониторинга генерации. ### `gen.stat_jobs` Статистика по параллельным процессам. В норме счётчики (столбец `events`) обработанных событий для каждого процесса должны быть примерно равны. Если счётчик какого-либо процесса не увеличивается во время генерации, следует искать сообщение об ошибке в журнале `gen.log`. ### `gen.stat_bookrefs` Статистика по количеству повторов, которые потребовались для генерации уникального номера бронирования (`book_ref`). ## Вопросы и ответы ### Как добавить для демобазы новый язык? Язык перевода в демобазе выбирается параметром `bookings.lang`. Переводятся названия моделей самолетов (представление `airplanes`), а также названия аэропортов, городов и стран (представление `airports`). Изначально доступно два языка: русский (`ru`) и английский (`en`). Чтобы добавить новый язык, добавьте переводы в JSON-столбцы `airport_name`, `city` и `country` таблицы `gen.airports_data` и столбец `model` таблицы `gen.airplanes_data`. Это непростая задача, поскольку в таблице аэропортов содержится около 5500 строк. Как минимум необходимо перевести строки с непустым значением пассажиропотока (`traffic`), то есть названия аэропортов, участвующих в формировании маршрутов. ### Как изменить флот авиакомпании? Чтобы изменить флот авиакомпании, укажите новые модели самолетов и конфигурацию их салонов в таблицах `gen.airplanes_data` и `gen.seats`. Они будут перенесены в аналогичные `bookings`-таблицы. В рейсах будут участвовать только самолеты с признаком `in_use`. > **Примечание** > > * Дальность полёта выбранного набора самолетов должна покрывать расстояния между городами, иначе некоторые перелёты могут оказаться нереализуемыми. > * Вместимость самолётов должна соответствовать планируемому пассажиропотоку: рейс выполняется минимум раз в неделю и максимум семь раз в неделю. Без должного разнообразия вместимостей все рейсы могут оказаться переполненными или недозаполненными. ### По каким критериям были выбраны страны для демобазы? Для демобазы были выбраны страны ведущих разработчиков PostgreSQL, а также несколько других стран, имеющих определенное отношение к PostgreSQL. Подробнее о добавлении новых стран смотрите [ниже](#как-добавить-новую-страну). ### Каков критерий выбора количества аэропортов? Количество аэропортов в демобазе пропорционально квадрату суммы логарифмов площадей стран и численности их населения: $$ 0,5 ( log( площадь, км^2 ) + log( население, млн ) - 5 )^2 $$ Подробнее о добавлении новых аэропортов смотрите [ниже](#как-добавить-новый-аэропорт). ### Как изменить маршрутную сеть в пределах существующих стран и городов? Установите непустое значение пассажиропотока (`traffic`) для желаемых аэропортов в таблице `gen.airports_data`. Эти аэропорты будут использоваться при формировании маршрутов. ### Как добавить новую страну? При добавлении новой страны недостаточно просто выбрать аэропорты в таблице `gen.airports_data`. Потребуется определить справочники имён (`firstnames.dat` и `lastnames.dat`) для новой страны. Для это нужно обновить таблицы `gen.firstnames` и `gen.lastnames`. Вам потребуется: * `country` — двухсимвольный код страны в соответствии с ISO 3166-1 alpha-2. * `grp` — `-` или критерий, по которому имена/фамилии делятся на группы. В этот столбец можно поместить любое текстовое значение, но оно должно совпадать в обеих таблицах (например, можно использовать `m` и `f`). Делить имена и фамилии на мужские и женские потребуется для языков, в которых форма фамилии зависит от рода имени (например, славянские и балтийские языки). Имена, относящиеся как к мужским, так и к женским, должны быть помещены в обе группы (возможно, с разной популярностью). Тот же принцип можно использовать для разделения и на другие группы, например чтобы смоделировать этнические группы с обособленными системами имён. * `name` — новое имя/фамилия на латинице; * `qty` — целое число, отражающее популярность имени/фамилии. Например, зарегистрированное количество людей, носящих данное имя/фамилию, по результатам переписи населения. В настоящее время справочники генератора содержат имена, записанные латиницей, для следующих стран: Россия (`RU`), Китай (`CN`), Индия (`IN`), США (`US`), Канада (`CA`), Япония (`JP`), Франция (`FR`), Германия (`DE`), Италия (`IT`), Великобритания (`GB`), Чили (`CL`), Швеция (`SE`), Непал (`NP`), Финляндия (`FI`), Новая Зеландия (`NZ`), Австрия (`AT`) и Чехия (`CZ`). Чтобы проверить результат, можно выполнить следующий запрос, где `XX` — код страны: ```sql CALL calc_names_cume_dist(); -- рассчитывает накопленную вероятность SELECT get_passenger_name('XX') FROM generate_series(1,10); ``` При генерации имени из всех групп выбирается случайная фамилия, а затем выбирается случайное имя из той группы, к которой принадлежит фамилия. При этом полные имена, в которых имя и фамилия одинаковы, не выбираются. ### Как добавить новый аэропорт? Чтобы добавить новый аэропорт, добавьте новую строку в таблицу `gen.airports_data` и заполните её следующей информацией: * код Международной ассоциации воздушного транспорта (IATA) (`airport_code`); * название аэропорта на доступных в демобазе языках (`airport_name`); * название города на доступных в демобазе языках (`city`); * страну на доступных в демобазе языках (`country`); * код страны (`country_code`); * часовой пояс (`timezone`); * координаты аэропорта (указываются в формате «долгота, широта») (`coordinates`). Полной актуальной базы аэропортов в свободном доступе нет. Существует несколько открытых проектов, таких как [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/) на официальном сайте Международной ассоциации воздушного транспорта (IATA). Чтобы новая запись в таблице `gen.airports_data` была согласована с существующими, стоит придерживаться некоторых правил: * В качестве города (`city`) выбирается город, который обслуживается этим аэропортом. Часто это ближайший к аэропорту крупный населённый пункт, а не тот пункт, в котором расположен аэропорт. Например, для IAR городом является Ярославль, а не село Туношна. * Официальное название аэропорта часто состоит из названия населенного пункта, в котором расположен аэропорт, посвящения какому-либо видному деятелю и других частей. Для краткости имя сокращается по следующим правилам: * Название города не повторяется в названии аэропорта (за исключением случаев, когда официальное название состоит только из названия города; например, AAC «El Arish»). * В качестве названия аэропорта используется либо название населённого пункта, где расположен аэропорт, либо имя человека, причём предпочтение отдаётся населённому пункту (например, SVO «Шереметьево имени А. С. Пушкина» сокращается до «Шереметьево»). * Если используется посвящение человеку, титулы и воинские звания отбрасываются (например, YAI «General Bernardo O'Higgins» сокращается до «Bernardo O'Higgins»). * Отбрасываются слова «международный», «региональный» и т. п. (например, ROA «Roanoke–Blacksburg Regional» сокращается до «Blacksburg», учитывая, что Roanoke — название города). * В русском языке промежуточные инициалы убираются (например, JFK «John F. Kennedy» переводится как «Джон Кеннеди»).