2008-09-25 13 views
5

Ahora tengo un analizador de registro que lee 515 mb de archivos de texto sin formato (un archivo por cada día de los últimos 4 años). Mi código actualmente se encuentra así: http://gist.github.com/12978. He usado psyco (como se ve en el código) y también estoy compilando y usando la versión compilada. Está haciendo aproximadamente 100 líneas cada 0.3 segundos. La máquina es un (C2D 2,4 GHz, 2 GB de RAM) 15" MacBook Pro estándar¿Cómo se hace Python/PostgreSQL más rápido?

¿Es posible que esto vaya más rápido o que es una limitación en el idioma/base de datos?

Respuesta

6

No pierda el perfil del tiempo. El tiempo siempre está en las operaciones de la base de datos. Haz lo menos posible. Solo el número mínimo de inserciones.

Tres cosas.

Uno. No SELECCIONE una y otra vez para conformar las dimensiones de Fecha, Nombre de host y Persona. Obtenga todos los datos UNA VEZ en un diccionario de Python y úselos en la memoria. No haga repetidas selecciones de singleton. Utiliza Python.

Dos. No actualizar

Específicamente, no hagas esto. Es un código malo por dos razones.

cursor.execute("UPDATE people SET chats_count = chats_count + 1 WHERE id = '%s'" % person_id) 

Se sustituirá por un simple SELECT COUNT (*) FROM .... Nunca actualice para incrementar un conteo. Simplemente cuente las filas que están allí con una instrucción SELECT. [Si no puede hacer esto con un simple SELECT COUNT o SELECT COUNT (DISTINCT), le faltan algunos datos; su modelo de datos siempre debe proporcionar recuentos completos correctos. Nunca actualizar.]

Y. Nunca construyas SQL usando la sustitución de cadenas. Completamente tonto.

Si, por algún motivo, el SELECT COUNT(*) no es lo suficientemente rápido (en primer lugar, como punto de referencia, antes de hacer cualquier cosa) puede almacenar en caché el resultado del recuento en otra tabla. DESPUÉS de todas las cargas. Haz un SELECT COUNT(*) FROM whatever GROUP BY whatever e inserta esto en una tabla de conteos. No actualizar Nunca.

Tres. Usar variables de vinculación. Siempre.

cursor.execute("INSERT INTO ... VALUES(%(x)s, %(y)s, %(z)s)", {'x':person_id, 'y':time_to_string(time), 'z':channel,}) 

El SQL nunca cambia. Los valores vinculados en cambio, pero el SQL nunca cambia. Esto es MUCHO más rápido. Nunca construyas declaraciones SQL dinámicamente. Nunca.

+0

La actualización se ha realizado porque más tarde on (en una aplicación de Ruby on Rails) Quiero encontrar al instante cuántas líneas tiene un usuario. Este número teóricamente nunca debería estar equivocado, y será más rápido. La sintaxis de la variable de vinculación es incorrecta (por lo que parece para mi psycopg2), ¿obtuve algo que funcionaría? –

+0

¿Has leído mi comentario en mi otra respuesta sobre la sintaxis de la variable de enlace específico de psycopg2? –

+0

Un recuento selectivo (*) en una tabla que tiene un índice (cualquier índice debería ser suficiente) es una operación muy rápida ya que la cantidad de elementos se almacena directamente en el índice. Seleccionar un recuento actualizado previamente no debería ser más rápido. – gooli

3

variables de uso se unen en lugar de literal valores en las sentencias SQL y crean un cursor para cada instrucción SQL única por lo que la declaración no tiene por qué ser reparsed la próxima vez que se usa Desde el doc pitón db api:.

Prepare and execute a database operation (query or command). Parameters may be provided as sequence or mapping and will be bound to variables in the operation. Variables are specified in a database-specific notation (see the module's paramstyle attribute for details). [5]

A reference to the operation will be retained by the cursor. If the same operation object is passed in again, then the cursor can optimize its behavior. This is most effective for algorithms where the same operation is used, but different parameters are bound to it (many times).

SIEMPRE SIEMPRE SIEMPRE use vincular variables.

3

En el bucle for, está insertando en la tabla 'chats' repetidamente, por lo que solo necesita una única instrucción sql con variables de vinculación, para ser ejecutada con diferentes valores. Por lo que podría poner esto antes de que el bucle for:

insert_statement=""" 
    INSERT INTO chats(person_id, message_type, created_at, channel) 
    VALUES(:person_id,:message_type,:created_at,:channel) 
""" 

Luego, en lugar de cada instrucción SQL se ejecuta a poner esto en su lugar:

cursor.execute(insert_statement, person_id='person',message_type='msg',created_at=some_date, channel=3) 

Esto hará que las cosas funcionen más rápido debido a que:

  1. El objeto cursor no tendrá que volver a analizar la instrucción cada vez
  2. El servidor db no tendrá que generar un nuevo plan de ejecución ya que puede usar el que crear previamente
  3. No tendrá que llamar a santitize() ya que los caracteres especiales en las variables de vinculación no formarán parte de la instrucción sql que se ejecuta.

Nota: La sintaxis de la variable de vinculación que utilicé es específica de Oracle. Deberá verificar la documentación de la biblioteca psycopg2 para conocer la sintaxis exacta.

Otras optimizaciones:

  1. Estás de incremento con el "pueblo de actualización del conjunto chatscount" después de cada iteración del bucle. Mantenga un usuario de mapeo de diccionarios en chat_count y luego ejecute la declaración del número total que ha visto. Esto será más rápido que golpear el db después de cada registro.
  2. Utilice variables de vinculación en TODAS sus consultas. No solo la declaración de inserción, elijo eso como un ejemplo.
  3. Cambie todas las funciones find _ *() que realizan búsquedas en db para almacenar en caché los resultados de modo que no tengan que pulsar el db todo el tiempo.
  4. psycho optimiza los programas de python que realizan una gran cantidad de operaciones de numeración. La secuencia de comandos es demasiado costosa y no costosa para la CPU, por lo que no esperaría darle mucha optimización, si es que la hay.
+0

El código anterior no funciona, me da un TypeError: 'd' es un argumento de palabra clave no válido para esta función. Aparentemente, la sintaxis no es válida para psycopg2, y específicamente afirmé que estaba ejecutando una base de datos PostgreSQL. –

+0

El método cursor.execute() toma argumentos ligeramente diferentes entre diferentes implementaciones de db api. Le gusta la implementación de psycopg2 toma variables de vinculación en un diccionario. Por ejemplo: –

+0

bind_vars = {'person_id:' person ',' message_type ':' msg ',' created_at ': some_date,' channel ': 3) cursor.execute (sql_statement, bindvars) –

2

Como Marcos sugirió, utilice variables de enlace. La base de datos solo debe preparar cada declaración una vez, luego "completar los espacios en blanco" para cada ejecución. Como un buen efecto secundario, se ocupará automáticamente de los problemas de cotización de cadenas (que su programa no está manejando).

Activar las transacciones (si no lo están) y realizar una única confirmación al final del programa. La base de datos no tendrá que escribir nada en el disco hasta que se hayan confirmado todos los datos. Y si su programa encuentra un error, no se comprometerá ninguna de las filas, lo que le permite simplemente volver a ejecutar el programa una vez que se haya corregido el problema.

Las funciones log_hostname, log_person y log_date están realizando SELECT innecesarios en las tablas. Haga los atributos de tabla apropiados PRIMARY KEY o UNIQUE. Luego, en lugar de verificar la presencia de la clave antes de INSERTAR, simplemente haga el INSERTAR. Si la persona/fecha/nombre de host ya existe, el INSERT fallará debido a la violación de la restricción. (Esto no funcionará si usa una transacción con una confirmación única, como se sugirió anteriormente.)

O bien, si sabe que usted es el único INSERTANDO en las tablas mientras su programa se está ejecutando, cree datos paralelos estructuras en la memoria y mantenerlos en la memoria mientras haces tus INSERT. Por ejemplo, lea todos los nombres de host de la tabla en una matriz asociativa al comienzo del programa. Cuando desee saber si desea hacer un INSERT, simplemente haga una búsqueda de matriz. Si no se encuentra ninguna entrada, realice INSERTAR y actualice la matriz de forma adecuada. (Esta sugerencia es compatible con las transacciones y un solo cometió, pero requiere más programación Será maldad más rápido, sin embargo..)

1

Adicionalmente a las muchas sugerencias finas @ Marcos Roddy ha dado, hacer lo siguiente:

  • no utilice readlines, puede iterar sobre archivo de objetos
  • tratan de utilizar executemany en lugar de execute: tratar de hacer inserciones en lugar lote insertos individuales, esto tiende a ser más rápido porque hay menos sobrecarga. También reduce el número de confirmaciones
  • str.rstrip funcionará bien en lugar de despojar de la nueva línea con una expresión regular

dosificadora de los insertos utilizará más memoria temporal, sino que debe estar bien cuando usted no lee todo el archivo en la memoria.

Cuestiones relacionadas