Перейти к содержимому

Failover региона LLM-эндпоинта

Если вы держите один и тот же деплоймент модели в нескольких регионах (Azure OpenAI East US + West Europe, зеркальные эндпоинты у других провайдеров), клиент при сбое сам уводит трафик в живой регион. Работает — но незаметно. А знать о смене активного региона надо: у другого региона своя латентность, свои квоты, иногда другая версия модели. Молчаливый failover сегодня — это сюрприз с квотой завтра.

Ротатор помнит текущий активный регион; когда трафик уходит на другой — шлём push один раз на смену, а не на каждый запрос.

import os, json, requests
import openai
NOTIFLY_URL = os.environ["NOTIFLY_URL"]
NOTIFLY_TOKEN = os.environ["NOTIFLY_TOKEN"]
REGIONS = [ # порядок = приоритет
{"name": "eastus", "base": "https://my-eastus.openai.azure.com"},
{"name": "westeurope", "base": "https://my-westeu.openai.azure.com"},
]
STATE = os.path.expanduser("~/.active-region.json")
def notify(title, msg, prio):
requests.post(f"{NOTIFLY_URL}/message",
params={"token": NOTIFLY_TOKEN},
json={"title": title, "message": msg, "priority": prio},
timeout=5)
def _active():
if os.path.exists(STATE):
return json.load(open(STATE)).get("region")
return None
def _set_active(name, reason):
prev = _active()
if name != prev: # регион сменился — сообщаем один раз
json.dump({"region": name}, open(STATE, "w"))
if prev is not None:
notify(
f"🌍 Регион сменился: {prev}{name}",
f"Активный LLM-эндпоинт теперь {name}. Причина: {reason}. "
f"Проверьте квоты и версию модели в новом регионе.",
priority=7,
)
def chat(**kwargs):
"""Пробуем регионы по порядку; на первом живом фиксируем как активный."""
last_err = None
for reg in REGIONS:
try:
client = openai.AzureOpenAI(
azure_endpoint=reg["base"],
api_key=os.environ["AZURE_KEY"],
api_version="2024-10-21")
resp = client.chat.completions.create(**kwargs)
_set_active(reg["name"], reason="primary ok")
return resp
except Exception as e:
last_err = f"{reg['name']}: {type(e).__name__}"
continue
raise RuntimeError(f"все регионы недоступны ({last_err})")
resp = chat(model="gpt-4o-mini",
messages=[{"role": "user", "content": "ping"}])

Push идёт на смену состояния, а не на каждый запрос из нового региона — иначе после failover телефон разрывался бы. priority=7: не катастрофа (сервис работает), но вмешаться стоит — свериться с квотами нового региона.

Логика в коде видит только те регионы, куда вы реально ходите. Чтобы знать о здоровье всех регионов независимо от вашего трафика, повесьте на health-URL каждого по активному монитору. Тогда падение резервного региона вы увидите до того, как он понадобится под failover, а не в момент, когда основной уже лёг и резерв тоже мёртв.

Смена A→B заметна, а вот тихий возврат B→A часто теряют. Тот же _set_active уже ловит оба направления — просто убедитесь, что текст алёрта нейтрален («регион сменился»), и вы одинаково узнаете и об уходе, и о возвращении на основной регион.

  • откуда и куда ушёл трафик (eastus → westeurope);
  • причину переключения (5xx, таймаут, исчерпание квоты региона);
  • напоминание сверить квоту и версию модели в новом регионе;
  • метку времени — для дедупликации, если failover «дребезжит» A↔B.