2016-03-22 13 views
5

Ho creato uno script in Laravel che legge un file JSON riga per riga e importa il contenuto nel mio database.Come evitare l'esaurimento della memoria quando si inserisce un milione di righe in mysql con php

Tuttavia, durante l'esecuzione dello script, viene visualizzato un errore di memoria insufficiente dopo l'inserimento di circa 80.000 di record.

mmap() failed: [12] Cannot allocate memory 

mmap() failed: [12] Cannot allocate memory 
PHP Fatal error: Out of memory (allocated 421527552) (tried to allocate 12288 bytes) in /home/vagrant/Code/sandbox/vendor/laravel/framework/src/Illuminate/Database/Query/Builder.php on line 1758 

mmap() failed: [12] Cannot allocate memory 
PHP Fatal error: Out of memory (allocated 421527552) (tried to allocate 32768 bytes) in /home/vagrant/Code/sandbox/vendor/symfony/debug/Exception/FatalErrorException.php on line 1 

ho costruito una sorta di coda di fortuna ai soli commettere gli elementi raccolti ogni 100, ma questo fatto alcuna differenza.

Questo è ciò che la parte del mio codice che fa gli inserti assomiglia:

public function callback($json) { 

    if($json) { 

     $this->queue[] = [ 

      'type' => serialize($json['type']), 
      'properties' => serialize($json['properties']), 
      'geometry' => serialize($json['geometry']) 
     ]; 

     if (count($this->queue) == $this->queueLength) { 

      DB::table('features')->insert($this->queue); 

      $this->queue = []; 
     } 
    } 
} 

E 'gli inserti effettivi (DB::table('features')->insert($this->queue);) che stanno causando l'errore, se lascio quelli fuori posso perfettamente iterare su tutto linee e li echeggia senza problemi di prestazioni.

Immagino di poter allocare più memoria, ma dubito che questa sarebbe una soluzione perché sto cercando di inserire 3 milioni di record e al momento è già in corso dopo 80 K con 512 MB di memoria allocata. Inoltre, in realtà voglio eseguire questo script su un server a basso budget.

Il tempo necessario per l'esecuzione di questo script non è di alcuna preoccupazione, quindi se potessi in qualche modo rallentare l'inserimento di record in basso sarebbe una soluzione per la quale potrei accontentarmi.

+0

vincolato a overhead di laravel, quindi smetterei di usarlo per primo. –

+1

L'ultima volta che dovevo farlo, ho scritto uno script cron che girava ogni N ore e prendeva N records, aggiornando i record processati con una bandiera. Ogni tanto lo script falliva, ma si riavviava semplicemente selezionando i record non elaborati. Non è la soluzione migliore (misurando la possibile sovrapposizione, per esempio, sperando che qualcuno qui faccia luce su di essa.) Modifica: Dagon ha anche un punto, aumenterà significativamente la perfomance se la costruirai su misura .. –

+0

Potresti passare in un imposta il limite di N righe al tuo metodo callback() di qualsiasi importo tu * puoi * elaborare contemporaneamente.Alla fine del tuo metodo, reindirizza alla tua pagina con un aumento di N righe + N per elaborare il batch successivo nella tua coda ? – Landjea

risposta

0

Sulle impalcature la mia applicazione ho installato il pacchetto itsgoingd/clockwork. È un'abitudine in cui mi sono imbattuto perché è uno strumento così utile. (Raccomando a tutti gli sviluppatori di Laravel di leggerlo per verificarlo!)

Ovviamente questo pacchetto registra le query a scopo di debug e quindi, è stato il colpevole che ha consumato tutta la mia memoria.

La disabilitazione del pacchetto eliminando il riferimento al proprio fornitore di servizi nella mia configurazione ha risolto il problema.

Saluti a @ dbushy727 per avermi dato una spinta nella giusta direzione per capirlo.

0

Informazioni sul problema, penso che dovresti usare la coda messaggi (Gearman, Rabbit ...) per risolvere questo problema.

Scarica tutto il record e passa alla coda. La coda verrà elaborata una alla volta

4

Se si utilizza MySQL 5.7+, dispone di funzionalità per l'importazione di dati dal file JSON utilizzando LOAD DATA INFILE.

LOAD DATA INFILE 'afile.json' INTO TABLE atable (field1, field2, ....); 

Se si utilizza la versione di MySQL inferiore, è necessario convertire prima JSON in formato CSV. Ad esempio, utilizzando https://github.com/danmandle/JSON2CSV

LOAD DATA INFILE 'afile.csv' INTO TABLE atable (field1, field2, ....); 

See s' documentationLOAD DATA INFILE.

+0

Ehi, è qualcosa che non sapevo! Sfortunatamente ho bisogno di cambiare considerevolmente il modo in cui sono strutturati questi dati prima di inserirli nel mio database. – Vercoutere

+0

LOAD DATA INFILE è la soluzione migliore se si dispone di dati molto grandi da inserire contemporaneamente. Sarà molto più veloce di INSERT –

+0

Potrei provare questo a volte per curiosità. La mia attuale soluzione tuttavia è molto veloce! Occorrono circa 15 minuti per importare 3 milioni di file di cui sono soddisfatto. – Vercoutere

1

Verificare che la registrazione della query non sia attiva. Se la registrazione delle query è attiva, anche se stai cancellando $ this-> queue, il log delle query aumenta fino a raggiungere dimensioni ingestibili e il limite di memoria.

Per verificare se la registrazione di query è acceso o spento, utilizzare la facciata DB in questo modo:

DB::logging() 

Se questo restituisce true, allora la vostra registrazione è acceso, e questo è il vostro problema. Se questo restituisce false, allora è qualche altra configurazione che trattiene le tue query.

+0

La registrazione delle query è disattivata. Tuttavia, il tuo commento mi ha dato il suggerimento di cui avevo bisogno per trovare il problema come descritto nella mia risposta! – Vercoutere

Problemi correlati