Caché compartido multiproceso persistente en Python con stdlib o dependencias mínimas

Acabo de probar un módulo de archivado de Python como el caché persistente para los datos obtenidos del servicio externo. El ejemplo completo está aquí .

Me preguntaba cuál sería el mejor enfoque si quisiera que este multiprocesamiento fuera seguro. Estoy al tanto de redis, memcached y esas “soluciones reales”, pero me gustaría usar solo las partes de la biblioteca estándar de Python o muy pocas dependencias para mantener mi código compacto y no introducir una complejidad innecesaria al ejecutar el código en un solo proceso. Modelo de un solo hilo.

Es fácil encontrar una solución de proceso único, pero esto no funciona bien en los tiempos de ejecución actuales de Python. Específicamente, el problema sería que en Apache + mod_wsgi enviroment

Además, si algún paquete PyPi hace esto sin dependencias externas, hágamelo saber, por favor. Los enfoques y recomendaciones alternativas, como “solo usar sqlite” son bienvenidos.

Ejemplo:

import datetime import os import shelve import logging logger = logging.getLogger(__name__) class Converter: def __init__(self, fpath): self.last_updated = None self.data = None self.data = shelve.open(fpath) if os.path.exists(fpath): self.last_updated = datetime.datetime.fromtimestamp(os.path.getmtime(fpath)) def convert(self, source, target, amount, update=True, determiner="24h_avg"): # Do something with cached data pass def is_up_to_date(self): if not self.last_updated: return False return datetime.datetime.now() < self.last_updated + self.refresh_delay def update(self): try: # Update data from the external server self.last_updated = datetime.datetime.now() self.data.sync() except Exception as e: logger.error("Could not refresh market data: %s %s", self.api_url, e) logger.exception(e) 

Yo diría que desearía usar alguna biblioteca de almacenamiento en caché existente, me viene a la mente dogpile.cache , ya tiene muchas características y puede conectar fácilmente los backends que pueda necesitar.

dogpile.cache documentación de dogpile.cache dice lo siguiente:

Este patrón de “obtener o crear” es la clave completa del sistema “Dogpile”, que coordina una operación de creación de un solo valor entre muchas operaciones de obtención simultáneas para una clave en particular, eliminando el problema de que un valor caducado se vuelva a generar de forma redundante. muchos trabajadores a la vez.

Consideremos sus requerimientos sistemáticamente:

Dependencias externas mínimas o no

Su caso de uso determinará si puede usar la sincronización dentro de banda (descriptor de archivo o región de memoria heredada a través de la bifurcación) o fuera de banda (lockings de archivos posix, memoria compartida de sys V).

Entonces puede tener otros requisitos, por ejemplo, la disponibilidad de las herramientas en varias plataformas, etc.

Realmente no hay mucho en la biblioteca estándar, excepto herramientas simples. Un módulo sin embargo, se destaca, sqlite3 . Sqlite usa los lockings fcntl / posix, aunque existen limitaciones de rendimiento, múltiples procesos implican una base de datos respaldada por archivos, y sqlite requiere fdatasync en la confirmación.

Por lo tanto, hay un límite para transacciones / s en sqlite impuestas por las rpm de su disco duro. Este último no es un gran problema si tiene una redada de HW, pero puede ser una gran desventaja en el hardware de los productos básicos, por ejemplo, una computadora portátil, una memoria USB o una tarjeta SD. Planifique ~ 100tps si usa un disco duro normal giratorio.

Sus procesos también pueden bloquearse en sqlite, si utiliza modos de transacción especiales.

previniendo el rebaño de truenos

Hay dos enfoques principales para esto:

  • probabilísticamente actualizar el elemento de caché antes de lo requerido, o
  • actualizar solo cuando sea necesario, pero bloquear a otras personas que llaman

Presumiblemente, si confía en otro proceso con el valor de caché, no tiene ninguna consideración de seguridad. Así funcionará, o tal vez una combinación de ambos.

Escribí una envoltura de locking (segura para subprocesos y procesos múltiples) alrededor del módulo de archivado estándar sin dependencias externas:

https://github.com/cristoper/shelfcache

Cumple con muchos de sus requisitos, pero no tiene ningún tipo de estrategia de retroceso para evitar hatos atronadores, y si desea el locking de Reader-Writer (para que se puedan leer varios hilos, pero solo una escritura), debe proporcionar su propio RW. bloquear.

Sin embargo, si volviera a hacerlo, probablemente “solo usaría sqlite”. El módulo de shelve que se abstrae sobre varias implementaciones diferentes de dbm, que a su vez se abstrae sobre varios mecanismos de locking del sistema operativo, es una molestia (usar la opción de flock shelfcache con gdbm en Mac OS X (o busybox), por ejemplo, produce un punto muerto).

Hay varios proyectos de Python que intentan proporcionar una interfaz de dict estándar para sqlite u otras tiendas persistentes, por ejemplo, https://github.com/RaRe-Technologies/sqlitedict

(Tenga en cuenta que sqldict es seguro para subprocesos incluso para la misma conexión de base de datos, pero no es seguro compartir la misma conexión de base de datos entre procesos).