IP Blacklist en PHP + MySQL

He estado intentando implementar una especie de lista negra de IP en PHP, donde almaceno bashs fallidos de inicio de sesión en una tabla de MySQL con el siguiente esquema:

CREATE TABLE blacklist( `ip_address` VARCHAR(35) NOT NULL, `failures` INTEGER DEFAULT 0, `release_time` BIGINT DEFAULT -1, PRIMARY KEY(`ip_address`) ); 

En mi verificación de inicio de sesión, primero elimino las entradas de la lista negra que tienen una hora de publicación anterior a la hora actual (si la hora de publicación ya ha pasado) con la siguiente consulta:

 /* I pass in time() */ DELETE FROM failures WHERE release_time < ?; 

Luego ejecuto lo siguiente:

 /* I pass in $_SERVER['REMOTE_ADDR'] */ SELECT failures FROM blacklist WHERE ip_address=? 

Si no recupero ninguna fila o si la $ fila [‘fallas’] que obtengo es superior a 5, permito verificar el nombre de usuario y la contraseña. De lo contrario, rechazo el inicio de sesión por completo.

Por cada bash fallido de inicio de sesión (ya sea mediante una regla de lista negra o mediante un nombre de usuario / contraseña no válido), ejecuto:

 /* IP address is $_SERVER['REMOTE_ADDR'], release_time is current time + (new failures * 90 minutes) */ INSERT INTO BLACKLIST(ip_address, failures, release_time) VALUES(?,?,?) ON DUPLICATE KEY UPDATE failures=failures+1, release_time=?; 

Desafortunadamente, estoy golpeando la base de datos al menos 3 veces (borrando la lista negra, obteniendo una dirección IP, incrementando las fallas como mínimo). ¿Hay una mejor manera de mantener una lista negra dinámica, tal vez escribir en un caché cada minuto?

Vi que Banning by IP con php / mysql es similar a mi pregunta, pero estoy permitiendo que las personas sean eliminadas de la lista negra si dejan de intentar iniciar sesión por un buen período de tiempo. De esta manera, las personas que simplemente olvidan sus credenciales se ven afectadas menos que las personas que intentan brutal forzar su camino para obtener credenciales.

La siguiente consulta no necesita ejecutarse regularmente y se podría mover a un trabajo cron:

 DELETE FROM failures WHERE release_time < ?; 

Esta consulta "booleana" devolverá 1 si la persona está en la lista negra, 0 de lo contrario:

 SELECT COUNT(ip_address) as blacklisted FROM blacklist WHERE ip_address = ? AND release_time > ? AND failures > 5 

Puede acelerar las cosas ya que no está utilizando PHP para contar filas y comparar números:

 if ($row['blacklisted']) { /* ... */ } 

No creo que puedas evitar el último realmente.