Реализация FreeSWITCH
Детальный план реализации FreeSWITCH (Слой 4: Media) — 8 фаз от базового звонка до WebRTC. Миграция с MirtaPBX (Asterisk). ~400 concurrent calls в EU, полный call-center.
Что такое FreeSWITCH
FreeSWITCH — это soft-switch (программный коммутатор). Если раньше телефонные станции были огромными шкафами с оборудованием, то FreeSWITCH — это то же самое, но в виде программы на Linux-сервере.
Представьте коммутатор из кинофильма — оператор втыкает провода, соединяя абонентов. FreeSWITCH делает то же самое, только программно и автоматически.
Open Source Написан на C Создан в 2006 Автор: Anthony Minessale (ex-Asterisk)
FreeSWITCH — это B2BUA. Он не просто пропускает звонок насквозь (как прокси). Он полностью разрывает звонок на две половинки:
Телефон A FreeSWITCH Телефон B │ │ │ │──── SIP Leg A ──────│ │ │ (отдельный звонок) │ │ │ │──── SIP Leg B ──────│ │ │ (отдельный звонок) │ │ │ │ │◄═══ Голос (RTP) ════►│◄═══ Голос (RTP) ════►│
Leg A — звонок между телефоном A и FreeSWITCH.
Leg B — звонок между FreeSWITCH и телефоном B.
Это даёт полный контроль: между двумя ногами FreeSWITCH может делать что угодно —
записывать, транскодировать, подмешивать аудио, ставить на удержание, переводить, подключать третьего участника.
Прокси (Kamailio) — это почтальон, который передаёт конверт не открывая. Быстро, массово, но не может изменить содержимое.
B2BUA (FreeSWITCH) — это секретарь, который читает письмо, может переписать, перенаправить, скопировать, добавить приложение.
┌──────────────────────────────────────────────────────────────┐ │ FreeSWITCH │ │ │ │ ┌────────────────────────────────────────────────────────┐ │ │ │ ЯДРО (Core) │ │ │ │ │ │ │ │ ┌──────────────┐ ┌──────────────┐ ┌────────────────┐ │ │ │ │ │ State Machine│ │ Event System │ │Channel Engine │ │ │ │ │ │ жизненный │ │ все события │ │ управление │ │ │ │ │ │ цикл канала │ │ pub/sub │ │ звонками │ │ │ │ │ └──────────────┘ └──────────────┘ │ Leg A ↔ Leg B │ │ │ │ │ └────────────────┘ │ │ │ │ ┌──────────────┐ ┌──────────────┐ ┌────────────────┐ │ │ │ │ │ Codec Engine │ │ Timer System │ │ Memory Pool │ │ │ │ │ │ перекодиров. │ │ │ │ управление │ │ │ │ │ │ Opus↔G.711 │ │ │ │ памятью │ │ │ │ │ └──────────────┘ └──────────────┘ └────────────────┘ │ │ │ └───────────────────────┬────────────────────────────────┘ │ │ │ Module API │ │ ┌───────────────────────┼────────────────────────────────┐ │ │ │ МОДУЛИ (~200) │ │ │ │ │ │ │ │ Endpoints Applications Codecs │ │ │ │ mod_sofia(SIP) mod_dptools mod_opus │ │ │ │ mod_verto(WS) mod_callcenter mod_g711 │ │ │ │ mod_skinny mod_conference mod_g729 │ │ │ │ mod_voicemail mod_g722 │ │ │ │ │ │ │ │ Events Languages Formats │ │ │ │ mod_event_socket mod_lua mod_shout(MP3) │ │ │ │ mod_json_cdr mod_python mod_sndfile(WAV) │ │ │ │ mod_v8(JS) │ │ │ │ │ │ │ │ Config TTS / ASR │ │ │ │ mod_xml_curl mod_flite │ │ │ │ mod_dialplan_xml mod_tts_commandline │ │ │ │ mod_dialplan_lua │ │ │ └─────────────────────────────────────────────────────────┘ │ │ │ │ ┌─────────────────────────────────────────────────────────┐ │ │ │ КОНФИГУРАЦИЯ │ │ │ │ Directory (кто) Dialplan (куда) SIP Profiles (как) │ │ │ └─────────────────────────────────────────────────────────┘ │ └──────────────────────────────────────────────────────────────┘
Написано на C. Это фундамент, который нельзя менять — на него нанизываются модули.
| Компонент | Что делает |
|---|---|
| Channel Engine | Управляет звонками. Каждый звонок = «канал» (channel) с уникальным UUID. Канал проходит состояния: NEW → INIT → ROUTING → EXECUTE → EXCHANGE_MEDIA → HANGUP |
| Event System | Внутренняя шина событий. Всё что происходит — звонок создан, DTMF нажата, запись начата — генерирует событие. ESL подписывается на эти события извне |
| State Machine | Жизненный цикл каждого канала. Чёткие переходы между состояниями. Гарантирует что ресурсы освобождаются при завершении звонка |
| Codec Engine | Перекодирование аудио на лету: Opus ↔ G.711 ↔ G.729. Если два телефона говорят на разных кодеках — ядро транскодирует прозрачно |
| Memory Pool | Управление памятью. Каждый звонок получает свой пул — когда звонок завершается, вся память освобождается одним блоком (нет утечек) |
Модули FreeSWITCH — подробная классификация
Endpoint-модули — как FreeSWITCH общается с внешним миром:
| Модуль | Протокол | Зачем |
|---|---|---|
mod_sofia | SIP (UDP/TCP/TLS/WSS) | Основной. 95% звонков идут через него |
mod_verto | WebSocket JSON-RPC | WebRTC из браузера (собственный протокол FS) |
mod_skinny | Cisco SCCP | Аппаратные Cisco-телефоны |
mod_freetdm | TDM / ISDN | Подключение к аналоговым линиям (legacy) |
Application-модули — что делать со звонком:
| Модуль | Зачем |
|---|---|
mod_dptools | Базовые инструменты: answer, bridge, playback, record, transfer, hold, park, intercept |
mod_callcenter | Очереди (ACD): стратегии распределения, агенты, тиры, музыка ожидания |
mod_conference | Конференц-звонки: комнаты, PIN, mute/unmute, запись |
mod_voicemail | Голосовая почта: greeting, хранение, уведомления, MWI |
mod_avmd | Определение автоответчика (Answering Machine Detection) |
mod_fifo | Простая парковка звонков (First In, First Out) |
Codec-модули — как кодировать голос:
| Модуль | Кодек | Битрейт | Когда |
|---|---|---|---|
mod_opus | Opus | 6-510 kbps (адаптивный) | WebRTC, внутренние звонки (лучшее качество) |
| встроен | G.711 (PCMU/PCMA) | 64 kbps | PSTN-транки, базовая совместимость |
mod_g729 | G.729 | 8 kbps | Экономия трафика (WAN, мобильные) |
mod_g722 | G.722 | 64 kbps | HD Voice (wideband) |
Language-модули — скриптовые языки для dialplan:
| Модуль | Язык | Для чего |
|---|---|---|
mod_lua | Lua | Рекомендуем Быстрый, встроенный, IVR-логика, HTTP-запросы из dialplan |
mod_python | Python | Сложная логика, ML/AI интеграции |
mod_v8 | JavaScript (V8) | Для тех кто знает JS |
Event/CDR-модули — связь с внешним миром:
| Модуль | Зачем |
|---|---|
mod_event_socket | ESL — Go подключается и управляет FS программно (порт 8021) |
mod_json_cdr | Отправляет CDR (детализацию) по HTTP в Go API после каждого звонка |
mod_xml_curl | FS запрашивает конфигурацию по HTTP у Go API (динамические пользователи, dialplan, очереди) |
| Раздел | Что определяет | Аналогия | Источник |
|---|---|---|---|
| Directory | Кто может звонить. Список SIP-аккаунтов с паролями и настройками | Телефонная книга компании | XML или mod_xml_curl (из БД) |
| Dialplan | Куда маршрутизировать звонок. Правила: «если набрали 1XXX → bridge к user/1XXX» | Правила коммутатора: «этот провод соединить с тем» | XML, Lua или mod_xml_curl |
| SIP Profiles | На каких IP/портах слушать SIP, какие кодеки, TLS, NAT | Какие «двери» открыты для звонков | XML (статический, меняется редко) |
Телефон A набирает 1002
│
▼
[1] CHANNEL_CREATE ← FS создаёт канал (Leg A), UUID: abc-123
│
▼
[2] ROUTING ← Ищет в Dialplan: что делать с номером 1002?
│ Нашёл: bridge user/1002
▼
[3] CHANNEL_CREATE ← Создаёт второй канал (Leg B), UUID: def-456
│ Звонит на телефон 1002
▼
[4] RINGING ← Телефон 1002 звонит (180 Ringing)
│
▼
[5] CHANNEL_ANSWER ← Телефон 1002 поднят (200 OK)
│
▼
[6] EXCHANGE_MEDIA ← Голос идёт: A → FS → B и B → FS → A
│ Здесь: запись, транскодирование, DTMF
│
│ ... разговор ...
│
▼
[7] CHANNEL_HANGUP ← Кто-то повесил трубку (BYE)
│
▼
[8] CDR ← Запись о звонке → Go API → PostgreSQL
│
▼
[9] DESTROY ← Каналы уничтожены, память освобождена
| FreeSWITCH ДЕЛАЕТ | FreeSWITCH НЕ ДЕЛАЕТ (это другие компоненты) |
|---|---|
| Принимает/делает SIP-звонки | Маршрутизация тысяч SIP-запросов → Kamailio |
| IVR, очереди, конференции | Веб-интерфейс → React |
| Запись звонков | REST API для пользователей → Go API |
| Транскодирование кодеков | Хранение данных → PostgreSQL / ClickHouse |
| DTMF-обработка | Балансировка нагрузки между серверами → Kamailio |
| Голосовая почта, TTS, ASR | NAT traversal для медиа → RTPEngine |
| Мост между двумя абонентами | Anti-DDoS, topology hiding → SBC (Kamailio) |
| Автообзвон (по команде из Go) | Бизнес-логика, кампании → Go API |
FreeSWITCH = медиа-сервер. Он обрабатывает звонки: принимает, играет аудио, записывает, соединяет, ставит в очередь.
Kamailio = SIP-прокси. Он маршрутизирует SIP быстро и массово, но не трогает медиа (голос).
Вместе: Kamailio решает куда направить звонок, FreeSWITCH решает что с ним делать.
Способы конфигурации FreeSWITCH (XML, HTTP API, Lua, ESL)
| Способ | Как работает | Для чего |
|---|---|---|
| Статический XML | Файлы на диске. Изменения → reloadxml в CLI |
SIP-профили, глобальные параметры — то, что меняется редко |
| mod_xml_curl Рекомендуем | FS запрашивает конфигурацию по HTTP у Go API. Go отвечает XML из PostgreSQL | Пользователи, очереди, dialplan — всё динамическое, из БД |
| mod_lua | Lua-скрипт в dialplan вместо XML. Может ходить в Redis, HTTP API | Сложная IVR-логика, маршрутизация по данным из БД |
| ESL (Event Socket) | Go подключается к FS (порт 8021) и управляет звонками программно | originate, transfer, hold, record — всё в реальном времени |
| mod_callcenter API | ESL-команды для управления очередями и агентами без перезагрузки | Добавить/удалить агента, сменить статус, создать очередь |
Для enterprise-системы: XML остаётся только для статики (SIP-профили, модули, ESL). Всё остальное — динамически через mod_xml_curl + Go API + PostgreSQL.
┌──────────────────────────────────────────────┐ │ Go API │ │ │ │ │ PostgreSQL (users, queues, │ │ dialplan rules, IVR trees) │ └─────────────┬──────────────┬─────────────────┘ │ │ ┌───────────┼──────────────┼──────────────┐ │ │ │ │ ▼ ▼ ▼ │ mod_xml_curl mod_lua ESL inbound │ «кто user? dialplan originate, │ какие логика на transfer, │ очереди?» Lua + HTTP uuid_kill... │ │ │ │ │ └───────────┼──────────────┘ │ │ │ FreeSWITCH │ │ │ Статический XML: │ • sip_profiles/internal.xml │ • modules.conf.xml │ • event_socket.conf.xml │ ┌───────────────────────────────────────────┘
Откуда: MirtaPBX (на базе Asterisk) — полный call-center: IVR, очереди, запись, автообзвон, супервизор, WebRTC.
Куда: FreeSWITCH — в составе enterprise-архитектуры (Kamailio SBC → Kamailio Core → FreeSWITCH → Go API).
Почему: масштабирование (43 страны), полный контроль, стоимость лицензий.
Подход: «снизу вверх» — на каждой фазе работающая система, постепенное наращивание функционала.
ФАЗА 1 ФАЗА 2 ФАЗА 3 ФАЗА 4 Базовый ESL IVR Очереди звонок управление меню ACD ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ mod_sofia│────▶│mod_event│────▶│ mod_lua │────▶│mod_call │ │ dialplan │ │ _socket │ │ DTMF │ │ center │ │ bridge │ │ Go ← FS │ │ TTS │ │ агенты │ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │ │ │ │ ▼ ▼ ▼ ▼ 2 телефона Go управляет Входящие Распределение звонят друг звонками попадают по операторам другу программно в меню автоматически ФАЗА 5 ФАЗА 6 ФАЗА 7 ФАЗА 8 Запись Конференции Автообзвон WebRTC + CDR + Voicemail (Dialer) + Видео ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │record_ │────▶│mod_conf │────▶│ Go ESL │────▶│mod_verto│ │ session │ │mod_voice│ │originate│ │ SIP.js │ │json_cdr │ │ mail │ │ AMD │ │ SRTP │ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │ │ │ │ ▼ ▼ ▼ ▼ Все звонки Комнаты с Обзвон Браузер = записываются PIN, голосовая списков телефон CDR → PostgreSQL почта автоматически оператора
Фаза 1: Установка + базовый звонок
Самый минимум: FreeSWITCH запущен, два устройства зарегистрированы, можно позвонить. Это фундамент, на котором строится всё остальное.
1. Docker-образ FreeSWITCH
Собираем из исходников (Debian-based) — чтобы включить только нужные модули. Ванильный пакет содержит ~200 модулей, нам нужно ~30.
2. SIP-профили (mod_sofia)
internal (порт 5060) — для внутренних устройств (телефоны, веб-клиенты).
external (порт 5080) — для SIP-транков (пока пустой, пригодится в будущем).
3. User Directory (XML)
Регистрация SIP-аккаунтов: 1001, 1002 — с паролями, доменом, настройками кодеков.
4. Dialplan (XML)
Минимальный: набираем 1001 → bridge к устройству 1001. Набираем 1002 → bridge к 1002.
5. Кодеки
G.711 (PCMU/PCMA) — базовый, работает везде. Opus — для будущего WebRTC.
| Модуль | Зачем |
|---|---|
mod_sofia | SIP-стек FreeSWITCH. Регистрация, приём/отправка SIP |
mod_dptools | Инструменты dialplan: bridge, answer, hangup, playback |
mod_dialplan_xml | Парсинг XML-диалплана |
mod_commands | CLI-команды (sofia status, show channels) |
mod_console | Консоль для отладки (fs_cli) |
mod_logfile | Логирование в файл |
Пример конфигурации: SIP-профиль + User + Dialplan
SIP-профиль internal (sip_profiles/internal.xml):
User Directory (directory/default/1001.xml):
Dialplan (dialplan/default.xml):
Установите MicroSIP (Windows) или Orologio (Mac). Зарегистрируйте 1001 и 1002. С 1001 наберите 1002 — должен пойти звонок. Если слышите голос — Фаза 1 готова.
Фаза 2: ESL — программное управление
ESL (Event Socket Library) — протокол FreeSWITCH для внешнего управления. Через него Go-backend может создавать звонки, слушать события, переводить, вешать трубку — всё программно, без изменения XML-конфигурации.
Inbound (основной) — Go подключается к FreeSWITCH на порт 8021. Может отправлять команды и подписываться на события. Один Go-сервис управляет всем FS.
Outbound — FreeSWITCH сам подключается к Go при входящем звонке. Каждый звонок = отдельное TCP-соединение. Используется для сложной логики dialplan на стороне Go.
Рекомендация Начинаем с Inbound — проще, Go всегда online, одно соединение.
| Команда | Что делает |
|---|---|
originate | Инициировать звонок программно (позвонить на номер) |
uuid_bridge | Соединить два активных канала |
uuid_transfer | Перевести звонок на другой номер/extension |
uuid_kill | Повесить трубку на конкретном канале |
uuid_hold | Поставить на удержание |
uuid_record | Начать/остановить запись |
uuid_setvar | Установить переменную на канале |
Go подписывается на события FreeSWITCH и реагирует в реальном времени:
| Событие | Когда | Что делаем в Go |
|---|---|---|
CHANNEL_CREATE | Новый звонок появился | Создаём запись в Redis (active call) |
CHANNEL_ANSWER | Абонент поднял трубку | Обновляем статус, начинаем таймер |
CHANNEL_HANGUP | Звонок завершён | CDR → PostgreSQL, очищаем Redis |
DTMF | Нажата клавиша | Обработка IVR, перевод по DTMF |
RECORD_START/STOP | Запись началась/кончилась | Метаданные записи → БД |
CUSTOM callcenter::info | Событие очереди | Обновляем dashboard супервизора |
Пример: Go ESL-клиент (inbound)
Конфигурация ESL + Lua в FreeSWITCH
Event Socket (autoload_configs/event_socket.conf.xml):
Lua-скрипт (scripts/incoming_call.lua) — пример сложной логики:
Go-сервис запускается, подключается к FS по ESL, вызывает originate — телефон 1001 звонит. Go получает события CHANNEL_CREATE → CHANNEL_ANSWER → CHANNEL_HANGUP. Если это работает — Фаза 2 готова.
Фаза 3: IVR (Interactive Voice Response)
IVR — это «голосовой робот», который отвечает: «Нажмите 1 для продаж, 2 для поддержки, 3 для бухгалтерии». В FreeSWITCH реализуется через Lua-скрипты + аудиофайлы + play_and_get_digits.
1. Аудиофайлы
Приветствие, пункты меню, ожидание, "Пожалуйста подождите", "Все операторы заняты". Формат: WAV 16kHz mono (для качества) или 8kHz (для совместимости).
2. IVR-дерево на Lua
Многоуровневое меню: Главное → Подменю → Действие. Вся логика в Lua-скриптах.
3. DTMF-обработка
RFC 2833 (основной), SIP INFO (fallback). play_and_get_digits — играет аудио и ждёт нажатия.
4. Таймауты и fallback
Не нажал ничего за 5 сек → повторяем. 3 неудачных попытки → перевод на оператора.
5. TTS (Text-to-Speech)
Для динамических фраз: "Ваш номер в очереди — пять". mod_flite (английский) или mod_tts_commandline + Piper (русский).
Входящий звонок
│
«Здравствуйте!»
«Нажмите 1, 2 или 3»
│
┌────────┼────────┐
│ │ │
[1] [2] [3]
Продажи Поддержка Бухгалтерия
│ │ │
│ ┌──┼──┐ │
│ [1] [2] │
│ Тех. Возврат │
│ отдел │
▼ ▼ ▼
Очередь Очередь Extension
sales support 3001
Пример: IVR на Lua
Звоним на номер 5000 (IVR) → слышим приветствие → нажимаем 1 → попадаем в очередь продаж. Нажимаем 2 → подменю поддержки. Ничего не нажимаем → через 3 попытки переводят на оператора. Если всё работает — Фаза 3 готова.
Фаза 4: Очереди (ACD — Automatic Call Distribution)
ACD — ядро call-центра. Клиент ждёт в очереди, система выбирает лучшего свободного оператора и соединяет. В FreeSWITCH — mod_callcenter.
| Стратегия | Как работает | Когда использовать |
|---|---|---|
| longest-idle-agent | Звонок идёт оператору, который дольше всех свободен | Самая справедливая. Рекомендуем |
| round-robin | По кругу: 1→2→3→1→2→3 | Равная загрузка |
| ring-all | Звонят всем одновременно, кто первый поднял | Маленькие команды |
| top-down | Сначала первому, если не ответил → второму | Приоритетные агенты |
| agent-with-least-talk-time | Кто меньше всего наговорил за смену | Равная нагрузка по минутам |
Очереди: sales, support, billing — каждая со своей стратегией, MOH, таймаутами.
Агенты: статусы — Available, On Break, Logged Out. Тиры (уровни приоритета): tier 1 звонят первыми, если все в tier 1 заняты → tier 2.
Музыка ожидания (MOH): mod_local_stream — разные плейлисты для разных очередей.
Announcements: «Вы 3-й в очереди», «Примерное время ожидания — 2 минуты».
Callback: клиент нажимает * → оставляет номер → система перезвонит когда оператор освободится.
Overflow: очередь переполнена (>20 ждущих) → перевод в другую очередь / voicemail / IVR.
Конфигурация mod_callcenter
Очередь (autoload_configs/callcenter.conf.xml):
Агенты:
Тиры:
ESL-события от очереди (Go получает в реальном времени):
Звоним на 5001 (очередь sales) → слышим музыку ожидания → «Вы 1-й в очереди» → телефон оператора 1001 звонит → оператор поднимает → разговор. Go получает все события через ESL. Если работает — Фаза 4 готова.
Фаза 5: Запись + CDR
Запись нужна для контроля качества, обучения, compliance (GDPR требует уведомлять). CDR — для аналитики: сколько звонков, средняя длительность, пропущенные, загрузка агентов.
record_session — записывает весь звонок от начала до конца, включая переводы.
Двуканальная запись (stereo): оператор в левом канале, клиент в правом. Это критично для speech analytics — позволяет анализировать каждого отдельно.
Формат: WAV (для качества) → конвертация в MP3 (mod_shout) для хранения. Или сразу MP3.
Хранение: локальный диск → Go API загружает в MinIO (S3). Путь в PostgreSQL.
Условная запись: только очереди, только внешние, по расписанию, по согласию клиента.
GDPR: уведомление «Разговор записывается» (playback перед записью), автоудаление через N дней.
mod_json_cdr — после каждого звонка FreeSWITCH отправляет JSON по HTTP POST в Go API.
Что содержит CDR:
caller_id кто звонил
destination куда звонил
start_time начало звонка
answer_time когда ответили
end_time конец звонка
duration общая длительность
billsec оплачиваемое время (от answer)
hangup_cause причина завершения
recording_path путь к записи
queue_name очередь
agent оператор
Go API → PostgreSQL → таблица cdr.
При 400+ concurrent calls CDR быстро растут до миллионов записей. PostgreSQL одна не справится с аналитическими запросами на таких объёмах. Решение — тройной стек: каждая БД делает то, в чём она лучшая.
FreeSWITCH │ │ JSON CDR (HTTP POST после каждого звонка) ▼ Go API ────────────────────────────────────────────── │ │ │ │ sync (мгновенно) │ async (батчами) │ async (батчами) ▼ ▼ ▼ ┌─────────────┐ ┌──────────────┐ ┌──────────────────┐ │ PostgreSQL │ │ ClickHouse │ │ Elasticsearch │ │ │ │ │ │ │ │ «Горячие» │ │ ВСЯ история │ │ Полнотекстовый │ │ последние │ │ за годы │ │ поиск │ │ 7-30 дней │ │ │ │ │ │ │ │ Аналитика: │ │ «Найди звонок │ │ JOIN с │ │ агрегации, │ │ с +4917...» │ │ users, │ │ отчёты, │ │ «Все звонки │ │ queues, │ │ дашборды │ │ агента Иванов» │ │ agents │ │ │ │ Fuzzy match │ │ │ │ Retention: │ │ │ │ TTL: 30 дней│ │ 3-5 лет │ │ TTL: 90 дней │ │ затем → │ │ сжатие 5-10x │ │ (горячий индекс) │ │ удаляется │ │ │ │ │ └─────────────┘ └──────────────┘ └──────────────────┘ │ │ │ ▼ ▼ ▼ Оперативная Grafana / Kibana / работа BI-отчёты поиск в UI
Роль: основная БД для «горячих» данных последних 7-30 дней. Здесь хранятся текущие смены, активные агенты, незакрытые звонки.
Почему PG: нужны JOIN-ы с таблицами users, queues, agents, tenants. Транзакции, целостность данных. ACID.
Что хранит:
cdr — CDR за последние 30 дней
active_calls — текущие звонки (Redis лучше, но PG как backup)
agent_sessions — рабочие смены агентов
queue_stats — текущая статистика очередей
TTL: cron-задача или pg_partman — автоудаление записей старше 30 дней (они уже в ClickHouse).
Оптимизации:
• Партиционирование по дате (1 партиция = 1 день)
• Индексы: caller_id, callee, start_time, agent_id, queue_name
• BRIN-индексы на start_time (эффективно для диапазонов дат)
Роль: вся история CDR за годы. Аналитические запросы, отчёты, дашборды.
Почему ClickHouse: колоночная СУБД — запросы вида «средняя длительность по очередям за Q4 2025» выполняются за 0.1-0.5 сек на 100M+ записей (PostgreSQL: 15-30 сек).
Что хранит: полную копию всех CDR за 3-5 лет. Сжатие 5-10x (100M CDR ≈ 8-15 GB).
Типичные запросы:
• «Все звонки за март в Германии» — 0.2 сек
• «Средняя длительность по очередям за квартал» — 0.1 сек
• «Top-10 агентов по количеству звонков» — 0.05 сек
• «Почасовая нагрузка за последний год» — 0.3 сек
• «Стоимость звонков по странам за месяц» — 0.1 сек
Запись: Go отправляет CDR батчами (каждые 5 сек или 1000 записей) через HTTP-интерфейс ClickHouse.
Роль: мгновенный поиск CDR по любому полю. Супервизор вводит номер телефона — за 50мс видит все звонки.
Почему Elastic:
• Full-text search — поиск по фрагменту номера: «+4917» → все звонки с номерами начинающимися на +4917
• Fuzzy matching — ошибся на цифру? Всё равно найдёт
• Агрегации в реальном времени — «сколько звонков за последний час по каждой очереди»
• Kibana — готовые дашборды без написания кода
Что индексируем:
caller_id callee agent_name queue_name hangup_cause direction country tenant_id
TTL: ILM-политика — горячий индекс (7 дней, SSD) → тёплый (30 дней) → удаление (90 дней). Для долгосрочного хранения — ClickHouse.
| Задача | Кто | Скорость |
|---|---|---|
| Текущие звонки агента | PostgreSQL | <10 мс |
| CDR с JOIN users/queues | PostgreSQL | <50 мс (30 дней) |
| «Все звонки за Q4 по Германии» | ClickHouse | 0.1-0.5 сек |
| «Среднее время ожидания за год» | ClickHouse | 0.05-0.2 сек |
| Ежемесячный отчёт (PDF) | ClickHouse | 1-3 сек |
| «Найди звонок с +4917612...» | Elasticsearch | <50 мс |
| «Все звонки агента Иванов» | Elasticsearch | <50 мс |
| Kibana дашборд (live) | Elasticsearch | real-time |
Go API: запись CDR в три хранилища
ClickHouse — схема таблицы CDR:
Elasticsearch — индекс CDR:
Примеры запросов: PostgreSQL vs ClickHouse vs Elasticsearch
«Текущие звонки агента 1001» → PostgreSQL (JOIN с users)
«Среднее время ожидания по очередям за Q4 2025» → ClickHouse
«Стоимость звонков по странам за январь» → ClickHouse
«Найди все звонки с номера +4917612...» → Elasticsearch
Шаг 1: Начинаем только с PostgreSQL (Фаза 5 базовая). Это работает сразу.
Шаг 2: Когда CDR перевалят за 1-5M записей и запросы начнут тормозить — добавляем ClickHouse. Go пишет в оба хранилища.
Шаг 3: Когда супервизорам понадобится мгновенный поиск и Kibana-дашборды — добавляем Elasticsearch.
Не нужно внедрять всё сразу. Каждый компонент добавляется когда появляется реальная потребность.
Фаза 6: Конференции + Voicemail
Конференции — для совещаний (несколько участников в одном звонке). Voicemail — когда оператор не ответил, клиент оставляет голосовое сообщение.
Создание комнат: набираем 8XXX → попадаем в конференцию 8XXX. Динамическое создание.
PIN-код: при входе вводится PIN для авторизации.
Управление: mute/unmute по DTMF (0 — mute себя), kick через ESL, запись конференции.
ESL: conference 8001 list — список участников. conference 8001 mute {member_id} — замутить.
Лимиты: максимум участников, автоокончание если остался 1.
Голосовая почта: если оператор не ответил за 20 секунд → «Оставьте сообщение после сигнала».
Greeting: персональное приветствие (запись своего) или стандартное.
Прослушивание: набираем *97 → вводим PIN → слушаем сообщения, удаляем, сохраняем.
Уведомление: email (SMTP) или webhook в Go API при новом сообщении.
MWI: индикатор на SIP-телефоне — мигающая лампочка «новое сообщение».
Фаза 7: Автообзвон (Dialer)
Вся логика дайлера — в Go, не в FreeSWITCH. FS только делает звонки по команде через ESL. Go управляет кампаниями, списками, стратегиями, пейсингом.
| Режим | Как работает | Когда | Ratio |
|---|---|---|---|
| Preview | Оператор видит карточку клиента на экране → сам нажимает «Позвонить» → Go отправляет ESL originate | Сложные продажи, VIP-клиенты. Оператор готовится к звонку | 1:1 |
| Progressive | Go ждёт свободного агента → originate на следующий номер из списка → при ответе bridge к агенту | Стандартный исходящий обзвон. Агент не простаивает, но и не перегружен | 1:1 |
| Predictive | Go оценивает average handle time + answer rate → originate заранее на N номеров одновременно → когда агент освободится, звонок уже ждёт | Массовый обзвон. Максимальная эффективность, но есть риск «брошенных звонков» (клиент поднял, а агент ещё не освободился) | 1.2–2:1 |
Campaign: название, список номеров, режим, расписание, Caller ID, trunk.
Pacing: сколько одновременных originate. Predictive подстраивает автоматически.
Retry: не ответил → повторить через 30 мин, максимум 3 попытки.
Blacklist / DNC: проверка номера перед звонком (Do Not Call).
Расписание: звоним только 9:00–18:00 по часовому поясу клиента.
Статистика: answer rate, average handle time, abandonment rate → dashboard.
mod_avmd — определяет, ответил человек или автоответчик.
Если автоответчик → оставляем голосовое сообщение (или вешаем трубку).
Если человек → bridge к оператору.
Точность: ~85-90%. Ложное срабатывание = потерянный клиент, поэтому лучше ошибиться в сторону «человек».
Альтернатива: не использовать AMD, а сразу bridge к агенту — агент сам разберётся. Проще, но агент тратит время на автоответчики.
Фаза 8: WebRTC + Видео
Финальная фаза — браузер становится полноценным телефоном. Оператору не нужен софтфон или аппаратный телефон — всё в веб-интерфейсе.
| Вариант A: mod_verto | Вариант B: SIP over WSS (через Kamailio) | |
|---|---|---|
| Как | Собственный протокол FreeSWITCH — JSON-RPC поверх WebSocket. Клиент: verto.js | Стандартный SIP через WebSocket (RFC 7118). Kamailio SBC терминирует WSS → UDP к FS. Клиент: SIP.js или JsSIP |
| Плюсы | Проще настроить. Прямое подключение к FS. Больше контроля | Стандартный протокол. Не привязан к FS. Kamailio SBC уже в архитектуре |
| Минусы | Привязка к FreeSWITCH. Нестандартный протокол | Сложнее: Kamailio WSS + RTPEngine + SRTP. Больше компонентов |
| Для нас | Быстрый старт, proof of concept | Production — SBC уже есть, стандартный SIP, масштабируемость |
Рекомендация Начать с mod_verto (быстрый proof of concept), потом перейти на SIP over WSS через Kamailio для production.
1. mod_verto — включаем, настраиваем WSS (порт 8082, TLS обязателен для браузеров).
2. Кодеки — Opus (голос, обязателен для WebRTC), VP8/VP9 (видео).
3. SRTP — обязательно для WebRTC (DTLS-SRTP). Браузеры не поддерживают обычный RTP.
4. React-интеграция — вебтелефон встроен в панель оператора: кнопки звонок/hold/transfer/mute, номеронабиратель, статус.
5. Видео — mod_conference с видео (VP8), screen sharing через extended SDP.
Открываем React-панель → нажимаем «Позвонить» на 1002 → браузер использует микрофон → звонок проходит → голос слышен в обе стороны. Если работает — все 8 фаз FreeSWITCH реализованы.
| Фаза | Модули | Результат |
|---|---|---|
| 1 | mod_sofia, mod_dptools, mod_dialplan_xml, mod_commands | Два телефона звонят друг другу |
| 2 | mod_event_socket, mod_lua, mod_json_cdr | Go управляет звонками через ESL |
| 3 | mod_lua, mod_flite, mod_tts_commandline, mod_say_ru | Входящие попадают в голосовое меню |
| 4 | mod_callcenter, mod_local_stream | Звонки распределяются по операторам |
| 5 | mod_dptools (record_session), mod_json_cdr, mod_shout | Запись + CDR в PostgreSQL |
| 6 | mod_conference, mod_voicemail | Конференции и голосовая почта |
| 7 | mod_avmd + Go ESL | Автообзвон списков |
| 8 | mod_verto / mod_sofia (WSS), mod_opus, mod_vp8 | Браузер = телефон |
После Фазы 1 — уже можно звонить. После Фазы 4 — уже полноценный call-center. После Фазы 8 — полный паритет с MirtaPBX (и больше). Миграцию пользователей можно начинать уже после Фазы 4-5.