Programa C bloqueado en espera ininterrumpida mientras se realiza la E/S de disco en Mac OS X Snow Leopard
macos io (5)
Una línea de fondo: soy el desarrollador de Redis, una base de datos NoSQL . Una de las nuevas características que estoy implementando es la memoria virtual, porque Redis toma todos los datos en la memoria. Gracias a VM, Redis puede transferir objetos raramente usados de la memoria al disco, hay varias razones por las cuales esto funciona mucho mejor que dejar que el sistema operativo haga el trabajo por nosotros para intercambiarlos (los objetos redis están compuestos de muchos objetos pequeños asignados de forma no contigua los lugares, cuando Redis los serializa en el disco, ocupan 10 veces menos espacio en comparación con las páginas de memoria en las que viven, etc.
Ahora tengo una implementación alfa que funciona perfectamente en Linux, pero no tan bien en Mac OS X Snow Leopard. De vez en cuando, mientras Redis intenta mover una página de la memoria al disco, el proceso de redis entra en el estado de espera ininterrumpida durante minutos. No pude depurar esto, pero esto sucede ya sea en una llamada a fseeko()
o fwrite()
. Después de unos minutos, la llamada finalmente regresa y el redis sigue funcionando sin problemas: sin bloqueo.
La cantidad de datos transferidos es muy pequeña, algo así como 256 bytes. Por lo tanto, no debe ser una gran cantidad de E / S realizadas.
Pero hay un detalle interesante sobre el archivo de intercambio que es el objetivo de la operación de escritura. Es un archivo grande (26 Gigabytes) creado al abrir un archivo con fopen()
y luego ampliarse usando ftruncate()
. Finalmente, el archivo está unlink()
ed para que Redis continúe tomando una referencia a él, pero estamos seguros de que cuando el proceso de Redis salga del sistema operativo realmente liberará el archivo de intercambio.
Ok, eso es todo, pero estoy aquí para más detalles. Y, por cierto, incluso puedes encontrar el código real en el git Redis, pero no es trivial entenderlo en cinco minutos, dado que es un sistema bastante complejo.
Muchas gracias por cualquier ayuda.
¿Ha desactivado el almacenamiento en caché de archivos para su archivo? es decir, fcntl (fd, F_GLOBAL_NOCACHE, 1)
¿Ha intentado realizar la depuración con DTrace y / o Instruments (el front-end experimental dtrace de Apple)?
Como Linus dijo una vez en la lista de correo de Git:
"Me doy cuenta de que a la gente de OS X le cuesta mucho aceptarlo, pero los sistemas de archivos de OS X son, en general, una mierda total, incluso más que Windows".
Como lo entiendo, HFS + tiene un soporte muy pobre para archivos dispersos. Por lo tanto, es posible que su escritura esté activando una expansión de archivo que está inicializando / materializando una gran parte del archivo.
Por ejemplo, sé que hacer un nuevo archivo vacío grande y luego escribir en unas pocas ubicaciones al azar produce un archivo muy grande en el disco con HFS +. Es bastante molesto ya que los archivos mmap y dispersos son una forma extremadamente conveniente de trabajar con datos, y prácticamente todas las demás plataformas / sistemas de archivos manejan esto con gracia.
Es el archivo de intercambio escrito en forma lineal? ¿Lo que significa que reemplazamos un bloque existente o escribimos un nuevo bloque al final e incrementamos un puntero de espacio libre? Si es así, tal vez hacer llamadas más breves más frecuentes para expandir el archivo resultaría en pausas más cortas.
Dejando de lado, tengo curiosidad por saber por qué redis VM no usa mmap y luego simplemente mueve bloques en un intento de concentrar bloques calientes en páginas calientes.
antirez, no estoy seguro de que sea de mucha ayuda ya que mi experiencia con Apple se limita a Apple ][
, pero lo intentaré.
Lo primero es una pregunta. Pensé que, para la memoria virtual, la velocidad de operación sería una medida más importante que el espacio en disco (especialmente para una base de datos NoSQL donde la velocidad es todo el punto, de lo contrario estaría usando SQL, ¿no?). Pero, si su archivo de intercambio es 26G, tal vez no :-)
Algunas cosas para probar (si es posible).
- Trate de aislar realmente el problema a la búsqueda o la escritura. Me cuesta mucho creer que una búsqueda podría tardar tanto tiempo, ya que, en el peor de los casos, debería ser un cambio de puntero del búfer. Aún así, no escribí OSX, así que no puedo estar seguro.
- Intente ajustar el tamaño del archivo de intercambio para ver si eso es lo que está causando el problema.
- ¿Alguna vez ha expandido dinámicamente el archivo de intercambio (a diferencia de la asignación previa)? Si lo haces, eso puede ser lo que está causando el problema.
- ¿Siempre escribes tan bajo en el archivo como puedas? Puede ser que la creación de un archivo 26G no lo llene con datos, pero si lo crea y luego escribe en el último byte, es posible que el sistema operativo tenga que poner a cero los bytes antes de esa fecha (aplazando la inicialización, si corresponde).
- ¿Qué sucede si simplemente asigna previamente el archivo completo (escribe a cada byte) y no lo desvincula? En otras palabras, deje el archivo allí entre ejecuciones de su programa (creándolo si no existe, por supuesto). Luego, en su código de inicio para Redis, simplemente inicialice el archivo (punteros y demás). Esto puede eliminar cualquier problema como los del punto 4 anterior.
- Pregunte en los diferentes sitios de BSD también. No estoy seguro de cuánto cambió Apple bajo las coberturas, pero OSX es solo BSD en el nivel más bajo (Pax patos para cubrir).
- También considere preguntar en los sitios de Apple (si aún no lo ha hecho).
Bueno, esa es mi pequeña contribución, espero que ayude. Buena suerte con tu proyecto.