Vamos a ver como nos conectamos a varios esclavos de MySQL que replican de un maestro. Como comentamos en el capitulo anterior, la replicación de MySQL nos permite aumentar la capacidad de lecturas de nuestra base de datos.
Ahora vamos a estudiar la manera en la que podemos conectar nuestro programa de Ruby a todos esos esclavos. La mayoría de las gemas que nos encontramos soportan múltiples esclavos. Pero la manera en la que se conectan es muy simple, y si nos encontramos en una situación de mucha carga, podemos llegar a tumbar toda la base de datos. Veamos el ejemplo de la gema Sequel.
Para configurar múltiples esclavos, tenemos que configurarla de la siguiente manera (codigo ejemplo de su documentación):
DB=Sequel.connect('mysql://master_server/database', \
:servers=>{:read_only=>proc{|db| {:host=>db.get_slave_host}}})
def DB.get_slave_host
@current_host ||= -1
"slave_server#{(@current_host+=1)%4}"
end
Siendo master_server nuestro servidor maestro y slave_server#{(@current_host+=1)%4} nuestros esclavos.
Vamos a hacerlo más aleatorio:
DB=Sequel.connect('mysql://maestro/my_db', \
:servers=>{:read_only=>proc{|db| {:host=>db.get_slave_host}}})
def DB.get_slave_host
slaves = [
"esclavo_1",
"esclavo_2",
"esclavo_3",
"esclavo_4",
"esclavo_5",
"esclavo_6"
]
return slaves[rand(slaves.length - 1)]
end
Ahora bien, viendo este código, podemos ver ya que la distribución de las conexiones va a ser equitativa, es decir, si tenemos 4 esclavos, cada uno de ellos va a recibir el mismo número de conexiones. Esto en un mundo ideal nos viene bien, pero no nos viene tan bien cuando tengamos alguno de los esclavos con más carga que los demás…
Vamos a darle una vuelta de tuerca. Vamos a tratar de comprobar el estado de los servidores antes de conectarnos, para saber si realmente queremos conectarnos a dicho servidor, o no…
DB=Sequel.connect("mysql://maestro/#{db_name}", \
:servers=>{:read_only=>proc{|db| {:host=>db.get_slave_host}}})
def DB.slaves_array
slaves = [
"esclavo_1",
"esclavo_2",
"esclavo_3",
"esclavo_4",
"esclavo_5",
"esclavo_6"
]
return slaves
end
def DB.get_slave_host
tries = 0
while tries <= DB.slaves_array.length
slave = DB.get_slaves_array[rand(DB.get_slaves_array.length - 1)]
return slave if DB.get_slave_delay <= 1
tries += 1
end
end
def DB.get_slave_delay(slave, db_name)
begin
DBSlave=Sequel.connect("mysql://#{slave}/#{db_name}")
delay = DBSlave["SHOW SLAVE STATUS"]
return delay[32].to_i
rescue
return 1000
end
end
Parte por parte.
Lo que este código hace es simplemente comprobar si el esclavo al que nos vamos a conectar esta sin retrasos con respecto al maestro y ademas si nos podemos conectar. Para ello, cuando Sequel llame a db.get_slave_host lo que vamos a hacer es lo siguiente:
def DB.get_slave_host
tries = 0
while tries <= DB.slaves_array.length
slave = DB.get_slaves_array[rand(DB.get_slaves_array.length - 1)]
return slave if DB.get_slave_delay <= 1
tries += 1
end
end
Vamos a sacar un esclavo de la lista de manera aleatoria:
slave = DB.get_slaves_array[rand(DB.get_slaves_array.length - 1)]
Y posteriormente vamos a tratar de conectarnos al esclavo y mirar su estado:
return slave if DB.get_slave_delay <= 1
Esto llama a la función que hemos definido:
def DB.get_slave_delay(slave, db_name)
begin
DBSlave=Sequel.connect("mysql://#{slave}/#{db_name}")
delay = DBSlave["SHOW SLAVE STATUS"]
return delay[32].to_i
rescue
return 1000
end
end
Esta función va a comprobar el estado del servidor. Si el esclavo tuviera algún problema, devolvería un retraso elevado (cuando los esclavos tienen mucha carga, suelen retrasarse con respecto a su maestro) o si no podemos conectar, entonces devolverá 1000, dando así un retraso muy elevado indicando que el servidor no esta disponible.
Nota: para que nuestro código pueda preguntar por el estado de los esclavos («SHOW SLAVE STATUS») necesitamos dar privilegios SUPER, REPLICATION CLIENT al usuario que hemos designado a nuestro programa para conectarse a los esclavos:
mysql> GRANT SUPER, REPLICATION CLIENT ON *.* TO 'user'@'domain' IDENTIFIED BY 'password'
En conclusión, si queremos que nuestro código soporte varios servidores esclavos y que compruebe la carga antes de cada conexión, debemos complicar la lógica de conexión a la hora de conectarnos a la base de datos. Las gemas como Sequel nos pueden ayudar a ello con abstracciones de conexión. Es muy sencillo hacer esta comprobación cada vez que nos conectamos. Pero como en todo, este método de comprobación no es escalable a grandes sistemas dado que cada vez que vamos a realizar una lectura, primero hacemos una conexión al esclavo para mirar su estado. Esto hace que la cantidad de conexiones se multiplique por dos.
Mi recomendación es que se trate de hacer un método híbrido donde en paralelo a este código, se compruebe el estado de los esclavos cada vez, y se modifiquen los pesos de cada esclavo dependiendo de dichas comprobaciones. Pero esto corresponde al siguiente post…