2011-04-07 13 views
5

Estamos evaluando el uso de PostgreSQL para implementar una base de datos multiusuario, Actualmente se están ejecutando algunas pruebas en modelo de una sola base de datos de múltiples esquema (básicamente, todos los inquilinos tienen el mismo conjunto de objetos de bases de datos bajo su propio esquema dentro de la misma base de datos). La aplicación mantendrá un grupo de conexiones que se compartirá entre todos los arrendatarios/esquemas.proceso en segundo plano PostgreSQL problema de uso de memoria alta

p. Ej. Si la base de datos tiene 500 inquilinos/esquemas y cada inquilino tiene 200 tablas/vistas, el número total de tablas/vistas será de 500 * 200 = 100,000.

Dado que todos los inquilinos utilizarán el conjunto de conexiones, eventualmente cada conexión afectará a todas las tablas/vistas.

En nuestras pruebas, cuando la conexión alcanza más vistas, encontramos que el uso de la memoria del proceso de back-end aumenta bastante rápido y la mayoría de ellas son memorias privadas. Esa memoria se mantendrá hasta que se cierre la conexión.

Tenemos un caso de prueba de que un proceso de back-end usa más memoria de 30GB y eventualmente obtiene un error de falta de memoria.

Para ayudar a entender el problema, que escribió el código para crear un caso de prueba simplificados - MTDB_destroy: se utiliza para limpiar los esquemas de inquilinos - MTDB_Initialize: se utiliza para crear un multitenant DB - MTDB_RunTests: caso de prueba simplificado, básicamente, de entre todo el inquilino ve uno por uno.

Las pruebas que he hecho fue en PostgreSQL 9.0.3 en CentOS 5.4

Para asegurarse de que tengo un medio ambiente limpio, me vuelven a crear clúster base de datos y configuraciones dejo mayoritarios como predeterminado, (la única cosa Tengo que cambiar es aumentar "max_locks_per_transaction", ya que MTDB_destroy tiene que caer muchos objetos)

Esto es lo que hago para reproducir el problema:.

  1. crear una nueva base de datos
  2. crear las tres funciones utilizando el código adjunto
  3. conectar con el nuevo creado db y ejecutar los scripts de inicialización

    - Inicializar

    seleccione MTDB_Initialize ('inquilino', 100, 100, true);

    - no estoy seguro si el vacío es útil analizar aquí, acabo de funciono

    vacío analizar;

    - comprobar las tablas/vistas creadas

    seleccione esquema_tabla, table_type, count (*) de INFORMATION_SCHEMA.tablas donde table_schema como 'tenant%' group por table_schema, table_type order por table_schema, table_type;

  4. abrir otra conexión a la nueva db creado y ejecutar los scripts de prueba

    - obtener identificador de proceso de back-end para la conexión actual

    pg_backend_pid SELECT();

    - abrir una consola de Linux y ejecutar -p ps y ver VIRT, RES y SHR

    - ejecutar pruebas

    seleccione MTDB_RunTests ('inquilino', 1);

Observaciones:

  1. cuando se creó primero la conexión para la ejecución de pruebas,

    VIRT = 182MB, RES = 6240K, SHR = 4648K

  2. después de ejecutar las pruebas una vez, (tomó 175 segundos)

    VIRT = 1661MB RES = 1,5GB SHR = 55MB

  3. re-ejecutar la prueba de nuevo (tomó 167 segundos)

    VIRT = 1661MB RES = 1,5 GB SHR = 55MB

  4. re-ejecutar la prueba de nuevo (tomó 165 segundos)

    VIRT = 1661MB RES = 1,5 GB SHR = 55MB

a medida que ampliar el número de mesas, los usos de memoria subir en las pruebas también.

¿Alguien puede ayudarme a explicar lo que está sucediendo aquí? ¿Hay alguna manera de controlar el uso de memoria del proceso de back-end de PostgreSQL?

Gracias.

Samuel

-- MTDB_destroy 
create or replace function MTDB_destroy (schemaNamePrefix varchar(100)) 
returns int as $$ 
declare 
    curs1 cursor(prefix varchar) is select schema_name from information_schema.schemata where schema_name like prefix || '%'; 
    schemaName varchar(100); 
    count integer; 
begin 
    count := 0; 
    open curs1(schemaNamePrefix); 
    loop 
     fetch curs1 into schemaName; 
     if not found then exit; end if;   
     count := count + 1; 
     execute 'drop schema ' || schemaName || ' cascade;'; 
    end loop; 
    close curs1; 
    return count; 
end $$ language plpgsql; 

-- MTDB_Initialize 
create or replace function MTDB_Initialize (schemaNamePrefix varchar(100), numberOfSchemas integer, numberOfTablesPerSchema integer, createViewForEachTable boolean) 
returns integer as $$ 
declare 
    currentSchemaId integer; 
    currentTableId integer; 
    currentSchemaName varchar(100); 
    currentTableName varchar(100); 
    currentViewName varchar(100); 
    count integer; 
begin 
    -- clear 
    perform MTDB_Destroy(schemaNamePrefix); 

    count := 0; 
    currentSchemaId := 1; 
    loop 
     currentSchemaName := schemaNamePrefix || ltrim(currentSchemaId::varchar(10)); 
     execute 'create schema ' || currentSchemaName; 

     currentTableId := 1; 
     loop 
     currentTableName := currentSchemaName || '.' || 'table' || ltrim(currentTableId::varchar(10)); 
     execute 'create table ' || currentTableName || ' (f1 integer, f2 integer, f3 varchar(100), f4 varchar(100), f5 varchar(100), f6 varchar(100), f7 boolean, f8 boolean, f9 integer, f10 integer)'; 
     if (createViewForEachTable = true) then 
      currentViewName := currentSchemaName || '.' || 'view' || ltrim(currentTableId::varchar(10)); 
      execute 'create view ' || currentViewName || ' as ' || 
        'select t1.* from ' || currentTableName || ' t1 ' || 
      ' inner join ' || currentTableName || ' t2 on (t1.f1 = t2.f1) ' || 
      ' inner join ' || currentTableName || ' t3 on (t2.f2 = t3.f2) ' || 
      ' inner join ' || currentTableName || ' t4 on (t3.f3 = t4.f3) ' || 
      ' inner join ' || currentTableName || ' t5 on (t4.f4 = t5.f4) ' || 
      ' inner join ' || currentTableName || ' t6 on (t5.f5 = t6.f5) ' || 
      ' inner join ' || currentTableName || ' t7 on (t6.f6 = t7.f6) ' || 
      ' inner join ' || currentTableName || ' t8 on (t7.f7 = t8.f7) ' || 
      ' inner join ' || currentTableName || ' t9 on (t8.f8 = t9.f8) ' || 
      ' inner join ' || currentTableName || ' t10 on (t9.f9 = t10.f9) ';      
     end if; 
     currentTableId := currentTableId + 1; 
     count := count + 1; 
     if (currentTableId > numberOfTablesPerSchema) then exit; end if; 
     end loop; 

     currentSchemaId := currentSchemaId + 1; 
     if (currentSchemaId > numberOfSchemas) then exit; end if;  
    end loop; 
    return count; 
END $$ language plpgsql; 

-- MTDB_RunTests 
create or replace function MTDB_RunTests(schemaNamePrefix varchar(100), rounds integer) 
returns integer as $$ 
declare 
    curs1 cursor(prefix varchar) is select table_schema || '.' || table_name from information_schema.tables where table_schema like prefix || '%' and table_type = 'VIEW'; 
    currentViewName varchar(100); 
    count integer; 
begin 
    count := 0; 
    loop 
     rounds := rounds - 1; 
     if (rounds < 0) then exit; end if; 

     open curs1(schemaNamePrefix); 
     loop 
     fetch curs1 into currentViewName; 
     if not found then exit; end if; 
     execute 'select * from ' || currentViewName; 
     count := count + 1; 
     end loop; 
     close curs1; 
    end loop; 
    return count; 
end $$ language plpgsql; 
+0

¿Entiendo correctamente que el uso de la memoria solo aumenta mientras mantenga una conexión abierta? ¿No podría simplemente cerrar y volver a abrir la conexión cada 'n' veces que cambie de esquema para evitar esto? –

+0

Sí, si cerramos la conexión, la memoria asignada al proceso vuelve de nuevo a os. Sin embargo, eso tampoco es ideal. La idea del grupo de conexiones es reducir el número de conexiones simultáneas al servidor db y eliminar el costo de conexión/desconexión (que puede ser considerable si tenemos que hacerlo para cada solicitud) – Samuel

Respuesta

2

¿Son estas conexiones inactivas en la transacción o simplemente ociosos? Parece que las transacciones sin terminar se están guardando en la memoria, o tal vez tienes una pérdida de memoria o algo así.

+0

El caso de prueba aquí es enviar consultas a través de la misma conexión única a través del período de prueba. – Samuel

+0

no hay ninguna transacción explícita aquí, por lo que está ejecutando el modo de transacción implícita, es decir, no debería haber problemas de transacción abierta aquí. – Samuel

+0

no parece que tenga pérdidas de memoria, ya que el uso de la memoria no siguió creciendo si volvíamos a ejecutar la prueba una y otra vez. Se siente más como un tipo de almacenamiento en caché, solo que no sé si hay una forma de controlar el uso total de la memoria para evitar que consuma toda la memoria (física + virtual) en el servidor de db. – Samuel

1

Para las personas que ven este hilo cuando buscan (como yo lo hice), encontré lo que parecía ser el mismo problema en un contexto diferente. Los procesos inactivos consumen lentamente más y más memoria hasta que el asesino OOM los elimina (lo que provoca bloqueos periódicos de DB).

Hemos remontado el problema a secuencias de comandos PHP realmente largas que mantienen una conexión abierta durante mucho tiempo. Pudimos controlar la memoria cerrando periódicamente la conexión y reconectando.

De lo que he leído postgres hace un montón de almacenamiento en caché, por lo que si tiene una sesión golpeando una gran cantidad de tablas/consultas diferentes, estos datos de caché pueden seguir creciendo y creciendo.

-Ken

Cuestiones relacionadas