Replicación semi-síncrona con MySQL 5.5
El problema más grave de la replicación en MySQL es su funcionamiento asíncrono. Cuando se añade o modifica algún dato en el master, este commitea los datos en local sin esperar a que los slaves lo hagan. Esto normalmente no supone un gran problema, ya que la replicación, si no hay ningún problema con índices o con la red, es casi instantanea. Pero aún así se pueden dar algunos problemas:
El master commitea los datos sin esperar. Durante un tiempo, aunque pequeño, master y slave tendrán datos diferentes. Contra mas alto sea el valor seconds behind master, mayor será el problema.
El master no comprueba que los esclavos hayan recibido los binlogs con los cambios.
El master no comprueba que los esclavos hayan hecho efectivos los cambios en sus bases de datos.
Este es un problema solucionado en MySQL Cluster, donde la replicación es totalmente síncrona. Los nodos no commitean los cambios hasta que estos se hayan escrito correctamente en los node groups que correspondan. Si esto no es así, se hace un rollback. Pero en la replicación normal no tenemos tanta suerte.
Una de las novedades de MySQL 5.5 viene a medio solventar el problema. Con esta nueva versión de desarrollo disponemos de replicación semi-síncrona. Algo es algo :)
Su funcionamiento es simple. Ahora el master antes de hacer commit espera a que al menos uno de los slaves reciba los logs binarios. Pero aún así hay que tener en cuenta lo siguiente:
El master solamente comprueba que un slave haya recibido los logs, pero no que si lo ha podido escribir correctamente o no. Esto es, no importa el estado del SQL Thread.
Podemos tener 1000 slaves, pero con que solo uno reciba los logs ya se da por bueno.
Si pasado un tiempo ninguno de los esclavos recibe los logs, el master cambia a modo asíncrono commiteando los cambios.
Vamos a hacer unas pruebas. Necesitaremos dos cosas, MySQL 5.5 y sandbox :) Creamos un entorno de replicación con un master y dos slaves:
punisher@shyris:~$ make_replication_sandbox --how_many_slaves=2 /home/punisher/MySQL/mysql-5.5.1-m2-linux-x86_64-glibc23.tar.gz installing and starting master installing slave 1 installing slave 2 starting slave 1 .. sandbox server started starting slave 2 . sandbox server started initializing slave 1 initializing slave 2
Una vez hecho, debemos cargar el plugin que nos permite hacer uso de la replicación semi-síncrona en todos los hosts:
master [localhost] {msandbox} ((none)) > INSTALL PLUGIN rpl_semi_sync_master SONAME 'semisync_master.so'; Query OK, 0 rows affected (0.00 sec) slave1 [localhost] {msandbox} ((none)) > INSTALL PLUGIN rpl_semi_sync_slave SONAME 'semisync_slave.so'; Query OK, 0 rows affected (0.00 sec) slave2 [localhost] {msandbox} ((none)) > INSTALL PLUGIN rpl_semi_sync_slave SONAME 'semisync_slave.so'; Query OK, 0 rows affected (0.00 sec)
A continuación, debemos habilitar su uso en los ficheros de configuración:
Master: rpl_semi_sync_master_enabled=1; Slaves: rpl_semi_sync_slave_enabled=1;
Reiniciamos mysql y ya lo tenemos :) Creamos una base de datos llamada prueba y comprobamos si al menos un slave ha recibido el binlog:
master [localhost] {msandbox} ((none)) > create database pruebas; Query OK, 1 row affected (0.00 sec) master [localhost] {msandbox} ((none)) > SHOW STATUS LIKE 'Rpl_semi_sync%tx'; +-----------------------------+-------+ | Variable_name | Value | +-----------------------------+-------+ | Rpl_semi_sync_master_no_tx | 0 | | Rpl_semi_sync_master_yes_tx | 1 | +-----------------------------+-------+ 2 rows in set (0.00 sec)
Rpl_semi_sync_master_yes_tx es el número de queries correctamente replicadas. Rpl_semi_sync_master_no_tx es el número de queries que no se han replicado.
Hay que tener en cuenta, que estamos hablando de IO no de SQL. Para comprobarlo, paramos el SQL thread en los dos nodos:
slave1 [localhost] {msandbox} ((none)) > STOP SLAVE SQL_THREAD; Query OK, 0 rows affected (0.00 sec) slave2 [localhost] {msandbox} ((none)) > STOP SLAVE SQL_THREAD; Query OK, 0 rows affected (0.00 sec)
Y a continuación insertamos un dato en el master:
master [localhost] {msandbox} (pruebas) > create table t(i int(10)); Query OK, 0 rows affected (0.00 sec) master [localhost] {msandbox} (pruebas) > SHOW STATUS LIKE 'Rpl_semi_sync%tx'; +-----------------------------+-------+ | Variable_name | Value | +-----------------------------+-------+ | Rpl_semi_sync_master_no_tx | 0 | | Rpl_semi_sync_master_yes_tx | 2 | +-----------------------------+-------+ 2 rows in set (0.00 sec)
Los slaves han recibido los datos, eso es suficiente para el master y se da por bueno. Ahora vamos a parar el IO Thread:
slave1 [localhost] {msandbox} ((none)) > STOP SLAVE IO_THREAD; Query OK, 0 rows affected (0.00 sec) slave2 [localhost] {msandbox} ((none)) > STOP SLAVE IO_THREAD; Query OK, 0 rows affected (0.00 sec)
Y volvemos a meter datos en el master:
master [localhost] {msandbox} (pruebas) > create table z(i int(10)); Query OK, 0 rows affected (10.00 sec) master [localhost] {msandbox} (pruebas) > SHOW STATUS LIKE 'Rpl_semi_sync%tx'; +-----------------------------+-------+ | Variable_name | Value | +-----------------------------+-------+ | Rpl_semi_sync_master_no_tx | 1 | | Rpl_semi_sync_master_yes_tx | 2 | +-----------------------------+-------+ 2 rows in set (0.00 sec)
La query ha tardado 10 segundos en commitearse. Si durante esos 10 segundos ninguno de los slaves ha recibido los binlogs, el master hace commit y se cuenta como una query no sincronizada.
En resumen, nuestra clase política
- "No vamos a llegar a los cuatro millones de parados." Enlace
"El paro supera los cuatro millones de personas por primera vez en la historia" Enlace
- "Zapatero no aceptará "chantajes" ni "cantos cínicos" de los que le piden reducir el gasto social" Enlace
"El Gobierno aprobará hoy un plan de austeridad para recortar en 50.000 millones el gasto del Estado" Enlace
- "Zapatero dice que no tiene intención de subir los impuestos" Enlace
"El Gobierno subirá el IVA dos puntos a partir de julio de 2010" Enlace
- "El Gobierno desmiente a Ordóñez y dice que no peligran las pensiones" Enlace
"Zapatero ve "razonable" retrasar la jubilación para subir las pensiones mínimas y asegurar el futuro del sistema" Enlace
- "Zapatero cree precisas medidas de austeridad para superar la crisis" Enlace
"576 millones de euros hasta 2015 para ayudar al cine español" Enlace
- "El Gobierno inyectará 30.000 millones a bancos y cajas para que den créditos" Enlace
"Zapatero ayuda a la banca... y los directivos de los bancos se suben el sueldo" Enlace
- "No se va a cerrar ninguna página web" Enlace
"Aprobada la ley que permitirá cerrar y bloquear webs con autorización judicial" Enlace
- "No hay riesgo de crisis económica" Enlace
"Zapatero reclama un gran pacto social para superar la crisis" Enlace
- "Zapatero pide el voto para fortalecer los brotes verdes" Enlace
"La sangría continúa: 2010 arrancará destruyendo 250.000 empleos más" Enlace
Así funciona este país. Ahora es el PSOE el que se llena los bolsillos, luego será el PP, de nuevo volverá el PSOE... y de mientras la sociedad jaleando a uno o a otro como si de un partido de fútbol se tratase, sin darse cuenta que todos son LA MISMA PUTA MIERDA.
Particionado Lógico (Parte III)
Gracias a information_schema es posible saber el tamaño que ocupan las tablas de nuestras bases de datos.
Toda la información que nos ofrece information_schema en relación con las tablas es la siguiente:
mysql> desc tables; +-----------------+--------------+------+-----+---------+-------+ | Field | Type | Null | Key | Default | Extra | +-----------------+--------------+------+-----+---------+-------+ | TABLE_CATALOG | varchar(512) | YES | | NULL | | | TABLE_SCHEMA | varchar(64) | NO | | | | | TABLE_NAME | varchar(64) | NO | | | | | TABLE_TYPE | varchar(64) | NO | | | | | ENGINE | varchar(64) | YES | | NULL | | | VERSION | bigint(21) | YES | | NULL | | | ROW_FORMAT | varchar(10) | YES | | NULL | | | TABLE_ROWS | bigint(21) | YES | | NULL | | | AVG_ROW_LENGTH | bigint(21) | YES | | NULL | | | DATA_LENGTH | bigint(21) | YES | | NULL | | | MAX_DATA_LENGTH | bigint(21) | YES | | NULL | | | INDEX_LENGTH | bigint(21) | YES | | NULL | | | DATA_FREE | bigint(21) | YES | | NULL | | | AUTO_INCREMENT | bigint(21) | YES | | NULL | | | CREATE_TIME | datetime | YES | | NULL | | | UPDATE_TIME | datetime | YES | | NULL | | | CHECK_TIME | datetime | YES | | NULL | | | TABLE_COLLATION | varchar(64) | YES | | NULL | | | CHECKSUM | bigint(21) | YES | | NULL | | | CREATE_OPTIONS | varchar(255) | YES | | NULL | | | TABLE_COMMENT | varchar(80) | NO | | | | +-----------------+--------------+------+-----+---------+-------+ 21 rows in set (0.00 sec)
Por ejemplo, si queremos calcular el tamaño de los índices:
mysql> select sum(index_length) from information_schema.tables; +-------------------+ | sum(index_length) | +-------------------+ | 11746971648 | +-------------------+ 1 row in set (3.32 sec)
El valor es en bytes, por lo que el tamaño de los índices es de aproximadamente 11 GB. No está nada mal.
Otra forma de sacar un buen resumen con más datos es la siguiente sentencia SQL que encontramos en mysqlperformanceblog.com:
mysql> SELECT -> count(*) TABLES -> ,concat(round(sum(table_rows)/1000000,2),'K') rows -> ,concat(round(sum(data_length)/(1024*1024*1024),2),'G') DATA -> ,concat(round(sum(index_length)/(1024*1024*1024),2),'G') idx -> ,concat(round(sum(data_length+index_length)/(1024*1024*1024),2),'G') total_size -> ,round(sum(index_length)/sum(data_length),2) idxfrac -> FROM -> information_schema.TABLES; +--------+---------+--------+--------+------------+---------+ | TABLES | rows | DATA | idx | total_size | idxfrac | +--------+---------+--------+--------+------------+---------+ | 929 | 301.52K | 15.73G | 10.94G | 26.67G | 0.70 | +--------+---------+--------+--------+------------+---------+ 1 row in set (3.75 sec)
Tenemos el número de tablas, filas, tamaño de los datos, índices, tamaño total y la proporción índices/datos.
Si lo que deseamos es saber los datos de una tabla en particular, solo es necesario hacer una busqueda más concreta. De esta forma sabremos, por ejemplo, la tabla candidata para el particionado:
mysql> SELECT -> count(*) TABLES -> ,concat(round(sum(table_rows)/1000000,2),'K') rows -> ,concat(round(sum(data_length)/(1024*1024*1024),2),'G') DATA -> ,concat(round(sum(index_length)/(1024*1024*1024),2),'G') idx -> ,concat(round(sum(data_length+index_length)/(1024*1024*1024),2),'G') total_size -> ,round(sum(index_length)/sum(data_length),2) idxfrac -> FROM -> information_schema.TABLES -> WHERE table_name LIKE '%log%'; +--------+---------+-------+-------+------------+---------+ | TABLES | rows | DATA | idx | total_size | idxfrac | +--------+---------+-------+-------+------------+---------+ | 8 | 192.81K | 7.05G | 8.94G | 15.99G | 1.27 | +--------+---------+-------+-------+------------+---------+ 1 row in set (1.76 sec)
En nuestro caso, prácticamente todos los datos pertenecen a la tabla log. Sus indices son mayores que la memoria RAM del equipo (4 GB) por lo que tendríamos una tabla perfecta para particionar. Cuando INDICES > RAM, particionar se hace más rápido que usar índices :)
Estadísticas de SlideShare
Hoy los de Slideshare me han enviado las estadísticas de las trasparencias que tengo publicadas en su web. La verdad es que los datos son interesantes y mucho mejores de lo que me esperaba:
In 2009, you uploaded 9 presentations and got:
17498 views
1944 average views per presentation
10 favorites
4 followers
Your most popular presentation was:
Administración Zimbra
Las 10 novedades de Wii para el 2010
A continuación, las 10 novedades de la consola más vendida entre casuals.
1- 2- 3- 4- 5- 6- 7- 8- 9- 10-
Realmente impresionante, estamos ante una revolución del mundo del videojuego.
Desactivar el Fixup Smtp de Cisco
Si tienes un firewall Cisco entre tu red y el resto del mundo, tendrás problemas con los correos salientes. Por ejemplo, hacer algo tan simple como un relay de tu smtp interno a uno externo no funcionará. Si tienes postfix, este será el mensaje que verás en el log:
postfix/smtp: enabling PIX workarounds: disable_esmtp delay_dotcrlf for ironmail.irontec.com:25
La solución es ejecutar lo siguiente en el Cisco:
en
conf t
no fixup smtp
^Z
wr mem
El origen de este problema es el uso de un Firewall Cisco bugeado:
smtp_pix_workarounds (default: disable_esmtp, delay_dotcrlf)
A list that specifies zero or more workarounds for CISCO PIX firewall bugs. These workarounds are implemented by the Postfix SMTP client. Workaround names are separated by comma or space, and are case insensitive. This parameter setting can be overruled with per-destination smtp_pix_workaround_maps settings.
delay_dotcrlf
Insert a delay before sending ".
disable_esmtp
Disable all extended SMTP commands: send HELO instead of EHLO.
This feature is available in Postfix 2.4 and later. The default settings are backwards compatible with earlier Postfix versions.
Particionado Lógico (Parte II)
Para las prácticas haremos uso de una BBDD de prueba que podemos descargar aquí:
Sample database with test suite
Lo bueno de esta BBDD es que ya viene repletita de datos, por ejemplo la tabla salaries tiene en torno a dos millones de registros. La particionaremos de forma que logremos mejorar el rendimiento. Hay que tener en cuenta que las pruebas se van a hacer sobre un Netbook, por lo que los resultados no son 100% fiables. Nunca pongáis un netbook como servidor de bases de datos en producción u os quedareis ciegos.
El particionado se puede hacer por rangos, listas, hashes y keys:
RANGO
CREATE TABLE employees ( id INT NOT NULL, fname VARCHAR(30), lname VARCHAR(30), hired DATE NOT NULL DEFAULT '1970-01-01', separated DATE NOT NULL DEFAULT '9999-12-31', job_code INT NOT NULL, store_id INT NOT NULL ) PARTITION BY RANGE (store_id) ( PARTITION p0 VALUES LESS THAN (6), PARTITION p1 VALUES LESS THAN (11), PARTITION p2 VALUES LESS THAN (16), PARTITION p3 VALUES LESS THAN (21), PARTITION p3 VALUES LESS THAN MAXVALUE );
Es el particionado más sencillo. En el ejemplo se divide usando el campo store_id, de forma que los valores menores de 6 van a la partición p0, los menores de 11 a la p1, los menores de 16 a la p2, etc. Es importante tener en cuenta la inclusión del MAXVALUE. Ahí es donde irán a parar aquellos registros que no encajen dentro del resto de tablas. Si no pusiéramos un MAXVALUE, MySQL no sabría donde meterlo y daría error.
LISTAS
CREATE TABLE employees ( id INT NOT NULL, fname VARCHAR(30), lname VARCHAR(30), hired DATE NOT NULL DEFAULT '1970-01-01', separated DATE NOT NULL DEFAULT '9999-12-31', job_code INT, store_id INT ) PARTITION BY LIST(store_id) ( PARTITION pNorth VALUES IN (3,5,6,9,17), PARTITION pEast VALUES IN (1,2,10,11,19,20), PARTITION pWest VALUES IN (4,12,13,14,18), PARTITION pCentral VALUES IN (7,8,15,16) );
En este caso, el particionado se realiza en base a una lista de posibles valores. Al contrario que con RANGE, aquí no tenemos MAXVALUE. Por lo que es de suponer, según el diseño de la web, que nunca tendremos valores distintos a los indicados.
HASHES
CREATE TABLE employees ( id INT NOT NULL, fname VARCHAR(30), lname VARCHAR(30), hired DATE NOT NULL DEFAULT '1970-01-01', separated DATE NOT NULL DEFAULT '9999-12-31', job_code INT, store_id INT ) PARTITION BY HASH( YEAR(hired) ) PARTITIONS 4;
Como podéis comprobar, estamos haciendo uso de una columna que no es INT, pero mediante la función YEAR la convertiremos y hacemos uso de ella en la partición. Este truco se suele utilizar muy a menudo para evitar la limitación de particiones en INT. A parte de todo eso, el uso de HASH nos permite dividir los datos de forma equitativa entre todas las particiones, cosa que con otros tipos de particiones podría no pasar. De esta forma, si estamos trabajando una tabla enorme y la queremos dividir en 10 particiones, estas tendrán un número de valores muy similar.
KEY
CREATE TABLE k1 ( id INT NOT NULL PRIMARY KEY, name VARCHAR(20) ) PARTITION BY KEY() PARTITIONS 2;
Con Key no es necesario indicar la columna que deseamos para particionar, en ese caso hará uso de la clave primaria. KEY es muy parecida a HASH, solo que en lugar de indicarle nosotros el HASH mediante una expresión, lo hará el propio MySQL usando MD5.
Dicho esto, comenzamos a particionar nuestra BBDD. La tabla en cuestión será, como ya indique, la de salarios.
mysql [localhost] {msandbox} (employees) > select count() from salaries; +----------+ | count() | +----------+ | 2844047 | +----------+ 1 row in set (1.66 sec)
Casi 3 millones de registros, no está nada mal :) Vamos a hacer una búsqueda para saber cuantos corresponden al año 2000.
mysql [localhost] {msandbox} (employees) > select count() from salaries where from_date between '2000-01-01' and '2000-12-31'; +----------+ | count() | +----------+ | 255785 | +----------+ 1 row in set (2.39 sec)
Fijaros en el tiempo que ha tardado MySQL en realizar la consulta, 2.39 segundos. Bastante tiempo, si en lugar de una sola querie fuesen 100 concurrentes estaríamos ante un verdadero problema de rendimiento.
mysql [localhost] {msandbox} (employees) > explain select count(*) from salaries where from_date between '2000-01-01' and '2000-12-31'\G *************************** 1. row *************************** id: 1 select_type: SIMPLE table: salaries type: index possible_keys: NULL key: emp_no key_len: 4 ref: NULL rows: 2844513 Extra: Using where; Using index 1 row in set (0.00 sec)
La querie se tiene que recorrer los 3 millones de registros usando un índice (lo cual no está mal) y aún así el rendimiento es pobre. Entonces, pidamos ayuda a nuestras amigas las particiones. Vamos a particionar por año :)
ALTER TABLE salaries PARTITION by range (year(from_date)) ( partition p001 VALUES LESS THAN (1986) , partition p002 VALUES LESS THAN (1987) , partition p003 VALUES LESS THAN (1988) , partition p004 VALUES LESS THAN (1989) , partition p005 VALUES LESS THAN (1990) , partition p006 VALUES LESS THAN (1991) , partition p007 VALUES LESS THAN (1992) , partition p008 VALUES LESS THAN (1993) , partition p009 VALUES LESS THAN (1994) , partition p010 VALUES LESS THAN (1995) , partition p011 VALUES LESS THAN (1996) , partition p012 VALUES LESS THAN (1997) , partition p013 VALUES LESS THAN (1998) , partition p014 VALUES LESS THAN (1999) , partition p015 VALUES LESS THAN (2000) , partition p016 VALUES LESS THAN (2001) , partition p017 VALUES LESS THAN (2002) , partition p018 VALUES LESS THAN (2003) );
Si ahora hacemos la misma select de antes:
mysql [localhost] {msandbox} (employees) > select count() from salaries where from_date between '2000-01-01' and '2000-12-31'; +----------+ | count() | +----------+ | 255785 | +----------+ 1 row in set (0.22 sec)
La búsqueda de los salarios del año 2000. Antes 2,39 segundos, ahora 0,22. La mejora es impresionante.
Un explain partitions nos indica que partición ha usado para la select:
mysql [localhost] {msandbox} (employees) > explain partitions select count(*) from salaries where from_date between '2000-01-01' and '2000-12-31'\G *************************** 1. row *************************** id: 1 select_type: SIMPLE table: salaries partitions: p016 type: index possible_keys: NULL key: emp_no key_len: 4 ref: NULL rows: 2830488 Extra: Using where; Using index 1 row in set (0.00 sec)
Particionado Lógico (Parte I)
Desde la versión 5.1 existe la posibilidad de particionar nuestras tablas de forma horizontal (en líneas), algo que nos puede ayudar en casos puntuales a mejorar el rendimiento de nuestra base de datos. Resumiendo, este sistema nos permite dividir lógicamente una tabla muy grande en otras más pequeñas, dentro de un rango de valores que nosotros indiquemos, de forma que la consulta de datos sea más rápida. Su uso es muy sencillo pero... ¿cuando debemos utilizarlo?
- Cuando la tabla sea tan grande que los índices no entren en RAM.
- Cuando tengamos una tabla realmente grande (no hablo de megas).
- Cuando almacenamos datos históricos.
- Cuando queremos rotar datos.
- Cuando los datos no paran de crecer y crecer...
Hay que tener en cuenta que este particionado es totalmente transparente para el usuario (y por lógica también para nuestra aplicación) por lo que en el caso de decidirnos por esta solución el cambio será poco dramático. Solamente tendremos que tener en cuenta estos detalles:
- La columna que utilicemos para definir el rango de las particiones debe ser un INT, no se acepta cualquier otro valor.
- Si tenemos una clave única o una primary key, esta debe usarse para particionar.
- Como máximo se permiten 1024 particiones.
- No se permiten claves externas.
- No se permiten busquedas FULL TEXT.
Si tenemos una tabla con millones de registros y hacemos una select, MySQL se deberá recorrer toda la tabla (en caso de no usar índices, si estos ocupan más que la RAM) o se tendrá que recorrer todos los índices. Esto, contra más grande es la tabla, mas ineficiente es:

Gracias al particionado es posible hacer una busqueda en una fracción mucho más pequeña de nuestra tabla. Si la dividimos de forma que cada partición incluya 100 filas (particionando por ID), MySQL sabe que el dato se tiene que encontrar en la segunda partición, por lo que se evita tener que buscar en los restantes X millones de registros.

La mejora es clara, pero no siempre particionar nos va a dar mayor rendimiento. Si la tabla no es lo suficientemente grande incluso podemos degradar el rendimiento, ya que añadimos overhead a una base de datos que no necesita grandes recursos para funcionar. Más adelante veremos como implementar el sistema y también como benchmarkear la solución para conocer si nos puede salvar la vida o no.
Bug en Plesk. Evitando los filtros de Spam.
Hace ya algunos meses me puse en contacto con Parallels desde su web y no he recibido respuesta, por lo que supongo que no estarán interesados en solucionar el fallo.
Es posible enviar correos con publicidad no deseada (SPAM) a servidores que tengan instalado Plesk para Windows, la combinación es MailEnable + SpamAssassin + Plesk 9.2.3. Para evitar el filtro AntiSpam únicamente hay que:
- No poner la cabecera Subject.
- Entre la cabecera Subject y el resto del cuerpo del mensaje no dejar una línea en blanco.
Listo, con esto tu sistema de correo se tragará completamente el correo de Spam sin siquiera analizarlo. ¿Bonito verdad?
No he probado con otras versiones ni con otros sistemas operativos. Si alguien lo prueba y tiene resultados parecidos (o no) que me comente :)
Ya soy 1/2 CCNA
Pasé el primer examen de los dos que hacen falta para ser CCNA. El examen ha sido bastante difícil en algunas partes, más aún cuando te encuentras con preguntas sobre Frame Relay que son del segundo libro. Me tiré como 20 minutos probando comandos show hasta que logré entender la dichosa pregunta y saber que leches tenia que responder... Era algo de saber la dirección en capa 2 de Frame Relay cuando el destino es una ip especifica.
¡Yo que coño se! ¡Eso es para el segundo examen cabrones!
Si soy sincero, cuando estaba por la mitad de examen tenia 0 de confianza en un aprobado, más bien pensé que iba como el culo. Llegó un momento en el que casi no me paraba a leer la pregunta, lo miraba un poco por encima y respondía "total... he suspendido". Que cara de idiota que se me quedó cuando en la pantalla ponía "PASS" XD
La parte de subnetting he tenido un 100% de aciertos, es gracioso verme haciendo las operaciones matemáticas más simples con los dedos... "a ver... mmm 192 + 32 + ... mmm... 128 + 32... mmm no... esto... ufff...". Pero bueno, a mi velocidad, poco a poco, pero los saqué todos. Quiero aprovechar para dar las gracias a Mikel por a increíble paciencia que ha tenido explicándome su método para hacer subnetting, mil veces mejor que el del libro :)
Ahora me tomaré un descanso de certificados hasta enero o febrero... Hoy cuando vuelva a casa de trabajar y pueda tocarme las pelotas a dos manos, sin tener que estudiar nada, será la puta ostia xD










