{"id":32382573,"url":"https://github.com/postgrespro/demodb","last_synced_at":"2025-10-25T01:49:03.295Z","repository":{"id":317607795,"uuid":"1061923814","full_name":"postgrespro/demodb","owner":"postgrespro","description":"Demonstration Database","archived":false,"fork":false,"pushed_at":"2025-09-25T14:22:33.000Z","size":1623,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-10-01T23:57:07.290Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"PLpgSQL","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/postgrespro.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-09-22T15:09:08.000Z","updated_at":"2025-09-29T10:05:39.000Z","dependencies_parsed_at":null,"dependency_job_id":"75043360-7268-4875-ad6b-074433737c27","html_url":"https://github.com/postgrespro/demodb","commit_stats":null,"previous_names":["postgrespro/demodb"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/postgrespro/demodb","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/postgrespro%2Fdemodb","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/postgrespro%2Fdemodb/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/postgrespro%2Fdemodb/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/postgrespro%2Fdemodb/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/postgrespro","download_url":"https://codeload.github.com/postgrespro/demodb/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/postgrespro%2Fdemodb/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":280893644,"owners_count":26409279,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-24T02:00:06.418Z","response_time":73,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-10-25T01:48:20.733Z","updated_at":"2025-10-25T01:49:03.284Z","avatar_url":"https://github.com/postgrespro.png","language":"PLpgSQL","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Генератор демобазы\n\n## Назначение\n\nПрограмма-генератор создает базы данных с той же структурой, что и [демобаза](https://postgrespro.ru/education/demodb), но иным наполнением. Она имеет ряд настроек, позволяющих изменить характеристики данных, есть возможность поменять маршрутную сеть и флот авиакомпании и т. п.\n\nПрограмму также можно использовать как генератор нагрузки, например для демонстрации систем мониторинга или тренировки оптимизации запросов.\n\n## Установка\n\nПодключитесь в psql к любой базе, кроме `demo`, убедитесь, что находитесь в каталоге репозитория, и выполните установку:\n\n```sql\n\\! pwd\n\\i install\n```\n\nПри необходимости смените текущий каталог командой `\\cd`.\n\nВ ходе установки будет заново создана база данных `demo`. Если она существовала, то все данные в ней будут потеряны! В этой базе данных будут созданы две схемы: `gen` для объектов генератора и `bookings` для создаваемой демобазы.\n\nУстанавливаются расширения `btree_gist` (для реализации темпорального ключа), `earthdistance` и `cube` (для расчета расстояний на сфере), а также `dblink` (для запуска параллельных процессов). Эти расширения входят в стандартный набор, но убедитесь, что они присутствуют в вашей установке PostgreSQL.\n\n## Быстрый старт\n\nГенерация запускается процедурой `generate`, которой передаются начальное и конечное _модельное_ время (которое будет фигурировать в таблицах демобазы). Например:\n\n```sql\nCALL generate( now(), now() + interval '1 year' );\n```\n\nТакая команда выполнит необходимую инициализацию, создаст очередь событий и начнет генерацию демобазы за один год.\n\nЧтобы ускорить работу, можно запустить генерацию в нескольких параллельных процессах:\n\n```sql\nCALL generate( now(), now() + interval '1 year', 4 );\n```\n\nРабота в любом случае выполняется в параллельных процессах (даже если такой процесс один), поэтому команда `CALL` немедленно возвращает управление.\n\nБыстро проверить состояние генерации можно запросом:\n\n```sql\nSELECT busy();\n```\n\nИстинное значение будет означать, что генерация еще в процессе.\n\nБолее полное представление о процессе можно получить, наблюдая за журналом сообщений генератора:\n\n```sql\nSELECT * FROM gen.log ORDER BY at DESC LIMIT 30 \\watch 60\n```\n\nРазрыв соединения не завершает работу генератора. Прервать генерацию досрочно можно командой:\n\n```sql\nCALL abort();\n```\n\nПосле успешного окончания генерации полезно ознакомиться с получившимися характеристиками:\n\n```sql\n\\i check.sql\n```\n\nЕсли все в порядке, полученную демобазу можно выгрузить в виде SQL-скрипта. Для этого в операционной системе выполните команду, находясь в каталоге репозитория:\n\n\n```sh\n./export.sh \u003e `date +%Y%m%d`.sql\n```\n\nГенерацию можно продолжить с того момента, на котором она остановилась. Например, сгенерировать данные еще за три месяца:\n\n```sql\nCALL continue( now() + interval '1 year 3 month', 4 );\n```\n\n## Настройка\n\nГенерация настраивается рядом конфигурационных параметров. Доступ к значениям параметров должны иметь параллельные процессы, поэтому установка на уровне сеанса (`SET`) не сработает. Удобнее всего устанавливать параметры на уровне базы данных:\n\n```sql\nALTER DATABASE demo SET gen.имя-параметра = значение;\n```\n\n(Внутренние настройки не вынесены на уровень пользователя, но присутствуют в исходном коде в виде immutable-функций; неудачные значения могут привести к ошибкам генерации или к неожиданным результатам. Часть настроек и вовсе никуда не вынесена и присутствует в коде только в виде констант.)\n\n### gen.connstr\n\nСтрока подключения к базе данных `demo`.\n\nЗначение по умолчанию `dbname=demo`, что соответствует подключению к локальному серверу. При необходимости укажите любые параметры, допускаемые libpq.\n\n### gen.airlines\\_name\n\nНазвание авиакомпании. Появляется только в значении, возвращаемом функцией `bookings.version`.\n\nЗначение по умолчанию `PostgresPro`.\n\n### gen.airlines\\_code\n\nКод авиакомпании. Используется как префикс номеров билетов `bookings.tickets.ticket_no`.\n\nЗначение по умолчанию `PG`.\n\n### gen.traffic\\_coeff\n\nКоэффициент для пересчета относительного пассажиропотока `gen.airports_data.traffic` в количество бронирований из данного аэропорта в неделю.\n\n### gen.domestic\\_frac\n\nДоля рейсов, которая должна приходиться на внутренние перелеты в пределах одной страны. Это целевое значение; оно играет роль при формировании графа перелетов, но граф в любом случае будет связным. \n\nЗначение по умолчанию `0.9`.\n\n### gen.roundtrip\\_frac\n\nДоля бронирований, при которых билеты покупаются «туда и обратно». Это целевое значение; в реальности доля будет меньше из-за того, что обратные билеты не всегда доступны.\n\nЗначение по умолчанию `0.9`.\n\n### gen.delay\\_frac\n\nДоля задержанных рейсов.\n\nЗначение по умолчанию `0.05`.\n\n### gen.cancel\\_frac\n\nДоля отмененных рейсов.\n\nЗначение по умолчанию `0.005`.\n\n### gen.exchange\n\nКоэффициент пересчета минут полета в стоимость билета в предпочитаемой валюте. В зависимости от класса обслуживания применяется дополнительный повышающий коэффициент (функция `get_price`).\n\nЗначение по умолчанию `50`.\n\n### gen.max\\_pass\\_per\\_booking\n\nМаксимальное количество пассажиров в одном бронировании.\n\nЗначение по умолчанию `5`.\n\n### gen.min\\_transfer\n\nМинимальное время в часах между пересадками. Уменьшение запаса на пересадку увеличивает количество рейсов, доступных для формирования маршрута, но и увеличивает шанс опоздать на стыковочный рейс при задержке предыдущего.\n\nЗначение по умолчанию `2`.\n\n### gen.max\\_transfer\n\nМаксимальное время в часах между пересадками. Увеличение этого порога увеличивает количество рейсов, доступных для формирования маршрута, но где взять таких терпеливых пассажиров?\n\nЗначение по умолчанию `48`.\n\n### gen.max\\_hops\n\nМаксимальное количество пересадок в одном билете. Чем больше допускается пересадок, тем больше вероятность, что пассажир сумеет воспользоваться нашей авиакомпанией, чтобы добраться до желаемого пункта.\n\nЗначение по умолчанию `4`.\n\n### gen.log\\_severity\n\nПриоритет сообщений, попадающих в журнал `gen.log`. Наиболее важные сообщения имеют приоритет 0.\n\nЗначение по умолчанию `0`, что означает, что записываются только важные сообщения. Большее значение имеет смысл устанавливать только для отладки.\n\n### bookings.lang\n\nЯзык, на котором в демобазе будут выводиться названия моделей самолетов, стран, городов и аэропортов. Это значение будет устанавливаться при развертывании демобазы, но впоследствии может быть изменено. Поддерживаются русский (`ru`) и английский (`en`) языки.\n\nЗначение по умолчанию `en`.\n\n## Программный интерфейс\n\nРяд процедур и функций образуют API, с помощью которого пользователь взаимодействует с генератором.\n\n### generate\n\nПодпрограмма `generate` начинает генерацию демобазы. Ее параметры:\n\n* `start_date` (`timestamptz`) — модельное время начала генерации;\n* `end_date` (`timestamptz`) — модельное время окончания генерации;\n* `jobs` (`integer`) — количество параллельных процессов (по умолчанию 1).\n\nПример вызова:\n\n```sql\nCALL generate(\n    start_date =\u003e date_trunc( 'day', now() ),\n    end_date   =\u003e date_trunc( 'day', now() + interval '1 year' ),\n    jobs       =\u003e 4\n);\n```\n\n### continue\n\nПодпрограмма `continue` продолжает генерацию демобазы после останова прошлого вызова `generate` или `continue`. Ее параметры:\n\n* `end_date` (`timestamptz`) — модельное время окончания генерации;\n* `jobs` (`integer`) — количество параллельных процессов (по умолчанию 1).\n\nВ качестве начальной даты будет взято время предыдущего останова генерации.\n\nПример вызова:\n\n```sql\nCALL continue(\n    end_date   =\u003e date_trunc( 'day', now() + interval '2 years' ),\n    jobs       =\u003e 4\n);\n```\n\n### busy\n\nФункция `busy` возвращает текущее состояние генерации: `true` — работает, `false` — завершилась.\n\nПример вызова:\n\n```sql\nSELECT busy();\n```\n\n### abort\n\nПроцедура `abort` досрочно прерывает генерацию. После прерывания можно начать генерацию заново с помощью `generate`, но вызов `continue` не гарантирует корректного продолжения.\n\nПример вызова:\n\n```sql\nCALL abort();\n```\n\n### get\\_passenger\\_name\n\nФункция `get_passenger_name` выдает случайное имя пассажира из указанной страны. \n\nПараметр:\n\n* `country` (`text`) — код страны.\n\nСправочники генератора содержат имена, записанные латиницей, для следующих стран: Россия (`RU`), Китай (`CN`), Индия (`IN`), США (`US`), Канада (`CA`), Япония (`JP`), Франция (`FR`), Германия (`DE`), Италия (`IT`), Великобритания (`GB`), Чили (`CL`), Швеция (`SE`), Непал (`NP`), Финляндия (`FI`), Новая Зеландия (`NZ`), Австрия (`AT`) и Чехия (`CZ`).\n\nФункцию не требуется вызывать отдельно, но эта часть генератора может быть полезной для наполнения других баз данных, в которых требуется случайные имена с правдоподобным распределением.\n\nПример вызова, выводящего десять случайных непальских имен:\n\n```sql\nCALL calc_names_cume_dist(); -- необходимо выполнить один раз\nSELECT get_passenger_name('NP') FROM generate_series(1,10);\n```\n\n## Журнальная таблица\n\nГенератор записывает сообщения в журнальную таблицу `gen.log`, которую можно исследовать после генерации или использовать в процессе генерации для мониторинга:\n\n```sql\nSELECT * FROM gen.log ORDER BY at DESC LIMIT 30 \\watch 60\n```\n\nВ журнальную таблицу попадают все сообщения с приоритетом не ниже, чем значение параметра `gen.severity`. Для целей отладки набор сообщений можно расширить, указав ненулевой приоритет.\n\nНиже перечислены основные типы сообщений и их значение.\n\n* Job _N_ (connname=_соединение_): _результат_\n\n  Запуск рабочего процесса номер _N_, используя соединение с именем _соединение_.\n\n* _день_: one day in _время_\n\n  Выводится раз в день модельного времени и сообщает, сколько реального времени занял расчет этого модельного дня. За этим сообщением следует несколько дополнительных.\n\n* New bookings: _B_ (forceoneway _F_), nopath _P_, noseat _S_\n\n  За день модельного времени было создано _B_ бронирований. Из них _F_ штук планировались как бронирования «туда и обратно» (с учетом значения параметра `gen.roundtrip_frac`), но из-за отсутствия нужных билетов бронь была сделана только в одну сторону. При этом _P_ попыток создать бронирование полностью не удались из-за отсутствия подходящих рейсов, а _S_ — из-за того, что на одном из выбранных рейсов не оказалось свободных мест.\n   \n* Book-ref retries = _R_ (_F_ retries/booking)\n\n  За день модельного времени потребовалось _R_ раз повторно выбирать случайный номер бронирования из-за того, что выбранный номер уже занят. Значение _F_ показывает количество повторных попыток в пересчете на одно бронирование.\n\n  Чем дольше работает генератор, тем больше номеров будет занято и тем больше времени будет уходить на поиск свободного значения. Емкость номера бронирования составляет около 2 млрд значений; за один модельный год при настройках по умолчанию генерируется около 5 млн бронирований, поэтому на горизонте нескольких модельных лет эта причина не вызывает замедления.\n\n* New boarding passes: _BP_\n\n  За день модельного времени было создано _BP_ посадочных талонов.\n\n* Building routes, range _период_\n\n  Выполняется перестроение маршрутов с периодом действия _период_. После этого сообщения идет перечисление новых маршрутов.\n\n* _А1_ -\u003e _A2_: _модель_ x _N_, traffic = _P_ pass/week\n\n  Построен маршрут из аэропорта _А1_ в аэропорт _А2_. Он выполняется самолетом _модель_ _N_ раз в неделю, предполагаемый пассажиропоток составляет _P_ пассажиров в неделю.\n\n* Cannot choose an aircraft for _А1_ -\u003e _А2_ (out of range?)\n\n  Маршрут из аэропорта _А1_ в аэропорт _А2_ не удалось построить. Обычная причина состоит в том, что расстояние между аэропортами превышает дальность всех самолетов компании. Само по себе такое сообщение не является ошибкой, но может указывать на неудачно выбранный флот, если повторяется многократно. \n\n* Vacuum\n\n  Выполнение очистки и анализа.\n\n* End date reached, exiting\n\n  Рабочий процесс завершил свою работу.\n\n## Скрипт проверки\n\nПосле того как демобаза сгенерирована, имеет смысл выполнить стандартные проверки, чтобы убедиться в качестве подготовленных данных.\n\n```sql\n\\i check.sql\n```\n\nСкрипт выполняет несколько запросов, описанных ниже.\n\n### Generation errors in log\n\nЧисло, отличное от нуля, означает, что в процессе генерации произошли ошибки. Ошибки будут записаны в журнальную таблицу `gen.log` и будут начинаться со слова Error.\n\n### Generation stats\n\nЗапросы этой секции показывают количество сгенерированных бронирований, билетов, перелетов, рейсов и маршрутов.\n\n### Generation speed\n\nСкорость генерации в событиях в секунду.\n\nРассчитанная скорость включает и время простоя между вызовами `generate` и `continue`.\n\n### Airplanes utilization\n\nПервый запрос показывает среднюю заполненность салонов самолетов.\n\nВторой запрос показывает количество рейсов, выполненных с пустым салоном. Небольшое количество пустых рейсов может выглядеть правдоподобно.\n\nТретий запрос показывает список моделей самолетов и количество маршрутов, которые эта модель обслуживала. Учтите, что в разные периоды времени один и тот же маршрут может обслуживаться разными самолетами, поэтому выведенные числа не стоит складывать. Вердикт `NOT USED` означает, что модель не выполнила ни одного рейса — такую модель, скорее всего, стоит заменить. Вердикт `WRONGLY USED` свидетельствует об ошибке в алгоритме и не должен появляться.\n\n### Roundtrips to overall tickets\n\nОтношение количества билетов «туда и обратно» к общему количеству билетов. Второе число показывает целевое значение (заданное параметром `gen.roundtrip_frac`). Реальное значение всегда будет меньше целевого из-за того, что не для каждого прямого билета удается купить обратный.\n\n### Passengers per booking\n\nВердикт `ERROR: max_pass_per_bookings not satisfied` говорит о том, что в бронированиях бывает более `gen.max_pass_per_booking` пассажиров и свидетельствует об ошибке в алгоритме.\n\nВторой запрос показывает распределение бронирований по количеству пассажиров в них. Например, строка с `npass`, равным 2, и `cnt`, равным 100, говорит о том, что сто бронирований включают двух пассажиров. \n\n### Frequent flyers\n\nЗапрос показывает распределение пассажиров по количеству выполненных ими бронирований. Например, строка с `nbook`, равным 3, и `cnt_pass`, равным 1000, говорит о том, что тысяча пассажиров сделали по три бронирования.\n\n### Segments per ticket\n\nЗапрос показывает распределение билетов по количеству перелетов в них. Например, строка с `segments`, равным 3, и `cnt`, равным 1000, говорит о том, что тысяча билетов включает три перелета.\n\n### Flight statuses\n\nКоличество рейсов в разных статусах.\n\nТри статуса, в которых рейсов немного, независимо от размера базы, — это `On Time` (вылет по расписанию), `Departed` (вылетел и находится в воздухе) и `Boarding` (посадка пассажиров). Если таких рейсов нет, возможно стоит продолжить генерацию на несколько модельных часов, чтобы обеспечить разнообразие данных.\n\n### Flight durations\n\nВердикт `ERROR: route and flight discrepancy` в первом запросе говорит о рассогласовании данных о продолжительности полетов между таблицами `routes` и `flights` и свидетельствует об ошибке в алгоритме.\n\nВторой запрос показывает минимальную, среднюю и максимальную продолжительность полетов — как запланированную, так и реальную.\n\n### Flight delays\n\nМинимальная, средняя и максимальная задержка вылетов и прибытий.\n\n### Overbookings\n\nВердикт `ERROR: overbooking` говорит о том, что посадочных талонов выдано больше, чем мест в самолете, и свидетельствует об ошибке в алгоритме.\n\n### Cancelled flights fraction\n\nДоля отмененных рейсов и целевое значение (параметр `gen.cancel_frac`). Должны совпадать с хорошей точностью.\n\n### Adjacency of segments\n\nВердикт `ERROR: non-adjacent segments` говорит о том, что аэропорт прибытия одного перелета не совпадает с аэропортом вылета следующего перелета из того же билета. Это свидетельствует об ошибке в алгоритме.\n\n### Routes validity ranges\n\nВердикт `ERROR: validity ranges have holes` говорит о наличии пропусков между периодами действия маршрутов. Это свидетельствует об ошибке в алгоритме.\n\n### Flights consistency with routes\n\nВердикт `ERROR: absent flights` говорит о том, что в таблице `flights` отсутствуют рейсы, которые должны быть согласно таблице `routes`.\n\nВердикт `ERROR: excess flights` говорит о том, что в таблице `flights` есть рейсы, не соответствующие расписанию в таблице `routes`.\n\nВозможны оба сообщения одновременно (`absent and excess flights`). Любая ошибка свидетельствует об ошибке в алгоритме.\n\n### Timings\n\nВердикт первого запроса `ERROR: flights timing discrepancy` говорит о рассогласовании времен в информации о рейсе.\n\nВердикт второго запроса `ERROR: boarding after takeoff` говорит о том, что посадка пассажиров продолжалась после взлета.\n\nВердикт третьего запроса `ERROR: booking after boarding` говорит о том, что бронирование на рейс продолжалось после начала посадки пассажиров.\n\nЛюбая ошибка в этой секции свидетельствует об ошибке в алгоритме.\n\n### Miss the flight\n\nПассажир может опоздать на рейс из-за задержки предыдущего стыковочного рейса. Генератор отслеживает такие ситуации, чтобы не допустить опоздавшего пассажира к следующим рейсам в билете.\n\nПервый запрос выводит фактическое количество пассажиров, опоздавших на рейс, а также количество некорректно зарегистрированных генератором опозданий и количество незарегистрированных генератором опозданий. Вердикт `ERROR: incorrect missed flights` свидетельствует об ошибке в алгоритме.\n\nВердикт второго запроса `ERROR: boarding after miss` говорит о том, что пассажир садился в самолет после опоздания и свидетельствует об ошибке в алгоритме.\n\n### Interlaced flights\n\nВердикт `ERROR: interlaced flights` говорит о том, что один и тот же пассажир совершал более одного путешествия одновременно. Это свидетельствует об ошибке в алгоритме.\n\n## Экспорт демобазы\n\nСгенерированную демобазу можно выгрузить в виде SQL-скрипта, аналогичного результату работы команды `pg_dump`. Находясь в каталоге репозитория выполните скрипт, перенаправляя его результат в файл, например:\n\n\n```sh\n./export.sh | gzip \u003e `date +%Y%m%d`.sql.gz\n```\n\nСкрипту можно передать любые параметры подключения, принимаемые утилитой `pg_dump`.\n\nВ SQL-скрипт будут входить объекты схемы `booking`, включая определения функций `bookings.now` (значение конечной модельной даты, указанной при генерации) и `bookings.version` (версия сгенерированной демобазы). Также будут включены команды для установки параметров `bookings.lang` и `search_path` на уровне базы данных.\n\n## Внутреннее устройство\n\nГенератор имитирует работу авиакомпании, создавая и обрабатывая поток случайных событий: бронирование авиабилетов пассажирами, регистрация и посадка, отправления и прибытия самолетов и т. п. Обработчик события может добавлять в очередь новые события, реализуя, в частности, конечный автомат состояний рейса.\n\nВсе таблицы генератора располагаются в схеме `gen`. Они используются при генерации и хранят ее текущее состояние. Далее, если имя схемы не указано, предполагается схема `gen`.\n\nТаблицы демобазы находятся в схеме `bookings` и описаны в документации по демобазе.\n\n### events\n\nОчередь событий. Очередь обрабатывается процедурой `process_queue`, а отдельное событие - процедурой `process_event`.\n\nНиже перечислены типы событий.\n\n#### INIT\n\nЭто «затравочное» событие, обработка которого (процедура `do_init`) приводит к инициализации состояния всех таблиц и вставке начального набора событий: `BUILD ROUTES`, `BOOKING`, `VACUUM`, `MONITORING`.\n\n#### BUILD ROUTES\n\nСобытие перестройки маршрутов. При его обработке (процедура `build_routes`) создается случайный, но связный граф маршрутов. В маршрутной сети участвуют аэропорты, имеющие непустое значение `traffic` в таблице `airports_data`. Перелеты между несколькими аэропортами одного города никогда не создаются.\n\nДобавляемые ребра графа записываются в таблицу `directions`. Вначале от каждого аэропорта, начиная с наименее загруженного, добавляются ребра к двум другим случайным аэропортам. Вероятность добавления ребра пропорциональна трафику аэропорта назначения и обратно пропорциональна расстоянию между аэропортами. При этом с вероятностью `gen.domestic_frac` выбирается аэропорт той же страны (при наличии в маршрутной сети хотя бы двух городов этой страны).\n\nСвязность графа отслеживается с помощью таблицы `directions_connect`. Если построенный граф не является связным, в него по тем же правилам добавляются другие случайные ребра, пока связность не будет достигнута.\n\nНовые маршруты переносятся из `directions` в таблицу `bookings.routes` с соответствующим интервалом действия `validity`. Самолет, обслуживающий маршрут, и количество рейсов в неделю подбираются исходя из прогнозируемого трафика между аэропортами (таблица `week_traffic`).\n\nПервое событие `BUILD ROUTES` добавляется при обработке `INIT` и порождает расписание на месяц, начиная с даты начала генерации. Далее маршруты перестраиваются каждый месяц, для чего в конце обработки события в очередь вставляется следующее событие `BUILD ROUTES`. Также для каждого построенного маршрута добавляется событие `FLIGHT`, отмечающее начало бронирований на этот маршрут за месяц до его вступления в силу.\n\n#### BOOKING\n\nСобытие бронирования из заданного аэропорта. При его обработке (процедура `make_booking`) совершается попытка бронирования путешествия в случайный аэропорт. Вероятность выбора целевого аэропорта пропорциональна его трафику и обратно пропорциональна расстоянию между аэропортами, а также учитывает параметр `gen.domestic_frac` (как и при создании расписания).\n\nМаршрут до выбранного аэропорта определяется (функция `get_path`) с учетом следующих факторов:\n* предпочтению отдается маршруту с наименьшим количеством пересадок, причем их количество не должно превышать `gen.max_hops`;\n* рассматриваются рейсы, на которые в продаже есть билеты и осталось достаточно времени до отправления (еще не открыта регистрация на рейс);\n* между стыковочными рейсами должно быть как минимум `gen.min_transfer` часов (иначе велик риск пропустить рейс из-за возможной задержки) и не должно быть больше `gen.max_transfer` часов (иначе слишком долго ждать);\n* среди возможных маршрутов выбирается тот, чей первый перелет начинается раньше. \n\nКоличество пассажиров в бронировании определяется случайно с учетом параметра `gen.max_pass_per_booking`.\n\nЕсли не удается найти подходящий маршрут или если на выбранный маршрут не удается забронировать билеты на всех пассажиров, попытка бронирования считается неудачной и отменяется.\n\nС вероятностью `gen.roundtrip_frac` выполняется также попытка забронировать полет в обратном направлении через случайный интервал времени (до месяца, в среднем — около недели). Маршрут обратного следования может не совпадать с исходным маршрутом. Неуспешная попытка не отменяет бронирования в прямом направлении.\n\nВ результате бронирования создается строка в таблице `bookings.bookings`, строки для каждого билета в `bookins.tickets` (по одной на каждого пассажира и направления следования), строки в таблице перелетов `bookings.segments`.\n\nСледующее событие `BOOKING` для данного аэропорта-источника создается так, чтобы события образовывали пуассоновский поток с частотой, соответствующей пассажиропотоку аэропорта. В среднем в неделю будет генерироваться `airports_data.traffic` умноженное на значение параметра `gen.traffic_coeff` событий бронирования.\n\n#### FLIGHT\n\nСобытие добавления рейса в расписание за месяц до отправления.\n\nОбработчик (процедура `open_booking`) вставляет строку в таблицу `bookings.flights` для рейса, открывая возможность бронирования (статус `Scheduled`). С вероятностью `gen.cancel_frac` рейс отменяется (статус `Cancelled`). Отмена всегда происходит заранее: не бывает ситуаций, когда рейс отменяется уже после того, как на него куплены какие-либо билеты.\n\nЗа день до отправления неотмененного рейса на него должна открыться регистрация, за это отвечает добавляемое событие `REGISTRATION`. \n\n#### REGISTRATION\n\nСобытие открытия регистрации на рейс за день до отправления.\n\nОбработчик (процедура `registration`) меняет статус рейса на `On Time` и определяет фактическое время начала регистрации (допустима небольшая задержка на несколько минут). С вероятностью `gen.delay_frac` рейс задерживается (статус `Delayed`) на время от часа до 12 часов.\n\nСоздает события регистрации `CHECK-IN` для каждого билета, проданного на данный рейс (регистрация заканчивается за 40 минут до отправления). Также создает событие начала посадки `BOARDING` на этот рейс.\n\n#### CHECK-IN\n\nСобытие регистрации на рейс для конкретного билета.\n\nПассажир проходит регистрацию только один раз, на первый рейс в билете. Обработчик (процедура `check_in`) создает строки в таблице `bookings.boarding_passes` для посадочных талонов на каждый рейс в билете (мы считаем, что все рейсы в билете являются стыковочными).\n\n#### BOARDING\n\nСобытие начала посадки на рейс за полчаса до вылета.\n\nОбработчик (процедура `boarding`) меняет статус рейса на `Boarding` и создает события посадки `GET IN` для каждого билета, проданного на рейс. Посадка завершается в течение 20 минут.\n\nЕсли посадка заканчивается до конца периода генерации, события `GET IN` не создаются, а вместо этого номер и время посадки проставляются в посадочных талонах (таблица `bookings.boarding_passes`) немедленно. Эта оптимизация позволяет существенно сократить количество событий и ускорить генерацию.\n\nТакже создает событие взлета `TAKEOFF`, определяя фактическое время вылета (допускается небольшая задержка на несколько минут).\n\n#### GET IN\n\nСобытие посадки в самолет для конкретного билета.\n\nОбработчик (процедура `get_in`) проставляет номер и время посадки в посадочном талоне (таблица `bookings.boarding_passes`).\n\n#### TAKEOFF\n\nСобытие взлета.\n\nОбработчик (процедура `takeoff`) меняет статус рейса на `Departed` и создает событие приземления `LANDING`, определяя фактическую продолжительность полета (возможно отклонение от плановой продолжительности на несколько процентов как в одну, так и в другую сторону).\n\n#### LANDING\n\nСобытие приземления.\n\nОбработчик (процедура `landing`) меняет статус рейса на `Arrived`. На этом рейс считается отработанным; новых событий не генерируется.\n\n#### VACUUM\n\nСобытие очистки.\n\nОбработка очереди событий происходит хотя и в разных транзакциях, но в одном операторе CALL. Из-за этого статистика изменения таблиц не передается сборщику статистики: автоочистка не имеет представления, что содержимое таблиц меняется, и не срабатывает.\n\nПоэтому раз в неделю модельного времени очистка и анализ запускаются принудительно с помощью данного события (процедура `vacuum`). Запуск происходит в отдельном процессе с помощью расширения `dblink`.\n\n#### MONITORING\n\nСобытие мониторинга.\n\nСобытие срабатывает раз в день модельного времени и заносит в журнальную таблицу `log` сведения о прошедшем дне (процедура `monitoring`).\n\n### event\\_history\n\nВ эту таблицу переносятся отработанные строки из `events` для возможности отладки.\n\n### airplanes\\_data\n\nДанные о моделях самолетов. В таблицу `bookings.airplanes_data` переносятся все строки и все столбцы, за исключением столбца `in_use`, который определяет, надо ли назначать данную модель на рейсы.\n\n### seats\n\nДанные о конфигурации салонов самолетов. Все данные переносятся без изменений в таблицу `bookings.seats`.\n\n### seats\\_remain\n\nИспользуется генератором для отслеживания остатка свободных мест на рейсах.\n\n### airports\\_data\n\nДанные об аэропортах. В таблицу `bookings.airports_data` переносятся все строки и все столбцы, за исключением столбцов:\n* `country_code` — двухсимвольный код страны в соответствии с ISO 3166-1 alpha-2 (попадает в номер билета `bookings.tickets.ticket_no`);\n* `traffic` — относительный пассажиропоток авиакомпании в данном аэропорту (абсолютное количество попыток бронирований в неделю из данного аэропорта вычисляется умножением этой величины на значение параметра `gen.traffic_coeff`).\n\n### directions\n\nИспользуется генератором при составлении графа перелетов. В эту таблицу добавляются пары аэропортов, которые будет связаны прямыми рейсами.\n\n### directions\\_connect\n\nИспользуется для определения связности графа перелетов.\n\n### airports\\_to\\_prob\n\nХранит предварительно вычисленные накопленные вероятности полета из одного аэропорта в другой (не обязательно связанных прямым рейсом) и используется при составлении графа перелетов и для выбора аэропорта назначения при бронировании.\n\n### week\\_traffic\n\nПрогноз пассажиропотока между двумя аэропортами, соединенными прямым рейсом.\n\n### firstnames\n\nДанные о популярности имен (given names) в разбивке по странам. Таблица позволяет разделить имена одной страны на группы, например, на мужские и женские для тех языков, в которых фамилия зависит от пола. При инициализации в столбец `cume_dist` заносится накопленная вероятность выбора данного имени.\n\n### lastnames\n\nДанные о популярности фамилий (family names) в разбивке по странам. Таблица позволяет разделить фамилии одной страны на группы. При инициализации в столбец `cume_dist` заносится накопленная вероятность выбора данной фамилии.\n\n### passengers\n\nПеречень уникальных пассажиров. Таблица используется генератором, чтобы гарантировать, что одному номеру документа `passenger_id` соответствует одно имя, а также что один пассажир не участвует в нескольких одновременных поездках. Информация из этой таблицы не переносится в демобазу, в которой нет отдельной сущности «пассажир».\n\n### missed\\_flights\n\nИспользуется генератором для отслеживания билетов тех пассажиров, которые опоздали на очередной перелет из-за задержки предыдущего рейса.\n\n### stat\\_bookings\n\nСтатистика по бронированиям. Используется для мониторинга генерации.\n\n### stat\\_jobs\n\nСтатистика по параллельным процессам. В норме счетчики обработанных событий для каждого процесса должны быть примерно равны. Если счетчик какого-либо процесса не увеличивается во время генерации, следует искать сообщение об ошибке в журнале `log`.\n\n\n### stat\\_bookrefs\n\nСтатистика по количеству повторов, которые потребовались для генерации уникального номера бронирования `book\\_ref`.\n\n## Вопросы и ответы\n\n### Как добавить перевод названий на новый язык?\n\nВ демобазе язык перевода выбирается параметром `bookings.lang`. Переводятся названия моделей самолетов (представление `airplanes`), а также названия аэропортов, городов и стран (представление `airports`). В настоящий момент имеются переводы на два языка: русский (значение параметра `ru`) и английский (`en`).\n\nЧтобы добавить новый язык, необходимо добавить переводы в JSON-столбцы `airplanes_data.model`, `airports_data.airport_name`, `airports_data.city` и `airports_data.country`. Это непростая задача, поскольку в таблице аэропортов содержится около 5500 строк. Как минимум нужно перевести хотя бы строки с непустым значением пассажиропотока (`traffic`), участвующие в маршрутной сети.\n\n### Как изменить флот авиакомпании?\n\nЖелаемые модели самолетов и конфигурацию их салонов необходимо поместить в таблицы `airplanes_data` и `seats`. Все они будут перенесены в аналогичные `bookings`-таблицы. В рейсах будут участвовать только самолеты с признаком `in_use`.\n\nНеобходимо учитывать, что дальность полета выбранного набора самолетов должна покрывать расстояния между городами, иначе некоторые перелеты могут оказаться нереализуемыми.\n\nВместимость самолетов должна соответствовать планируемому пассажиропотоку: рейс выполняется минимум раз в неделю и максимум семь раз в неделю. Без должного разнообразия вместимостей все рейсы могут оказаться переполненными или недозаполненными.\n\n### Каков критерий выбора стран для демобазы?\n\nБыли выбраны страны ведущих разработчиков PostgreSQL (major contributors), а также несколько других стран, имеющих определенное отношение к PostgreSQL. \n\nВы можете добавить и другие страны (см. ниже).\n\n### Каков критерий выбора количества городов?\n\nКоличество городов в демобазе пропорционально квадрату суммы логарифмов площадей стран и численности их населения:\n\n$$ 0,5 ( log( площадь, км^2 ) + log( население, млн ) - 5 )^2 $$\n\nВы можете сделать другой выбор (см. ниже).\n\n### Как изменить маршрутную сеть в пределах существующих стран и городов?\n\nУстановите непустое значение пассажиропотока `traffic` для желаемых аэропортов в таблице `airports_data`. Эти аэропорты и будут использоваться в маршрутах.\n\n### Как добавить новую страну?\n\nПри добавлении новой страны недостаточно просто выбрать аэропорты в таблице `airports_data`. Потребуется также определить справочники имен для новой страны.\n\nВ настоящее время справочники генератора содержат имена, записанные латиницей, для следующих стран: Россия (`RU`), Китай (`CN`), Индия (`IN`), США (`US`), Канада (`CA`), Япония (`JP`), Франция (`FR`), Германия (`DE`), Италия (`IT`), Великобритания (`GB`), Чили (`CL`), Швеция (`SE`), Непал (`NP`), Финляндия (`FI`), Новая Зеландия (`NZ`), Австрия (`AT`) и Чехия (`CZ`).\n\nПолное имя пассажира формируется из имени (given name) и фамилии (family name), поэтому в качестве исходных данных необходимо иметь список имен и фамилий и их относительную частоту. В качестве такого списка можно, например, использовать данные переписи населения. В простом случае, если по правилам языка страны фамилия не склоняется в зависимости от рода имени (как, например, в германских и романских языках, таких как английский или французский), таких исходных данных будет достаточно. \n\nИмена необходимо добавить в таблицу `firstname`:\n* `country` — двухсимвольный код страны в соответствии с ISO 3166-1 alpha-2;\n* `name` — имя;\n* `qty` — целое число, отражающее популярность имени (например, зарегистрированное количество людей, носящих данное имя).\n\nВ столбец `grp` поместите значение `-`.\n\nФамилии добавляются в таблицу `lastnames` с той же структурой.\n\nЧтобы проверить результат, можно выполнить такой запрос, где `XX` — код страны:\n\n```sql\nCALL calc_names_cume_dist(); -- рассчитывает накопленную вероятность\nSELECT get_passenger_name('XX') FROM generate_series(1,10);\n```\n\nДля языков, в которых фамилии зависят от рода имени (например, славянские и балтийские языки, такие как русский), потребуется разделить все имена и все фамилии на мужские и женские с помощью столбца `grp`. В этот столбец можно поместить любое текстовое значение, но оно должно совпадать в обеих таблицах (например, можно использовать `m` и `f`).\n\nИмена, относящиеся как к мужским, так и к женским, должны быть помещены в обе группы (возможно, с разной популярностью).\n\nТот же принцип можно использовать для разделения и на другие группы, например, чтобы смоделировать этнические группы с обособленными системами имен.\n\nПри генерации имени сначала из всех групп выбирается случайная фамилия, а затем выбирается случайное имя из той группы, к которой принадлежит фамилия. При этом полные имена, в которых имя (given name) и фамилия (family name) одинаковы, не выбираются.\n\n### Как добавить новый аэропорт?\n\nДобавьте строку в таблицу `airports_data`. Для этого необходимо знать:\n* Код ИАТА;\n* Английское название аэропорта;\n* Английское название города;\n* Часовой пояс;\n* Координаты аэропорта (указываются в формате «долгота, широта»).\n\nПолной актуальной базы аэропортов нет в свободном доступе. Существует несколько открытых проектов ([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/) на официальном сайте ИАТА.\n\nНазвание аэропорта и города необходимо перевести на русский язык (и другие языки, если они должны поддерживаться настройкой многоязычности `bookings.lang`).\n\nЧтобы новая запись была согласована с существующими, стоит придерживаться некоторых правил.\n\nВ качестве города (`city`) выбирается город, который обслуживается этим аэропортом. Часто это ближайший к аэропорту крупный населенный пункт, а не тот пункт, в котором расположен аэропорт. Например, для IAR городом является Ярославль, а не село Туношна.\n\nОфициальное название аэропорта часто состоит из названия населенного пункта, в котором расположен аэропорт, посвящения какому-либо видному деятелю и других частей. Для краткости имя сокращается по следующим правилам:\n* Название города не повторяется в названии аэропорта (за исключением случаев, когда официальное название состоит только из названия города, например, AAC «El Arish»);\n* В качестве названия аэропорта используется либо название населенного пункта, где расположен аэропорт, либо имя человека, причем предпочтение отдается населенному пункту (например, SVO «Шереметьево имени А. С. Пушкина» сокращается до «Шереметьево»).\n* Если используется посвящение человеку, титулы и воинские звания отбрасываются (например, YAI «General Bernardo O'Higgins» сокращается до «Bernardo O'Higgins»);\n* Отбрасываются слова «международный», «региональный» и т. п. (например, ROA «Roanoke–Blacksburg Regional» сокращается до «Blacksburg», учитывая, что Roanoke — название города).\n* В русском языке промежуточные инициалы убираются (например, JFK «John F. Kennedy» переводится как «Джон Кеннеди»).\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpostgrespro%2Fdemodb","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpostgrespro%2Fdemodb","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpostgrespro%2Fdemodb/lists"}