Несколько городов на одном API
> Кратко. Один процесс API и общие воркеры обслуживают все города. Город запроса задаёт префикс пути /city/<код>/, базу — список TGIDCITYDBS. Схему базы города API доводит сам: после переконвертации из десктопа ничего не надо запускать вручную.
Два режима
| Режим | Как включается | Город запроса | База |
|---|---|---|---|
| Один город | TGID_CITY_DBS не задан |
TGID_CITY, иначе по имени базы: almatygid → almaty, almatygid_copy → almaty |
DB_NAME |
| Несколько городов | TGID_CITY_DBS=код=база,код=база,… |
префикс пути /city/<код>/ |
из списка |
Пример: TGID_CITY_DBS=almaty=almatygid,shatura=shatura. Код города — латиница, цифры, _ и -, от 2 до 31 знака.
Ошибка в списке (нет =, город указан дважды) останавливает запуск API с сообщением «TGID_CITY_DBS: …».
TGID_CITIES — справочник названий: код:Название[:путь веба], путь по умолчанию /tgid/<код>/. Без переменной
действует встроенный список из 12 городов (cities.DEFAULT_CITIES). Справочник нужен для выбора городов
у пользователя и для перехода между городами в вебе.
Город запроса
- nginx отдаёт API города по его пути:
location /tgid/<код>/map-api/→http://127.0.0.1:<порт>/city/<код>/. Веб и егоNUXT_PUBLIC_MAP_API_BASE_URLпри этом не меняются. - Слой
city_context.pyснимает префикс/city/<код>и запоминает город. Маршруты, ссылки OGC и проверки путей остаются прежними. - Без префикса отвечают только
/health,/api/health,/metricsи/auth/check. Остальное — 404 «Город не указан: путь API — /city/<код города>/…». Город не изTGID_CITY_DBS— 404 «Город «…» не обслуживается этим API». - Токен входа общий для всех городов. Доступ к городу (список городов пользователя) проверяется на каждом запросе.
Что разделено по городам
| Что | Как |
|---|---|
| Пул соединений | Свой пул на город (DB_POOL_MIN_SIZE…DB_POOL_MAX_SIZE). Недоступная при запуске база открывается при первом запросе |
| Кеши процесса (каталог, графы сети, колонки журналов, версия данных карты, охват OGC) | Ключ кеша — код города (cities.cache_key()); у API одного города — пустой |
| Очередь Celery | tgid.<код>; выгрузки с FILE_JOBS_QUEUE=<очередь> — <очередь>.<код>. Задача несёт заголовок tgid_city |
| Результат и файл задачи | Отдаются только запросу того же города |
| Доводка схемы базы | Своя фоновая задача и своё состояние на город |
| Функции тайлов martin | MARTIN_SQL_DIR/<код>.sql (у API одного города — MARTIN_SQL_FILE) |
| Статичные тайлы PMTiles | STATIC_TILES_DIR/<код>/ |
Общие для всех городов: UsersDB (пользователи и журнал действий), база Redis (REDIS_DB), справочник sprav.
Размер пула. DB_POOL_MAX_SIZE действует на каждый город в каждом процессе API. По умолчанию 20: при 12 городах
и двух процессах это до 480 соединений. Сверьте с max_connections PostgreSQL, на проде пул на город — 6.
Воркеры и очереди
- Воркер слушает очереди, перечисленные в
-Q. Общий воркер слушает все города (-Q tgid.almaty,tgid.astana,…). Для крупного города можно завести отдельный воркер (-Q tgid.almaty). - Перед задачей воркер берёт город из заголовка и открывает его базу. Задачу без известного города он не выполняет: это ошибка, а не расчёт на чужой базе.
- Задача в очереди города, которую никто не слушает, ждёт бесконечно. Добавили город — добавьте его очередь воркерам.
Как добавить город
- Создайте базу города конвертером десктопа (gid8). Проверьте, что в ней есть таблицы сети
nodes,linesobj,heatpipesectionsиexternalcodes. - Добавьте город в
TGID_CITY_DBSслужбы API, а если его нет во встроенном списке — и вTGID_CITIES. - Добавьте очередь
tgid.<код>в-Qворкеров. - Перезапустите API и воркеры. В ответе
/healthу города должно быть"ok": true, аcity_schema.status—okилиapplied. - Опубликуйте слои города в GeoServer: рабочая область
<Город>GIS, как у других городов. Для тайлов из martin сгенерируйте функции (scripts/martin/build_martin_functions.py) и положите их вMARTIN_SQL_DIR/<код>.sql. - Веб: служба на город с
NUXT_APP_BASE_URL=/tgid/<код>/,NUXT_PUBLIC_MAP_API_BASE_URL, охватомNUXT_PUBLIC_CITY_BBOX/NUXT_PUBLIC_MAP_CENTERи префиксомNUXT_PUBLIC_STORAGE_PREFIX. Без префикса города на одном домене делят настройки в браузере. - nginx: пути
/tgid/<код>/(веб) и/tgid/<код>/map-api/(API). Перед перезагрузкой проверьте конфигурациюnginx -t. - Дайте пользователям доступ к городу: «Пользователи» → города пользователя. Пустой список — все города.
- Добавьте базу в список резервного копирования — Резервные копии.
Если у города своя система координат (не 3857, как у последних городов Казахстана), API о ней не знает. Нужен
пункт в database/city_schema.py → CITIES и SQL с описанием SRID. Это правка кода, а не настройка.
Что API делает с базой города сам
База города приходит из десктопа и после каждой переконвертации теряет то, что добавил веб. API проверяет это при
запуске и затем раз в DB_UPKEEP_INTERVAL_S секунд (по умолчанию 600, 0 — выключить) и применяет только
недостающее. Каждый пункт применяется своим идемпотентным SQL из sql/, все вместе — одной транзакцией.
Что доводится:
- функции
tg_format*для слоёв GeoServer; - индексы:
nodes_internalnodeid_idx,ut_out_lineid_calculationid_idx, пространственные индексы зданий; - таблицы API: журнал отмены и повтора правок, протоколы расчёта, износ, шаблоны карточек, авторство объектов («только свои»), сценарии, конфигурации трассировки, стили слоёв;
- таблицы функций: наряды и фото, треки бригад, комментарии на карте, ключи встраивания, короткие ссылки, социальные объекты, отключения, вебхуки, присутствие на карте, формы обходов, телеметрия и тревоги, погода, водопровод, нормы сближений, глубина теплосети, прогноз аварийности, последствия аварий;
- колонка
generalizedconsumers.name; - Астана и Шатура — описание SRID 9998; Шатура — ещё колонка
linesobj.internalnodeidс триггером; - функции тайлов martin, если задан
MARTIN_SQL_FILEилиMARTIN_SQL_DIR.
Когда API ничего не трогает:
- база ещё не готова: нет таблиц сети или нужной системы координат (
not_ready); - в базе идёт запись: конвертер работает или висит транзакция (
busy); - другой процесс уже доводит эту базу (блокировка
pg_try_advisory_xact_lock).
Во всех этих случаях API повторит попытку в следующий проход. Если применить не удалось, транзакция откатывается,
база остаётся как была, а в /health у города city_schema.status = "error" с текстом ошибки.
UsersDB API сам не мигрирует: новые миграции alembic применяются при выкатке — Выкатка и откат.
Если не получается
- 404 «Город не указан» — запрос пришёл без
/city/<код>/. Проверьтеproxy_passв nginx: адрес должен заканчиваться на/city/<код>/. - Расчёт или выгрузка города «висит» в очереди — ни один воркер не слушает
tgid.<код>. city_schema.status: "busy"долго — в базе висит транзакция (idle in transaction) или идёт конвертация. Найдите её вpg_stat_activity.- Объекты города «уехали» — после переконвертации нет описания SRID 9998. API вернёт его в ближайший проход;
/healthпокажетapplied.
Для разработчика
- Город:
cities.current_city(); база —cities.city_db_name(), а неos.getenv("DB_NAME"). Новый кеш процесса, построенный по базе, — только с ключомcities.cache_key(), иначе данные одного города попадут в другой. - Фоновая работа вне запроса — внутри
with cities.use_city(код):; очередь задачи —cities.task_queue(). - Новый пункт схемы —
Itemвdatabase/city_schema.pyи SQL-файл вsql/, а не скрипт для администратора. - Подробности и проверка на стенде —
docs/multi-city.mdв репозитории API.
