Высокое потребление памяти при импорте скрипта

Я пытаюсь провести тестовый импорт из нашего существующего форума. Нам нужно импортировать около 25 миллионов сообщений (обычные посты и приватные переписки), поэтому для ускорения процесса я создал несколько копий скрипта импорта для параллельного запуска и разделил загрузку тем. Это работало нормально в течение нескольких дней, но со временем я заметил, что потребление памяти каждым процессом медленно растет и достигает примерно 2 ГБ. В итоге сервер исчерпал память и убил исходную базу данных MySQL примерно на отметке в 16 миллионов сообщений.

Я увеличил объем оперативной памяти системы с 24 ГБ до 32 ГБ, но теперь, когда я пытаюсь перезапустить даже один процесс импорта и продолжить с того места, где он остановился, этот процесс сразу же потребляет около 10 ГБ памяти, еще до начала импорта сообщений. Раньше я мог запускать 8 параллельных процессов импорта, а теперь в пул памяти большего размера помещается только 2. Почему существует такая огромная разница в потреблении памяти между чистой установкой и перезапуском импорта после сбоя? Есть ли способ уменьшить этот объем потребляемой памяти, чтобы снова ускорить процесс импорта? Сервер с 128–256 ГБ памяти будет чрезмерно дорогим (и не понадобится после завершения импорта), а работа всего с двумя процессами импорта означает, что весь процесс займет недели.

Звучит как регулярное выражение, застрявшее в цикле, или что-то в этом роде. Выводите отладочные сообщения и пропускайте проблемную строку при импорте.

Использование памяти, по-видимому, происходит исключительно на этапе «Загрузка существующих сообщений…» (или тем) при запуске скрипта импорта, а не во время самой обработки сообщений. Судя по всему, на этом этапе из базы данных извлекается информация о сообщениях и темах, и использование регулярных выражений здесь не должно применяться.

Переменные @posts и @topics, видимо, используются для таких задач, как метод “topic_lookup_from_imported_post_id”. Это логично, однако на начальном запуске скрипта использование памяти никогда не приближалось к тем значениям, которые я наблюдаю сейчас, при этом эти методы продолжают работать корректно.

Вы смотрели скрипты массового импорта? У них может быть меньший расход памяти.

Но действительно, скрипты импорта хранят в памяти карту соответствия старых идентификаторов пользователей, тем и сообщений новым, так что это требует значительного объёма оперативной памяти, особенно если вы планируете несколько копий.

Вы понимаете, что после запуска первоначального импорта вы запустите его снова для импорта только новых данных, и он выполнится гораздо быстрее, верно? Так что после того, как вы подождёте месяц для выполнения первоначального импорта, финальный займёт гораздо меньше времени.

Я не знаю о существовании скриптов пакетного импорта. Они где-то в директории import_scripts?

Да, именно здесь у меня возникают трудности. Во время первоначального импорта, когда работало 8 процессов импорта, всё было относительно нормально, пока система не исчерпала память. Теперь, когда я пытаюсь перезапустить процесс импорта и продолжить с того места, где он остановился, каждый процесс использует примерно в 5 раз больше памяти, чем при первом сбое.

Нам необходимо завершить полный импорт, чтобы провести надлежащее тестирование и сформировать ожидания относительно того, когда эта миграция может произойти в реальности. На данный момент у меня всё ещё нет чёткого понимания того, чего ожидать, например, в плане производительности. Кроме того, я заметил, что даже при отметке в 16 миллионов постов размер базы данных уже более чем на 50% превышает размер нашей текущей базы — это немного удивительно. Длительное время импорта не делает задачу невыполнимой, но было бы гораздо удобнее, если бы ожидания были сформулированы в днях, а не в неделях.

Смотрите: discourse/script/bulk_import at main · discourse/discourse · GitHub.

Для тем и сообщений запуск параллельных импортов в любом случае не совсем реалистичен, так как нельзя импортировать сообщение в теме, если сама тема и все предыдущие сообщения ещё не импортированы. Предположительно, можно было бы запустить параллельные процессы для пользователей и тем, но не для сообщений, если только не переписать скрипт так, чтобы он загружал все сообщения темы сразу, что позволило бы проводить параллельные импорты. Это, безусловно, выполнимо, но ни один из скриптов, которые я использовал, так не работает. Однако у вас всё равно останется проблема с хранением в оперативной памяти карты соответствия старых и новых ID.

Импорт 25 миллионов сообщений — это неудобно. :slight_smile:

Именно это я и делаю. Я разделил темы так, чтобы каждая тема обрабатывалась только одним процессом. Разделение не идеальное, но это в несколько раз быстрее, чем один линейный процесс.

Это самый запутанный момент. Я предполагаю, что это происходит по мере выполнения, и именно поэтому я наблюдал рост использования памяти каждым процессом в течение трёх дней. Перед потерей соединения с базой данных объём используемой памяти достигал 2–2,5 ГБ на каждый процесс.

Разве каждый процесс хранит карту только для сообщений, которые он уже импортировал? Если это так, то становится понятно, почему использование памяти резко возросло после перезапуска импорта.

Думаю, да. А остальные не будут работать корректно, так как у них нет тех дней, которые были импортированы другими процессами. Не думаю, что то, что вы делаете, сработает.

Вам нужно либо изучить скрипты пакетного импорта, либо переписать base.rb, чтобы отслеживать ссылки на идентификаторы импорта другим способом.

Вероятность того, что вы потратите больше недель на отладку своего кода, чем просто будете ждать, очень высока. Скорость одного процессора — лучший способ ускорить процесс.

Пока я не заметил никаких проблем с этим подходом, хотя могу представить, что некоторые сложности могут возникнуть с методами, которые выполняются после импорта поста. Вероятно, мне стоит убедиться, что всё останавливается после этого этапа, а затем запустить выполнение в однопоточном режиме, чтобы остальная часть была обработана корректно.

Тем не менее, ваше предложение по-другому обрабатывать ссылки на ID импорта, вероятно, является хорошим решением в любом случае. Хранение произвольного объёма данных в некоторых переменных на протяжении всего выполнения скрипта не очень эффективно.