Год назад ФНС прекратила выкладывать базу ФИАС в формате DBF, теперь база доступна только в формате XML. Данные из DBF можно было вставлять в базу как есть, с XML так не получается, перед вставкой в базу, XML надо парсить, и это занимает время. Полная база ФИАС занимает 290 GB, парсинг такого объёма занимает значительное время. Это время можно сократить, если парсинг распараллелить. Для этого надо разделить файлы на группы и каждую группу обрабатывать отдельно, но одновременно (процессоры давно многоядерные, надо не забывать этим пользоваться). Кроме того, конечно, надо использовать потоковый парсер, чтобы не ждать полной загрузки файла в оперативную память. Я разработал несколько скриптов для создания и импорта базы. Эта статья будет о том как с помощью этих скриптов развернуть базу ФИАС за 9 часов на 6-ти ядерном процессоре. Странно начинать с итогов, но это интересней чем вникать в подробности, поэтому сначала кратко, а потом всё более и более подробно. Скачать с сервера ФНС полную версию БД (https://fias.nalog.ru/Updates) 36 GB Развернуть архив, файлы с данными займут 300 GB Установить пакет для создания таблиц БД Установить пакет для импорта файлов Скопировать скрипты из исходников пакетов в отдельную директорию Разбить директории с данными регионов на равные по весу группы (каждая группа регионов будет импортироваться отдельным процессом PHP) Для каждой группы сделать копию скрипта импорта, в скрипте отредактировать список регионов в соответствии с группой Выполнить скрипт создания таблиц Запустить копии скриптов импорта в отдельных окнах терминала (параллельное выполнение) Выполнить скрипт создания индексов Have fun 01-22 регионы, 54 GB - Rows was read is 301 929 373 duration is 08:20:06 23-36, 59 GB - Rows was read is 326 217 126 duration is 08:51:45 37-52, 63 GB - Rows was read is 342 339 705 duration is 09:15:50 53-67, 57 GB - Rows was read is 315 815 574 duration is 08:36:36 68-99, 55 GB - Rows was read is 285 073 083 duration is 07:53:59 Дополнительно к этому, создание индексов, ещё час - Creation index duration is 01:05:17. Запускать создание индексов параллельно особого смысла не имеет, потому что загрузка диска была 100% пополам с промежутками загрузки на 5%, чтобы совсем не было простоя диска, можно запустить два обработчика на создание индексов. Это сэкономит минут 20. Не самый медленный диск, во время импорта его скорости вполне хватало, загрузка не превышала 50%. Когда я развернул базу на "быстром" NVMe диске, импорт занял 40 часов, на этот "медленный" диск база развернулась за 42:58:16, + 1 час на индексы, вместе 44 часа, это на четыре часа дольше, да, разница есть, но не драматичная. База без индексов занимает 190 GB, с индексами весит столько же сколько исходные файлы: 231 GB (248,504,378,147 bytes), надо сказать, что у меня сделан всего 1 индекс в каждой таблице, для работы с базой, мне кажется, надо минимум пять индексов. Загрузка процессора для пяти обработчиков упёрлась в потолок 100%, для четырёх - так же 100%, 3 - 85%, 2 - 55%, 1 - 35%. Памяти потребовалось: 8 Mb на процесс PHP и 4 Mb на процесс PostreSql, чтение с диска PHP - 2 Mb/s, PostreSql - 1.5 Mb/s. В следующий раз я разобью файлы (регионы) на 4 группы, и импорт буду делать в 4 потока, пять это перебор для моей машины. Про оперативную память нет смысла писать, потому что как видите, её объём не значителен. Настало время рассказать в подробностях. Устанавливаем пакет для журналирования, на свой вкус. Логи можно вообще не писать, тогда в скрипте в качестве логера надо использовать \Psr\Log\NullLogger . Копирование гладко не пройдёт, потому что в исходниках совпадают и файлы и директории, если будет предложено перезаписать файлы, то надо отказаться. Файл "config.env.example" нужен из исходников "fias-gar-data-import-tool". Проверьте, что директории скопировались вместе с файлами, в директории templates должно быть 28 директорий, и в каждой директории 2-4 файла. Открываем config.env, правим настройки. LOGIN PASSWORD DSN меняем под СУБД, в выбранной БД создаём схему для ФИАС, название схемы пишем в SCHEMA. У меня СУБД - Постгрес 14. СУБД должна поддерживать секционирование. Может быть MySQL тоже умеет, но я не уверен за правильность синтаксиса. Синтаксис придётся править в шаблонах, для каждой из 28-ми таблиц. В моих скриптах не используется ORM, поэтому извините, под вашу СУБД придётся поработать руками. DO_IMPORT_WITH_CHECK присваиваем FALSE - чтобы перед вставкой не проверять существование записи, это будет лишний SELECT. При импорте значение FALSE , при обновлении базы - TRUE . XML_FILES_PATH записываем путь к развёрнутому архиву ФИАС. BATCH_SIZE это количество записей до выполненияCOMMIT, устанавливаем на свой вкус, по умолчанию 100 000. Я ставил 1 000 000, в этом случае, пауза между камитами просто увеличилась в 10 раз, ускорения не заметил. Скрипт создания таблиц. Ниже создаётся логгер, запись будет и в файл и в консоль. Логи можно вообще не писать, тогда в скрипте в качестве логера надо использовать \Psr\Log\NullLogger . Настройку логгера надо повторить в каждом скрипте (три скрипта - три раза). Задаём путь к шаблонам SQL скриптов создания таблиц, директории перечисляем как элементы массива. Корневая директория задаётся при создании экземпляра $pathComposer = new Path(__DIR__);  Задаём список таблиц (имена папок с шаблонами скриптов для конкретной таблицы) В зависимости от того как вы собираетесь использовать базу, можно удалить создание каких то таблиц, например "*_PARAMS", "NORMDOCS", "CARPLACES", "ROOMS", "STEADS". Задаём таблицы для которых нужно сделать секционирование по региону (таблицы с колонкой REGION), файлы этих таблиц лежат в директории региона. Соответственно, удаляем таблицы которые были удалены из первого списка. Скрипт для импорта XML файлов в БД. Делаем пять копий (для параллельной обработки в пять потоков). В каждой копии скрипта импорта, настраиваем параметры выполнения команды импорта (класс $options = new ImportOptions()). Первый аргумент (логическая переменная) отвечает за выполнение проверки существования записи перед её добавлением. В скрипте значение этого аргумента задаётся переменной окружения DO_IMPORT_WITH_CHECK. Соответственно при разворачивании базы нам эта проверка не нужна, передаём значение false. При обновлении базы, значение должно быть true.  Второй аргумент (массив) это справочники. Справочники общие на все регионы, для них не надо создавать партиции, они импортируются один раз.Импорт справочников много времени не занимает, но при желании можно убрать из списка справочники которые вам не нужны. Таблицы справочников надо импортировать только один раз, их импортировать должен только один скрипт, в других скриптах этот список должен быть пустым. Третий аргумент (строка) определяет маску для выбора директорий с регионами. Соответственно делаем копии этого скрипта по количеству потоков обработки. У меня было пять копий, с такими значениями: Четвёртый аргумент (массив), это список таблиц для каждого региона, лишние можно удалить, для каждого региона в этих таблицах будут созданы партиции. Разбить регионы на группы можно любым образом, с учётом количества ядер у вашего сервера. Скрипт создания индексов. Снова редактируем список таблиц для которых будем создавать индексы. Какие индексы будут созданы можно посмотреть и переделать под себя в шаблонах. Сначала создаём таблицы. Перед этим, конечно, включаем OPCache и отключаем дебагер (у меня xDebug), всё это делается в php.ini. Запускам скрипт Через минуту схема БД будет готова Теперь в отдельных окнах терминала запускаем собственно импорт Аналогичным образом запускаем остальные копии скрипта импорта базы. Скрипт начнёт работу, в консоли будет написано: Каждые 100 000 (BATCH_SIZE) записей будет выводиться статистика: Ждём 8 часов :)) По завершению работы скрипта будет подведена статистика: Для ускорения можно создать две копии скрипта создания индексов. Как разделить список таблиц не подскажу, для этого надо из логов выписать время создания индексов для каждой из 28-ми таблиц, а потом разбить их на две примерно равные группы. Вы может попросить у меня логи, я с радостью поделюсь. Запускаем скрипт. Ждём час. Вот и сказочке конец, а кто слушал, молодец ! Обновление происходит аналогичным образом. В конфиге (config.env), в XML_FILES_PATH записываем путь к файлам обновления, меняем значение DO_IMPORT_WITH_CHECK на TRUE, запускаем скрипт, ждём, пользуемся. Импорт написан в декларативном стиле, каждая таблица объявлена как набор колонок. Для колонок определёно их поведение, при присвоении значения, при вставке новой записи, при обновлении ранее добавленной записи. Это позволяет на базе этого кода писать произвольные импорты, в любые таблицы, любых данных. Одно условие - значения для импорта, надо передавать как массив [имя колонки => значение колонки]. Пространство имён SbWereWolf\FiasGarDataImport\Import\Composition\Column содержит примеры классов колонок, SbWereWolf\FiasGarDataImport\Import\Processor - примеры таблиц. Изучайте, копируйте, распространяйте :) PgAdmin, загрузка по транзакциям, 5 скриптов импорта. Загрузка HDD, 5 скриптов импорта. Метрики процессов, 5 скриптов импорта. Загрузка HDD, 4 скрипта импорта. Метрики процессов, 3 скрипта импорта. Загрузка HDD, 3 скрипта импорта. Загрузка CPU, 3 скрипта импорта. Метрики процессов, 2 скрипта импорта. Метрики процессов, 1 скрипт импорта. Загрузка CPU, 1 скрипт импорта. Загрузка HDD, работает скрипт создания индексов в один поток. Учитывая белые провалы, можно запустить создание индексов в два потока, в два скрипта. CPU при этом не сильно нагружен. Что интересно, при создании индексов, сначала идёт чтение на скорости 100 MB/s, потом запись на той же скорости 100 MB/s.  программист эникейщик