Oleoducto Goldfields, por SeanMac (Wikimedia Commons)
Si está intentando optimizar el rendimiento de su aplicación basada en PostgreSQL, probablemente se esté centrando en las herramientas habituales:EXPLICAR (BUFFERS, ANALYZE) , pg_stat_statements , explicación_automática , log_statement_min_duration , etc.Tal vez esté investigando una disputa de bloqueo con log_lock_waits , monitorear el rendimiento de su punto de control, etc.
Pero, ¿pensaste en la latencia de red? ? Los jugadores conocen la latencia de la red, pero ¿pensaste que era importante para tu servidor de aplicaciones?
La latencia importa
Las latencias típicas de red de ida y vuelta de cliente/servidor pueden oscilar entre 0,01 ms (host local) y ~0,5 ms de una red conmutada, 5 ms de WiFi, 20 ms de ADSL, 300 ms de enrutamiento intercontinental e incluso más para elementos como enlaces satelitales y WWAN .
Un SELECT trivial puede tomar del orden de 0.1ms para ejecutarse del lado del servidor. Un INSERT trivial puede tardar 0,5 ms.
Cada vez que su aplicación ejecuta una consulta, tiene que esperar a que el servidor responda con éxito/fracaso y posiblemente un conjunto de resultados, metadatos de consulta, etc. Esto genera al menos un retraso de ida y vuelta en la red.
Cuando trabaja con consultas pequeñas y simples, la latencia de la red puede ser significativa en relación con el tiempo de ejecución de sus consultas si su base de datos no está en el mismo host que su aplicación.
Muchas aplicaciones, particularmente ORM, son muy propensas a ejecutar
De manera similar, si está llenando la base de datos desde un ORM, probablemente esté haciendo cientos de miles de INSERT triviales. s... y esperando después de todos y cada uno para que el servidor confirme que está bien.
Es fácil tratar de concentrarse en el tiempo de ejecución de la consulta e intentar optimizarlo, pero no hay mucho que pueda hacer con un trivial INSERT INTO ...VALUES ... . Elimine algunos índices y restricciones, asegúrese de que esté incluido en una transacción y ya casi ha terminado.
¿Qué hay de deshacerse de todas las esperas de la red? Incluso en una LAN, comienzan a acumularse en miles de consultas.
COPIAR
Una forma de evitar la latencia es usar COPY . Para usar el soporte COPY de PostgreSQL, su aplicación o controlador debe producir un conjunto de filas similar a CSV y transmitirlas al servidor en una secuencia continua. O se le puede pedir al servidor que envíe su aplicación como un flujo CSV.
De cualquier manera, la aplicación no puede intercalar una COPIA con otras consultas, y las inserciones de copia deben cargarse directamente en una tabla de destino. Un enfoque común es COPIAR en una tabla temporal, luego desde allí haga un INSERTAR EN... SELECCIONAR... , ACTUALIZAR... DESDE.... , ELIMINAR DE... USANDO... , etc. para usar los datos copiados para modificar las tablas principales en una sola operación.
Eso es útil si está escribiendo su propio SQL directamente, pero muchos marcos de aplicaciones y ORM no lo admiten, además, solo puede reemplazar directamente el simple INSERT . Su aplicación, marco o controlador de cliente tiene que lidiar con la conversión para la representación especial que necesita COPY , busque cualquier metadato de tipo necesario, etc.
(Controladores notables que hacen apoyo COPIAR incluyen libpq, PgJDBC, psycopg2 y la gema Pg... pero no necesariamente los marcos y ORM creados sobre ellos).
PgJDBC:modo por lotes
El controlador JDBC de PostgreSQL tiene una solución para este problema. Se basa en el soporte presente en los servidores PostgreSQL desde 8.4 y en las funciones de procesamiento por lotes de la API de JDBC para enviar un lote de consultas al servidor, luego espere solo una vez para confirmar que todo el lote se ejecutó correctamente.
Bueno, en teoría. En realidad, algunos desafíos de implementación limitan esto, de modo que los lotes solo se pueden realizar en fragmentos de unos pocos cientos de consultas en el mejor de los casos. El controlador también puede ejecutar consultas que devuelvan filas de resultados en lotes por lotes solo si puede determinar qué tan grandes serán los resultados antes de tiempo. A pesar de esas limitaciones, el uso de Statement.executeBatch() puede ofrecer un gran aumento de rendimiento a las aplicaciones que realizan tareas como la carga masiva de datos en instancias de bases de datos remotas.
Debido a que es una API estándar, puede ser utilizada por aplicaciones que funcionan en múltiples motores de bases de datos. Hibernate, por ejemplo, puede utilizar el procesamiento por lotes de JDBC, aunque no lo hace de forma predeterminada.
libpq y procesamiento por lotes
La mayoría (¿todos?) de los demás controladores de PostgreSQL no admiten el procesamiento por lotes. PgJDBC implementa el protocolo PostgreSQL de forma completamente independiente, mientras que la mayoría de los demás controladores utilizan internamente la biblioteca C libpq que se proporciona como parte de PostgreSQL.
libpq no es compatible con el procesamiento por lotes. Tiene una API asíncrona sin bloqueo, pero el cliente solo puede tener una consulta "en curso" a la vez. Debe esperar hasta que se reciban los resultados de esa consulta antes de poder enviar otra.
El servidor de PostgreSQL admite el procesamiento por lotes muy bien, y PgJDBC ya lo usa. Así que he escrito soporte por lotes para libpq y lo envió como candidato para la próxima versión de PostgreSQL. Dado que solo cambia el cliente, si se acepta, seguirá acelerando las cosas cuando se conecte a servidores más antiguos.
Estaría realmente interesado en los comentarios de los autores y usuarios avanzados de libpq controladores de cliente y desarrolladores de libpq aplicaciones basadas en El parche se aplica bien sobre PostgreSQL 9.6beta1 si desea probarlo. La documentación es detallada y hay un programa de ejemplo completo.
Rendimiento
Pensé que un servicio de base de datos alojado como RDS o Heroku Postgres sería un buen ejemplo de dónde sería útil este tipo de funcionalidad. En particular, acceder a ellos desde nuestro lado, sus propias redes, realmente muestra cuánto puede doler la latencia.
Con una latencia de red de ~320 ms:
- 500 inserciones sin procesamiento por lotes:
167.0s - 500 inserciones con procesamiento por lotes:
1.2s
… que es más de 120 veces más rápido.
Por lo general, no ejecutará su aplicación a través de un enlace intercontinental entre el servidor de la aplicación y la base de datos, pero esto sirve para resaltar el impacto de la latencia. Incluso en un socket de Unix para localhost vi una mejora del rendimiento de más del 50 % para 10 000 inserciones.
Lotes en aplicaciones existentes
Desafortunadamente, no es posible habilitar automáticamente el procesamiento por lotes para las aplicaciones existentes. Las aplicaciones tienen que usar una interfaz ligeramente diferente en la que envían una serie de consultas y solo luego solicitan los resultados.
Debería ser bastante sencillo adaptar las aplicaciones que ya usan la interfaz libpq asíncrona, especialmente si usan el modo sin bloqueo y un select() /encuesta() /epoll() /EsperarMúltiplesObjetosEx círculo. Aplicaciones que utilizan libpq síncrono las interfaces requerirán más cambios.
Lotes en otros controladores de clientes
De manera similar, los controladores de cliente, marcos y ORM generalmente necesitarán cambios internos y de interfaz para permitir el uso de procesamiento por lotes. Si ya están usando un bucle de eventos y E/S sin bloqueo, deberían ser bastante simples de modificar.
Me encantaría ver que los usuarios de Python, Ruby, etc. puedan acceder a esta funcionalidad, así que tengo curiosidad por ver quién está interesado. Imagina poder hacer esto:
import psycopg2 conn = psycopg2.connect(...) cur = conn.cursor() # this is just an idea, this code does not work with psycopg2: futures = [ cur.async_execute(sql) for sql in my_queries ] for future in futures: result = future.result # waits if result not ready yet ... process the result ... conn.commit()
La ejecución por lotes asíncrona no tiene por qué ser complicada a nivel de cliente.
COPIAR es el más rápido
Donde los clientes prácticos aún deberían preferir COPY . Aquí hay algunos resultados de mi computadora portátil:
inserting 1000000 rows batched, unbatched and with COPY batch insert elapsed: 23.715315s sequential insert elapsed: 36.150162s COPY elapsed: 1.743593s Done.
El trabajo por lotes proporciona un aumento de rendimiento sorprendentemente grande incluso en una conexión de socket local de Unix... pero COPIAR deja a ambos enfoques de insertos individuales muy atrás en el polvo.
Utilice COPIAR .
La imagen
La imagen de esta publicación es de la tubería del Esquema de suministro de agua de Goldfields desde Mundaring Weir cerca de Perth en Australia Occidental hasta los campos de oro del interior (desierto). Es relevante porque tardó tanto en terminarse y fue objeto de críticas tan intensas que su diseñador y principal defensor, C. Y. O'Connor, se suicidó 12 meses antes de que se pusiera en marcha. A nivel local, la gente suele decir (incorrectamente) que murió