PmaControl logo PmaControl
  • Главная
  • PmaControl
    • ИИ-агенты 13 on-premise агентов
    • Тарифы Community, Cloud, On-Premise, Premium
    • Документация Руководства, API, архитектура
    • Клиенты 28+ компаний
    • FAQ 25 вопросов / 7 категорий
    Базы данных
    • MariaDB 30 статей
    • MySQL 10 статей
    • Galera Cluster 6 статей
    • MaxScale 3 статьи
    • ProxySQL 2 статьи
    • Amazon Aurora MySQL 0 статьи
    • Azure Database 0 статьи
    • ClickHouse 0 статьи
    • GCP CloudSQL 0 статьи
    • Percona Server 0 статьи
    • SingleStore 0 статьи
    • TiDB 0 статьи
    • Vitess 0 статьи
    Решения
    • Поддержка 24×7 Экстренная помощь MariaDB & MySQL
    • Observabilité SQL Мониторинг, алерты, топология
    • Haute disponibilité Репликация, failover, Galera
    • Disaster Recovery Backup, restore, RPO/RTO
    • Sécurité & conformité Аудит, GDPR, SOC2
    • Migration & upgrade Zero downtime, pt-osc, gh-ost
  • Тарифы
  • Ресурсы
    • Документация Технические руководства и API
    • FAQ 25 частых вопросов
    • Отзывы Отзывы клиентов и кейсы
    • Блог Статьи и аналитика
    • Roadmap Планируемые функции
    Области экспертизы
    • Observabilité SQL Мониторинг, алерты, топология Dot3
    • Haute disponibilité Репликация, failover, Galera
    • Sécurité & conformité Аудит, GDPR, SOC2, ISO 27001
    • Disaster Recovery Backup, restore, RPO/RTO
    • Performance & optimisation Digests, EXPLAIN, tuning
    • Migration & upgrade Zero downtime, pt-osc
    Быстрые ссылки
    • Wiki GitHub 26 страниц — установка, движок, плагины
    • Исходный код Официальный репозиторий GitHub
    • Поддержка 24×7 Экстренная помощь MariaDB & MySQL
    • Записаться на демо 30 мин — реальная архитектура
  • Поддержка 24×7
  • Записаться на демо
Записаться на демо
🇫🇷 FR Français 🇬🇧 EN English 🇵🇱 PL Polski 🇷🇺 RU Русский 🇨🇳 ZH 中文
← Вернуться в блог

GeoIP в PmaControl: разрешение IPv4 и IPv6 без чтения файла mmdb при каждом запросе

Опубликовано April 15, 2026 Автор Aurélien LEQUOY
pmacontrol geoip ipv6 mariadb performance architecture
Поделиться X LinkedIn Facebook Email PDF
GeoIP в PmaControl: разрешение IPv4 и IPv6 без чтения файла mmdb при каждом запросе

Проблема

Когда вы мониторите 100+ серверов MariaDB / MySQL, распределённых по нескольким дата-центрам и странам, при каждом отображении главной страницы возникает один и тот же вопрос: где находится этот сервер?

Классический ответ: открыть файл GeoLite2 .mmdb от MaxMind, выполнить lookup для каждого IP и показать флаг страны. Просто... но есть нюансы:

  • Открытие файла .mmdb размером 70 МБ при каждом HTTP-запросе обходится дорого
  • При 100 серверах — это 100 файловых lookup'ов на страницу
  • Файл представляет собой бинарное дерево, оптимизированное для последовательного чтения, а не для пакетного параллельного доступа
  • В PHP-FPM каждый worker перезагружает файл независимо

В PmaControl страница server/main обновляется каждую секунду через AJAX. Открывать .mmdb 100 раз в секунду — абсурд.

Решение: всё поместить в MariaDB

Идея проста: импортировать все диапазоны GeoLite2 в таблицу MariaDB, а затем делать обычные SQL-запросы. SELECT с индексом работает значительно быстрее, чем обход бинарного дерева на диске.

Схема

CREATE TABLE data_geoip (
  id INT AUTO_INCREMENT PRIMARY KEY,
  network_start VARBINARY(16) NOT NULL,
  network_end   VARBINARY(16) NOT NULL,
  country_iso   CHAR(2) NOT NULL DEFAULT '',
  country_name  VARCHAR(100) NOT NULL DEFAULT '',
  INDEX idx_network_start (network_start)
) ENGINE=InnoDB;

Выбор VARBINARY(16) имеет принципиальное значение:

  • 4 байта достаточно для IPv4 (32 бита)
  • 16 байт необходимо для IPv6 (128 бит)
  • VARBINARY(16) хранит оба формата единообразно
  • INET6_ATON() преобразует любой IP (v4 или v6) в сравнимое бинарное представление

Запрос lookup

SELECT country_iso FROM data_geoip
WHERE network_start <= INET6_ATON('89.30.104.134')
  AND network_end   >= INET6_ATON('89.30.104.134')
LIMIT 1;

Результат: FR (Франция). Время выполнения: < 1 мс.

Тот же запрос работает для IPv6:

SELECT country_iso FROM data_geoip
WHERE network_start <= INET6_ATON('2001:4860:4860::8888')
  AND network_end   >= INET6_ATON('2001:4860:4860::8888')
LIMIT 1;

Результат: US (Соединённые Штаты — это публичный DNS Google).

Импорт: обход адресного пространства

IPv4: от 0.0.0.0 до 255.255.255.255

Адресное пространство IPv4 составляет 2^32 = 4,3 миллиарда адресов. Перебирать их по одному не нужно — используется getWithPrefixLen() из reader'а MaxMind, который возвращает полный CIDR для каждого адреса:

$ip = 0;
while ($ip <= 4294967295) {
    [$record, $prefixLen] = $reader->getWithPrefixLen(long2ip($ip));

    // Вычислить конец сети
    $networkSize = 1 << (32 - $prefixLen);
    $networkEnd = $ip + $networkSize - 1;

    if ($record && !empty($record['country']['iso_code'])) {
        // INSERT в data_geoip
    }

    // Перейти к следующему блоку CIDR
    $ip = $networkEnd + 1;
}

Результат: ~650 000 диапазонов, импортированных за несколько секунд. Каждый диапазон покрывает целый блок CIDR (например, 89.30.104.0/22 — 1024 адреса в одной строке).

IPv6: пространство 2000::/3

Адресное пространство IPv6 составляет 2^128 адресов — перебирать его как IPv4 невозможно. Но публичные маршрутизируемые адреса находятся в блоке 2000::/3 (Global Unicast), а аллокации GeoIP обычно имеют размер от /32 до /48.

Принцип тот же: продвигаемся на размер возвращённого префикса. Разница в том, что работа идёт с бинарными адресами длиной 16 байт.

$current = inet_pton('2000::');
$end6    = inet_pton('3fff:ffff:ffff:ffff:ffff:ffff:ffff:ffff');

while ($current <= $end6) {
    [$record, $prefixLen] = $reader->getWithPrefixLen(inet_ntop($current));
    $endBin = binNetworkEnd($current, $prefixLen);

    if ($record && !empty($record['country']['iso_code'])) {
        // INSERT с UNHEX(bin2hex(...))
    }

    $current = binIncrement($endBin); // +1 в 128-битной арифметике
}

128-битная бинарная арифметика реализована на чистом PHP (без зависимости от GMP):

// Инкрементировать IPv6-адрес на 1
function binIncrement(string $bin): string|false
{
    $bytes = unpack('C16', $bin);
    for ($i = 15; $i >= 0; $i--) {
        $bytes[$i]++;
        if ($bytes[$i] <= 255) return pack('C16', ...$bytes);
        $bytes[$i] = 0; // carry
    }
    return false; // overflow
}

Результаты в продакшене

Объёмы

Таблица IPv4 IPv6 Итого
data_geoip (country) ~650K ~180K ~830K диапазонов
data_geoip_city (city) ~3.7M ~1.2M ~4.9M диапазонов

Производительность

Операция Время
Импорт country (IPv4 + IPv6) ~30 секунд
Импорт city (IPv4 + IPv6) ~5 минут
Lookup 1 IP < 1 мс
Lookup 100 IP (страница server/main) ~15 мс суммарно
Обновление AJAX (1 раз/секунду) пренебрежимо мало

Отображение

На главной странице PmaControl каждый сервер показывает эмодзи-флаг рядом с IP:

🇫🇷 89.30.104.134:3306    PIXID-MDB-MASTER1
🇩🇪 136.243.1.1:3306       Hetzner-Slave
🇯🇵 210.171.224.1:3306     NTT-Tokyo
🇺🇸 8.8.8.8:3306           Google-Test

Приватные IP-адреса (10.x, 172.16-31.x, 192.168.x, 127.x) не имеют записей в GeoLite2 — флаг просто отсутствует.

Обновление

MaxMind обновляет GeoLite2 каждую неделю. Для обновления:

# Скачать новый .mmdb в data/
# Затем запустить импорт:
php App/Webroot/index.php server loadGeoip       # country (~30с)
php App/Webroot/index.php server loadGeoipCity   # city (~5мин)

Импорт выполняет TRUNCATE, а затем вставляет всё заново. Diff или миграция не нужны — это одноразовый кеш.

Таблица city: расширенные возможности

Таблица data_geoip_city добавляет регион, город, GPS-координаты и часовой пояс:

SELECT country_iso, region_name, city, latitude, longitude, time_zone
FROM data_geoip_city
WHERE network_start <= INET6_ATON('136.243.1.1')
  AND network_end   >= INET6_ATON('136.243.1.1')
LIMIT 1;

Результат: DE | Saxony | Falkenstein | 50.4779 | 12.3713 | Europe/Berlin

Это открывает возможности для картографии серверов, определения задержек между дата-центрами или просто более информативного отображения в интерфейсе.

Почему не просто LEFT JOIN?

Можно было бы сделать LEFT JOIN data_geoip g ON g.network_start <= INET6_ATON(s.ip) AND g.network_end >= INET6_ATON(s.ip) прямо в запросе к серверам. Проблема: при 650K диапазонов такой range-join дорого обходится оптимизатору. Мы предпочитаем N отдельных lookup'ов (по 1 на уникальный IP), которые выполняются мгновенно благодаря индексу.

Заключение

Импортируя данные GeoLite2 в MariaDB, мы устраняем зависимость от файла .mmdb при каждом рендеринге страницы. Lookup превращается в индексированный SELECT за < 1 мс, совместимый с IPv4 и IPv6, а обновление сводится к еженедельному TRUNCATE + INSERT.

Исходный код доступен на GitHub — мы рады вашим контрибуциям.

Поделиться X LinkedIn Facebook Email PDF
← Вернуться в блог

Комментарии (0)

Комментариев пока нет.

Оставить комментарий

PmaControl
+33 6 63 28 27 47 contact@pmacontrol.com
Юридическая информация GitHub Контакты
Не ждите инцидента, чтобы понять свою архитектуру. © 2014-2026 PmaControl — 68Koncept