PostgreSQL: heavyweight locks, lwlocks, deadlocks, spinlocks, pg_locks, row locks... sálvese quien pueda! Álvaro Herrera Command Prompt, Inc. alvherre@commandprompt.com São Paulo, SP
Introducción Esta charla es sobre locks su implementación bloqueos en aplicaciones? WTF: locks sobre registros WTF: locks sobre transacciones
Introducción Esta charla es sobre locks su implementación bloqueos en aplicaciones? WTF: locks sobre registros WTF: locks sobre transacciones Esta charla NO es sobre MVCC Para eso, vaya a la de Bruce Momjian, que además tiene traducción simultánea
Introducción Esta charla es sobre locks su implementación bloqueos en aplicaciones? WTF: locks sobre registros WTF: locks sobre transacciones Esta charla NO es sobre MVCC Clusters, replicación, sharding,...
Locks en PostgreSQL Tres tipos de locks en PostgreSQL: spinlocks LWLocks heavyweight locks
Spinlocks Mecanismo básico: spinlocks objetos muy livianos rápida adquisición (de no haber contención) la CPU queda ocupada esperando hasta obtener el lock, si está ocupado busy waiting en estricto rigor significa espera activa busy waiting normalmente es malo: la CPU queda ocupada
Spinlocks en PostgreSQL Implementación en Postgres: macros TAS (test-and-set) y SPIN_DELAY (especie de pausa) TAS es un mecanismo atómico (incluso en multiprocesadores) código específico en assembly para cada plataforma/compilador src/include/storage/s_lock.h src/backend/storage/s_lock.c src/backend/storage/spin.c
Spinlocks en PostgreSQL (2) Obtención del lock: Si el spinlock está disponible: se setea un bit en memoria Si el lock está tomado por otro proceso: se itera el TAS hasta que se libere en cada iteración: spin (girar) usando SPIN_DELAY de ahí el nombre Liberar el lock: resetear el bit de memoria otro proceso que desee adquirir lo detectará en su siguiente iteración no hay detección de deadlocks!
Paréntesis: Deadlocks Recordatorio: es una especie de abrazo de oso forma más simple: proceso A adquiere lock X proceso B adquiere lock Y proceso A desea adquirir candado Y (se bloquea) proceso B desea adquirir candado X todo el sistema está bloqueado sin esperanza
Deadlocks es fundamental evitar deadlocks o corregirlos corregir (detectar) un deadlock es costoso. Lo veremos más adelante evitar deadlocks es responsabilidad del programador inventar reglas de manera que la situación nunca se produzca
Deadlocks y Spinlocks en spinlocks, la regla es simple: Jamás obtener un spinlock cuando se tiene otro spinlock bloqueado Para asegurar que esto se cumple, el uso de spinlocks es muy restringido Cada usuario de un spinlock normalmente sólo ejecuta una o dos docenas de instrucciones con el spinlock bloqueado
Usuarios de spinlocks Se utilizan spinlocks para proteger la implementación de memoria compartida (ShmemLock) implementar LWLocks (struct LWLock en lwlock.c) proteger descriptor de cada buffer (de shared buffers ) (BufferDesc en buf_internals.h) proteger información de control de checkpoints y XLog
LWLocks LWLock: lightweight locks Mecanismo intermedio entre heavyweight locks y spinlocks más capaces que spinlocks dos modos de adquisición: compartido (candado de sólo lectura, más de un proceso simultáneamente) exclusivo (candado de escritura) para proteger acceso a información de control almacenada en memoria compartida No hay detección de deadlocks
LWLocks (2) Para adquirir un LWLock: adquirir su spinlock si el lwlock está libre: tomarlo si el lwlock está ocupado: ponerme a dormir (semáforo SysV) liberar spinlock Al liberar un LWLock: adquirir el spinlock si hay alguien durmiendo en este lwlock (puede ser más de uno), despertarlo liberar el spinlock
LWLocks (2) Para adquirir un LWLock: adquirir su spinlock si el lwlock está libre: tomarlo si el lwlock está ocupado: ponerme a dormir (semáforo SysV) liberar spinlock Al liberar un LWLock: adquirir el spinlock si hay alguien durmiendo en este lwlock (puede ser más de uno), despertarlo liberar el spinlock Ventaja sobre spinlock: el tiempo de espera no utiliza CPU
LWLocks (3) Es legal tomar más de un lwlock simultáneamente... es obligatorio hacerlo en variadas ocasiones Es responsabilidad del Postgres hacker asegurarse que no hay posibilidad de deadlock La regla: si se adquiere más de uno, debe ser siempre en el mismo orden Tomar más de dos es muy raro Todo esto requiere cuidadoso análisis por parte del PG hacker
LWLocks (4) Muchas operaciones regulares se protegen con LWLocks escribir en WAL (WALInsertLock, WALWriteLock) generar un OID o un XID (OidGenLock, XidGenLock) examinar o modificar el contenido de ProcArray, la cola shared-inval, pg_clog, etc. examinar el contenido (datos) de un buffer (content_lock en BufferDesc) En resumen: toda estructura fija (predefinida) en memoria compartida Otra regla: un lwlocks sólo debe mantenerse bloqueado por períodos muy cortos de tiempo es decir: no realizar I/O con un lwlock bloqueado excepción: lwlocks usados para controlar I/O lwlocks para I/O de un buffer (io_in_progress_lock en BufferDesc)
Heavyweight locks Bloqueos sobre objetos de usuario (src/backend/storage/lmgr/lmgr.c) Estos son los realmente interesantes para la aplicación relación (tabla, secuencia, vista, etc) extender (agrandar) una relación página de una relación tupla (registro, fila) de una relación transacción transacción virtual objeto (cualquier cosa que no sea una relación) advisory
Modos de adquisición AccessShare (SELECT) RowShare (SELECT FOR UPDATE, SELECT FOR SHARE) RowExclusive (UPDATE, INSERT, DELETE, etc) ShareUpdateExclusive (VACUUM, ANALYZE, CREATE INDEX CONCURRENTLY) Share (CREATE INDEX) ShareRowExclusive (no utilizado por el código) Exclusive (no utilizado por el código, excepto para bloquear ciertos catálogos en ocasiones) AccessExclusive (ALTER TABLE, DROP TABLE, TRUNCATE, REINDEX, CLUSTER, VACUUM FULL)
Tabla de conflictos AS RS RE SUE S SRE E AE AccessShare X RowShare X X RowExclusive X X X X ShareUpdateExcl X X X X X Share X X X X X ShareRowExclusive X X X X X X Exclusive X X X X X X X AccessExclusive X X X X X X X X
Deadlocks Este tipo de locks es el único que tiene detección de deadlocks (src/backend/storage/lmgr/deadlock.c) El sistema espera la obtención del lock durante 1s Si después de este tiempo no se ha obtenido el lock, el detector de deadlocks es ejecutado (deadlock.c) El detector de deadlocks recorre el grafo de waiter holder y determina si existe un ciclo en el grafo De existir un ciclo, significa que hay un deadlock El proceso que detecta primero el deadlock aborta su propia transacción El grafo puede ser arbitrariamente complejo En términos de aplicación: todo código que esté posiblemente sujeto a deadlocks debería tener un bucle de reintento
Locks sobre transacciones Cada transacción tiene asociado un virtualxid desde el momento en que se inicia Cada transacción obtiene un Xid al momento de hacer su primera escritura Cada transacción adquiere ExclusiveLock en su Xid y vxid Se libera automáticamente cuando termina Cuando una transacción quiere esperar que otra termine, trata de obtener un ShareLock en ese Xid o vxid
Examen de locks: vista pg_locks pg_locks vista de sistema permite conocer los locks que se tienen o se esperan (sólo hablamos de heavyweight locks aquí) muy útil para comprender bloqueos entre procesos concurrentes... algo difícil de leer :-( múltiples registros por proceso granted=f son candados que un proceso está esperando obtener granted=t son candados que un proceso tiene bloqueados
pg_locks ejemplo alvherre=# select * from pg_locks where not granted; Record 1 locktype relation database 16384 relation 16398 page (null) tuple (null) virtualxid (null) transactionid (null) classid (null) objid (null) objsubid (null) virtualtransaction 3/2 pid 30786 mode AccessShareLock granted f fastpath f
pg_locks ejemplo alvherre=# select * from pg_locks where locktype = relation and database = 16384 and relation = 16398 and granted; Record 1 locktype relation database 16384 relation 16398 page (null) tuple (null) virtualxid (null) transactionid (null) classid (null) objid (null) objsubid (null) virtualtransaction 2/3 pid 30778 mode AccessExclusiveLock granted t fastpath f
pg_locks ejemplo Record 1 locktype relation database 16384 relation 16398 page (null) tuple (null) virtualxid (null) transactionid (null) classid (null) objid (null) objsubid (null) virtualtransaction 2/3 pid 30778 mode AccessExclusiveLock granted t fastpath f
campos usados cada tipo de lock relation database relation page tuple virtualxid transactionid classid objid objsubid Lock a relation
campos usados cada tipo de lock extend database relation page tuple virtualxid transactionid classid objid objsubid Lock a relation for extension
campos usados cada tipo de lock page database relation page tuple virtualxid transactionid classid objid objsubid Lock a specific page within a relation
campos usados cada tipo de lock tuple database relation page tuple virtualxid transactionid classid objid objsubid Lock a specific tuple within a relation
campos usados cada tipo de lock virtualtransaction database relation page tuple virtualxid transactionid classid objid objsubid Lock a virtual Xid
campos usados cada tipo de lock transactionid database relation page tuple virtualxid transactionid classid objid objsubid Lock an Xid
campos usados cada tipo de lock object database relation page tuple virtualxid transactionid classid objid objsubid Lock a database object that s not a relation or part thereof
campos usados cada tipo de lock advisory database relation page tuple virtualxid transactionid classid objid objsubid (1=int64 key; 2=2xint32 keys) Lock an arbitrary user-specified key (either a single int64 value, or two int32 values)
Una vista útil sobre pg_locks create view waiter_holder as select holder.*, waiter.virtualtransaction as waitervxid, waiter.pid as waiterpid, waiter.mode as waitermode from pg_locks holder join pg_locks waiter on holder.locktype = waiter.locktype and ( holder.database, holder.relation, holder.page, holder.tuple, holder.virtualxid, holder.transactionid, holder.classid, holder.objid, holder.objsubid ) is not distinct from ( waiter.database, waiter.relation, waiter.[... mismas columnas...] ) and holder.granted and not waiter.granted;
Una vista útil sobre pg_locks (2) Record 1 locktype relation database 16384 relation 16411 page (null)..... virtualtransaction 2/5 pid 30778 mode AccessExclusiveLock granted t fastpath f waitervxid 3/3 waiterpid 30786 waitermode AccessShareLock
qué está haciendo cada proceso? SELECT holder_activity.procpid AS holder_pid, holder_activity.current_query AS holder_query, waiter_activity.procpid AS waiter_pid, waiter_activity.current_query AS waiter_query FROM pg_stat_activity AS holder_activity JOIN waiter_holder ON (holder_activity.procpid = pid) JOIN pg_stat_activity AS waiter_activity ON (waiter_activity.procpid = waiterpid);
qué está haciendo cada proceso? (2) Record 1 holder_pid 5655 holder_query <IDLE> in transaction waiter_pid 5665 waiter_query drop table fk;
Locks sobre registros Row locks No aparecen en pg_locks excepto temporalmente Bloqueo de largo plazo utiliza un row mark row mark es una marca especial ( en disco ) en el registro mismo no hay representación del row mark en memoria (ocuparía mucha) Para examinar row marks existentes, contrib/pgrowlocks debe recorrer la tabla completa para obtenerlos Candado exclusivo: SELECT FOR UPDATE Candado compartido: SELECT FOR SHARE
Locks sobre registros: protocolo de obtención Protocolo de obtención: Bloquear el buffer Obtener heavyweight lock sobre registro Si está tomado el row mark, desbloquear el buffer dormir en el lock del Xid de la transacción que lo tiene obs: lock sobre registro queda tomado Si no, marcar el registro con mi Xid ( row mark ) liberar el heavyweight lock desbloquear el buffer
Llaves foráneas La implementación de llaves foráneas usa locks de registros Internamente se usa SELECT FOR SHARE sobre el registro referenciado (src/backend/utils/adt/ri_triggers.c Esto asegura que el registro no se borrará hasta después de que la transacción que crea la referencia haya terminado
Llaves foráneas La implementación de llaves foráneas usa locks de registros Internamente se usa SELECT FOR SHARE sobre el registro referenciado (src/backend/utils/adt/ri_triggers.c Esto asegura que el registro no se borrará hasta después de que la transacción que crea la referencia haya terminado... pero también impide hacer un UPDATE sobre el registro... aún cuando dicho UPDATE no afecte la permanencia de la llave
Desarrollo en progreso optimización de spinlocks en >32 CPUs 9.2: heavyweight locks usan fast path SELECT FOR KEY SHARE
Conclusión Los mecanismos de locking son complicados...
Conclusión Los mecanismos de locking son complicados...... pero es preciso comprenderlos para depurar ciertos problemas en PostgreSQL
Preguntas?