Búsqueda de sitios web

node.js - Problemas de memoria en TypeORM, o simplemente yo llenándola


He escrito un script de inicialización de base de datos en TypeScript usando TypeORM. Y parece que he causado un problema de memoria, pero no puedo encontrar una forma de solucionarlo.

Actualmente, el script importa tres archivos: Perfiles (55 registros), Usuario (5306 registros), Inicios de sesión (1006909 registros)

Reescribí las llamadas de modo que, en todos los casos, el script creará un JSON con todas las actualizaciones y luego usará createQueryBuilder para ejecutar la actualización como se muestra a continuación:

getConnection()
                                    .createQueryBuilder()
                                    .insert()
                                    .into(EULogin)
                                    .values(loginChunk)
                                    .execute()
                                    .catch(error => console.log(error))

Funciona de maravilla con los primeros, pero cuando se trata del último (los 1,000,000 de registros), no coopera. Y me da un problema de memoria

INICIO DE SESIÓN: Comprometido 196000/1006909 (Tamaño del bloque: 500) en la base de datos en 155 MS INICIO DE SESIÓN: Comprometido 196500/1006909 (Tamaño del bloque: 500) en la base de datos en 823 MS

<--- Últimos pocos GC --->

[60698:0x110008000] 34328 ms: Recolección 1389.1 (1423.6) -> 1388.6 (1423.6) MB, 12.1/0.0 ms (promedio mu=0.104, mu actual=0.099) fallo de asignación [60698:0x110008000] 34339 ms: Recolección 1389.3 (1423.6) -> 1388.9 (1423.6) MB, 10.4/0.0 ms (promedio mu=0.104, mu actual=0.099) fallo de asignación [60698:0x110008000] 34361 ms: Recolección 1389.4 (1423.6) -> 1389.1 (1424.1) MB, 12.5/0.0 ms (promedio mu=0.104, mu actual=0.099) fallo de asignación

<--- Rastreo de pila de JS --->

==== Rastreo de pila de JS =========================================

0: ExitFrame [pc: 0x20ed7445be3d]
1: StubFrame [pc: 0x20ed7440d608]
2: StubFrame [pc: 0x20ed7502c4cc] Security context: 0x0fbfb411e6e9 <JSObject>
3: /* anonymous */(aka /* anonymous */) [0xfbf79c62a41] [/Users/bengtbjorkberg/Development/EUGrapherNode/node_modules/typeorm/query-builder/InsertQueryBuilder.js:348]

[bytecode=0xfbf93b851a1 offset=26](this=0x0fbfab4826f1 ,valueSet=0x0fbf8846ebc1 <Mapa de objetos=0xfbfbf7...

F

Proceso terminado con código de salida 134 (interrumpido por la señal 6: SIGABRT)

He intentado abrir la base de datos en modo sincrónico (no asíncrono). He intentado dividir la última actualización en tan solo 50 registros a la vez. Incluso intenté abrir y cerrar la base de datos por cada segmento, pero eso falló porque no pude lograr que se hiciera de forma sincrónica.

Abriendo la base de datos con:

createConnection().then(connection => {

Y abajo está el "Chunkloader"

.on('end', () => {
                            let compTime: number = new Date().getTime()
                            console.log("LOGIN: Entities read: " + loginReadCounter + " in " + new Date(compTime - loginStartTime).getMilliseconds() + " MS")
                            let currentChunk :number = 0;
                            let chunkSize : number = 500;
                            let loginChunk = [];
                            let loginStartChunkTime : number = compTime;
                            loginEntries.forEach(entry => {
                                loginChunk.push(entry);
                                currentChunk ++;
                                loginCommitCounter++;
                                if (currentChunk === chunkSize !! ){
                                    getConnection()
                                        .createQueryBuilder()
                                        .insert()
                                        .into(EULogin)
                                        .values(loginChunk)
                                        .execute()
                                        .catch(error => console.log(error))

                                    let compTime: number = new Date().getTime()
                                    console.log("LOGIN: Committed "  + loginCommitCounter + "/" + loginReadCounter + " (Chunk Size:" + loginChunk.length + ") to database in " + new Date(compTime - loginStartChunkTime).getMilliseconds() + " MS");
                                    currentChunk = 0;
                                    loginStartChunkTime = compTime;
                                    loginChunk = [];

                                }
                            });

¿Alguna idea?

========================== EDITAR SIGUIENDO BUENA ENTRADA ====================

Para intentar aclarar mi propia cabeza, lo moví a una función separada, logré que await funcionara, pero ¿cómo detengo que el proceso continúe después de la llamada? Porque await funciona dentro de createConnection, pero no funciona en createConnection, así que la función retornará de inmediato

function syncDataWrite(dbEntitiy, dataSet){
    console.log("DBLOADER Started for: " + dataSet.length);
    createConnection().then(async connection => {
        console.log("DBLOADER Connected!");
        const completion = await createQueryBuilder()
            .insert()
            .into(dbEntitiy)
            .values(dataSet)
            .execute()
            .catch(error => console.log(error))
            console.log("DBLOADER SQL uploaded")
    })
    // console.log(dbEntity);

}

Respuesta #1

Tu código sugiere que, mientras divides las cosas en partes, estás ejecutando todo en paralelo, lo cual probablemente no sea lo que quieres.

Te recomendaría que reescribieras esto para ponerlo correctamente en secuencia.

Lo más fácil, con diferencia, será cambiar a async/await y usar un bucle for normal. Suponiendo que .execute() devuelve una promesa, esto se vería algo así:

const connection = getConnection();
for (const entry of loginEntries) {
   // [snip]  
   await createQueryBuilder()
     .insert()
     .into(EULogin)
     .values(loginChunk)
     .execute()
   // [snip]
}

He eliminado gran parte de tu código, pero espero que esta configuración general todavía tenga sentido.

Es posible hacer esto sin async/await, pero va a verse mucho más complicado.

Editar basado en tu edición.

Aquí hay una versión reescrita de syncDataWrite:

async function syncDataWrite(dbEntitiy, dataSet){

    console.log("DBLOADER Started for: " + dataSet.length);
    const connection = await createConnection();
    console.log("DBLOADER Connected!");
    const completion = await createQueryBuilder()
       .insert()
       .into(dbEntitiy)
       .values(dataSet)
       .execute();

}

Tenga en cuenta que si utiliza syncDataWrite varias veces, una por cada fragmento, aún necesita await syncDataWrite cuando se llama.

Respuesta #2

Me tomó mucho tiempo solucionar este problema de rendimiento, al final del día lo logré haciendo que funcionara usando el repositorio, en lugar de queryBuilder.

Antes :

ActiveRentalEntity.createQueryBuilder('ar')
  .insert()
  .values(data)
  .execute()

Después de (usar el repositorio). No es por lo del chunk, intenté dividirlo manualmente usando el generador de consultas y obtuve el mismo problema, parece que el repositorio está mejor optimizado:

getRepository(ActiveRentalEntity).save(data, { chunk: 1000 });