Массовая вставка Doctrine - Как исправить «Недостаточно памяти» с помощью массовой вставки с помощью Doctrine / Symfony 4 - PullRequest
0 голосов
/ 16 мая 2019

Когда я пытаюсь получить метаданные от поставщика, я конвертирую данные в наш собственный формат метаданных. Но из-за огромного размера импортируемых данных приложение получает исключение OutOfMemoryException.

Я пробовал несколько вещей. Как загрузка памяти, которая может быть использована, а также я попытался использовать Doctrine Batch Processing , но с этим подходом есть небольшая проблема. Обработка данных доктрины основана на цикле for с индексацией.

$batchSize = 20;
for ($i = 1; $i <= 10000; ++$i) {
    $user = new CmsUser;
    $user->setStatus('user');
    $user->setUsername('user' . $i);
    $user->setName('Mr.Smith-' . $i);
    $em->persist($user);
    if (($i % $batchSize) === 0) {
        $em->flush();
        $em->clear(); // Detaches all objects from Doctrine!
    }
}
$em->flush(); //Persist objects that did not make up an entire batch
$em->clear();

Но данные, которые я импортирую, представляют собой многослойный массив, который я создал в трехмерном цикле 'foreach':

$this->index = 0;
$batchSize = 100;
foreach ($response as $item) {
    $item = new Item;
    $item->setName($item->name);
    $item->setStatus($item->status);
    $em->persist($item);
    if (($this->index % $batchSize) === 0) {
        $em->flush();
        $em->clear();
    }
    foreach ($item->category as $category) {
        $category = new Category;
        $category->setName($category->name);
        $category->setStatus($category->status);
        $em->persist($item);
        if (($this->index % $batchSize) === 0) {
            $em->flush();
            $em->clear();
        }
        foreach ($category->suppliers as $supplier) {
            $supplier = new Supplier;
            $supplier->setName($supplier->name);
            $supplier->setStatus($supplier->status);
            $em->persist($item);
            if (($this->index % $batchSize) === 0) {
                $em->flush();
                $em->clear();
            }
        }
    }
}
$this->em->flush();

Это вымышленный код, иллюстрирующий мою проблему. При этом приложение по-прежнему получает исключение OutOfMemoryException, и у меня возникает ощущение, что метод пакетной обработки работает неправильно.

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

1 Ответ

0 голосов
/ 17 мая 2019

Как вы написали свои вложенные циклы foreach, вы, очевидно, будете использовать ресурсы в геометрической прогрессии.Я также подозреваю, что он не достигнет того, чего вы действительно хотите, так как у вас будет МНОГО дубликатов Supplier с и Category с.

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

Мой подход к массовому импорту такой, как этот, - работать снизу вверх.В вашем случае это может быть вариант того, что я имею ниже.Предполагается, что у вас есть данные в существующей базе данных, и каждая существующая «сущность» в старой базе данных будет иметь свою собственную уникальную id.

1 - Импортировать всех поставщиков из старой базы данных в новую базу данных;в новом БД есть столбец с именем oldId, который ссылается на уникальный id из старого БД.Перестаньте очищать кэш / память.

2- Извлеките всех поставщиков из новой базы данных в массив, индексированный по их oldId.Я использую код так:

$suppliers = [];
$_suppliers = $this->em->getRepository(Supplier:class)->findAll();
foreach ($_suppliers as $supplier) {
    $suppliers[$supplier->getOldId()] = $supplier;
}

3- Повторите шаг 1 для категорий.Во время импорта ваша старая БД будет иметь ссылку на oldId связанных поставщиков.Хотя ваш код этого не делает, я предполагаю, что вы хотите сохранить связь между поставщиком и категорией, поэтому теперь вы можете ссылаться на поставщика по его oldId в цикле над связанными "старыми" поставщиками:

$category->addSupplier($suppliers[ <<oldSupplier Id>> ]);

4- Повторите выше для отдельных элементов, только на этот раз сохраняя связанные категории.

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

...