Pruebas de rendimiento IRIS - PostgreSQL - MySQL
Como antiguo programador de JAVA siempre ha sido un reto decidir qué base de datos era la más adecuada para el proyecto que íbamos a desarrollar, uno de los principales criterios que usaba era el del rendimiento de las mismas, así como sus capacidades de configuración HA (high availability o alta disponibilidad). Pues bien, es el momento de poner a prueba a IRIS respecto a algunas de las bases de datos más comunmente usadas, por eso me he propuesto elaborar un pequeño proyecto de Java basado en SpringBoot que conecte vía JDBC con una base de datos MySQL, otra de PostgreSQL y finalmente con IRIS.
Vamos a aprovechar que disponemos de imágenes de Docker de estas bases de datos para usarlas en nuestro proyecto y permitiros probarlo vosotros mismos sin necesitar de realizar ninguna instalación. Podemos comprobar la configuración de docker en nuestro archivo docker-compose.yml
version:"2.2"services:# mysql mysql: build: context:mysql container_name:mysql restart:always command:--default-authentication-plugin=mysql_native_password environment: MYSQL_ROOT_PASSWORD:SYS MYSQL_USER:testuser MYSQL_PASSWORD:testpassword MYSQL_DATABASE:test volumes: -./mysql/sql/dump.sql:/docker-entrypoint-initdb.d/dump.sql ports: -3306:3306# postgres postgres: build: context:postgres container_name:postgres restart:always environment: POSTGRES_USER:testuser POSTGRES_PASSWORD:testpassword volumes: -./postgres/sql/dump.sql:/docker-entrypoint-initdb.d/dump.sql ports: -5432:5432 adminer: container_name:adminer image:adminer restart:always depends_on: -mysql -postgres ports: -8081:8080# iris iris: init:true container_name:iris build: context:. dockerfile:iris/Dockerfile ports: -52773:52773 -1972:1972 command:--check-capsfalse# tomcat tomcat: init:true container_name:tomcat build: context:. dockerfile:tomcat/Dockerfile volumes: -./tomcat/performance.war:/usr/local/tomcat/webapps/performance.war ports: -8080:8080Con un rápido vistazo veremos que estamos usando las siguientes imágenes:
- IRIS: instancia de IRIS Community a la que nos conectaremos vía JDBC.
- Postgres: imagen de la base de datos PostgreSQL escuchando en el puerto 5432.
- MySQL: imagen de la base de datos MySQL escuchando en el puerto 3306.
- Tomcat: imagen de Docker configurado con un servidor de aplicaciones Apache Tomcat sobre la que desplegaremos el archivo WAR de nuestra aplicación.
- Adminer: administrador de base de datos que nos permitirá consultar las bases de datos de Postgres y MySQL.
Como véis tenemos configurado los puertos de escucha para que se mapeen también en nuestro equipo, no sólo dentro de Docker. Para el caso de las bases de datos no sería necesario, ya que la conexión se realizará dentro de los contenedores de Docker, por lo que, si tenéis algún problema por los puertos podéis borrar la linea ports del archivo docker-compose.yml
Cada imagen de base de datos está ejecutando un script previo que creará las tablas necesarias para las pruebas de rendimiento, veamos uno de los archivos dump.sql
CREATESCHEMAtest;
DROPTABLEIFEXISTS test.patient;
CREATETABLE test.country (
idINT PRIMARY KEY,
nameVARCHAR(225)
);
CREATETABLE test.city (
idINT PRIMARY KEY,
nameVARCHAR(225),
lastname VARCHAR(225),
photo BYTEA,
phone VARCHAR(14),
address VARCHAR(225),
country INT,
CONSTRAINT fk_country
FOREIGN KEY(country)
REFERENCES test.country(id)
);
CREATETABLE test.patient (
idINTGENERATEDBYDEFAULTASIDENTITY PRIMARY KEY,
nameVARCHAR(225),
lastname VARCHAR(225),
photo BYTEA,
phone VARCHAR(14),
address VARCHAR(225),
city INT,
CONSTRAINT fk_city
FOREIGN KEY(city)
REFERENCES test.city(id)
);
INSERTINTO test.country VALUES (1,'Spain'), (2,'France'), (3,'Portugal'), (4,'Germany');
INSERTINTO test.city VALUES (1,'Madrid',1), (2,'Valencia',1), (3,'Paris',2), (4,'Bordeaux',2), (5,'Lisbon',3), (6,'Porto',3), (7,'Berlin',4), (8,'Frankfurt',4);
Vamos a crear 3 tablas para nuestras pruebas, patient, city y country, estas dos últimas van a tener datos precargados de ciudades y países.
Perfecto, a continuación vamos a ver como realizaremos las conexiones a la base de datos.
Para ello hemos creado nuestro proyecto de Java utilizando un proyecto preconfigurado de Spring Boot disponible desde Visual Studio Code que nos proporciona la estructura básica.
.png)
No os preocupéis si no entendéis a primera vista la estructura del proyecto, el objetivo no es aprender Java, pero aún así vamos a explicar un poco más en detalle los principales documentos.
MyDataSourceFactory.java
Clase de java que a abre las conexiones a las diferentes bases de datos.
PerformancerController.java
Controlador encargado de la publicación de los endpoints a los que llamaremos desde Postman.
application.properties
Archivo de configuración con las diferentes conexiones a las bases de datos desplegadas en nuestro Docker.
.png)
Como podéis ver las URL de conexión utilizan el nombre del contenedor ya que, al desplegarse en un contenedor de Tomcat, las bases de datos serán accesibles por nuestra aplicación de Java únicamente con el nombre del contenedor correspondiente. También podemos comprobar como la URL está realizando una conexión vía JDBC a nuestras bases de datos. Las librerías de Java usadas en el proyecto están definidas en el archivo pom.xml
Si modificáis el código fuente sólo deberéis ejecutar el comando:
mvn packageY este os generará un archivo performance-0.0.1-SNAPSHOT.war, renombrarlo a performance.war y movedlo al directorio /tomcat, reemplazando al ya existente.
Tal y como está el proyecto en GitHub sólo necesitaremos clonarlo en nuestro equipo desde Visual Studio y ejecutar en el terminal los siguientes comandos:
docker-compose build
docker-compose up -dComprobemos el portal del Docker:
.png)
¡Genial! Contenedores del Docker funcionando. Ahora comprobemos desde nuestro Adminer y el portal de gestión de IRIS que nuestras tablas se han creado correctamente.
Accedamos primeramente a la base de datos MySQL. Si consultáis el archivo docker-compose.yml veremos que el usuario y la contraseña definidas para MySQL y PostgreSQL son los mismos testuser/testpassword
Aquí tenemos nuestras tres tablas dentro de nuestra base de datos Test, veamos nuestra base de datos PostgreSQL:
.png)
Seleccionemos la base de datos testuser y el esquema test:
.png)
Aquí tenemos nuestras tablas perfectamente creadas en PostgreSQL. Comprobemos finalmente que todo está bien configurado en IRIS:
Todo correcto, tenemos nuestras tablas creadas en el Namespace USER bajo el schema Test.
Muy bien, una vez terminadas las comprobaciones, let's rock! Para ello usaremos Postman, en el que cargaremos el fichero adjunto al proyecto performance.postman_collection.json
.png)
Estas son las diferentes pruebas que vamos a lanzar, empezaremos con inserciones y continuaremos con consultas sobre la base de datos. No he incluido ningún tipo de índice más allá de los que se crean automáticamente al definir claves primarias en las diferentes bases de datos.
Insert
Llamada REST: GET http://localhost:8080/performance/tests/insert/{database}?total=1000
La variable {database} podrá tener los siguientes valores:
- postgres
- mysql
- iris
Y el atributo total será el que modificaremos para indicar el total de inserciones que queremos realizar.
El método que se invocará se llama insertRecords y lo podéis encontrar en el archivo de java PerformanceController.java ubicado en /src/main/java/com/performance/controller/, podéis comprobar que es un insert extremadamente sencillo:
INSERTINTO test.patient VALUES (null, ?, ?, null, ?, ?, ?)El primer indicado es nulo al ser el índice autogenerado y el segundo nulo corresponde a un campo de tipo BLOB/BYTEA/LONGVARBINARY donde registraremos una foto posteriormente.
Vamos a lanzar los siguientes lotes de inserciones: 100, 1000 , 10000, 20000 y comprobaremos los tiempos de respuesta que recibiremos en el Postman. Para cada medida haremos 3 pruebas y calcularemos la media de los 3 valores que obtengamos.
| 100 | 1000 | 10000 | 20000 | |
| MySQL | 0.754 | 8.91 s | 88 s | 192 s |
| PostgreSQL | 0.23 s | 2.24 s | 20.92 s | 40.35 s |
| IRIS | 0.07 s | 0.33 s | 2.6 s | 5 s |
Veamoslo gráficamente.
.png)
Insert con archivo
En el anterior ejemplo hemos realizado inserciones simples, vamos a forzar un poco más la máquina incluyendo en el insert una imagen de 50 KB a modo de foto de cada paciente.
Llamada REST: GET http://localhost:8080/performance/tests/insertBlob/{database}?total=1000
La variable {database} podrá tener los siguientes valores:
- postgres
- mysql
- iris
Y el atributo total será el que modificaremos para indicar el total de inserciones que queremos realizar.
El método que se invocará se llama insertBlobRecords y lo podéis encontrar en el archivo de java PerformanceController.java ubicado en /src/main/java/com/performance/controller/, podéis comprobar que es un insert similar al anterior con la salvedad de que estamos pasando el archivo en en insert:
INSERTINTO test.patient (Name, Lastname, Photo, Phone, Address, City) VALUES (?, ?, ?, ?, ?, ?)Modifiquemos ligeramente el número de inserciones anterior para evitar que se eternice la prueba y limpiare el Docker de las imágenes para partir nuevamente de una total igualdad de condiciones.
| 100 | 1000 | 5000 | 10000 | |
| MySQL | 1.87 s | 17 s | 149 s | 234 s |
| PostgreSQL | 0.6 s | 5.22 s | 23.93 s | 60.43 s |
| IRIS | 0.13 s | 0.88 s | 4.58 s | 12.57 s |
Consultemos la gráfica:
.png)
Select
Comprobemos el rendimiento con una consulta simple que nos obtenga todos los registros de la tabla Patient.
Llamada REST: GET http://localhost:8080/performance/tests/select/{database}
La variable {database} podrá tener los siguientes valores:
- postgres
- mysql
- iris
El método que se invocará se llama selectRecords y lo podéis encontrar en el archivo de java PerformanceController.java ubicado en /src/main/java/com/performance/controller/, la consulta es extremadamente básica:
SELECT * FROM test.patientProbaremos la consulta con el mismo conjunto de elementos que usamos para la primera prueba de inserción.
| 100 | 1000 | 10000 | 20000 | |
| MySQL | 0.03 s | 0,02 s | 0.03 s | 0.04 s |
| PostgreSQL | 0.03 s | 0.02 s | 0.04 s | 0.03 s |
| IRIS | 0.02 s | 0.02 s | 0.04 s | 0.05 s |
Y graficamente:
.png)
Select group by
Comprobemos el rendimiento con una consulta que incluya un left join así como funciones de agregación.
Llamada REST: GET http://localhost:8080/performance/tests/selectGroupBy/{database}
La variable {database} podrá tener los siguientes valores:
- postgres
- mysql
- iris
El método que se invocará se llama selectGroupBy y lo podéis encontrar en el archivo de java PerformanceController.java ubicado en /src/main/java/com/performance/controller/, veamos la consulta:
SELECTcount(p.Name), c.Name FROM test.patient p leftjoin test.city c on p.City = c.Id GROUPBY c.NameProbaremos la consulta nuevamente con el mismo conjunto de elementos que usamos para la primera prueba de inserción.
| 100 | 1000 | 10000 | 20000 | |
| MySQL | 0.02 s | 0.02 s | 0.03 s | 0.03 s |
| PostgreSQL | 0.02 s | 0.02 s | 0.02 s | 0.02 s |
| IRIS | 0.02 s | 0.02 | 0.03 s | 0.04 s |
Y gráficamente:
.png)
Update
Para el update vamos a lanzar una query con una subquery asociada dentro de sus condiciones.
Llamada REST: GET http://localhost:8080/performance/tests/update/{database}
La variable {database} podrá tener los siguientes valores:
- postgres
- mysql
- iris
El método que se invocará se llama UpdateRecords y lo podéis encontrar en el archivo de java PerformanceController.java ubicado en /src/main/java/com/performance/controller/, veamos la consulta:
UPDATE test.patient SET Phone = '+15553535301'WHERENamein (SELECTNameFROM test.patient whereNamelike'%12')Lancemos la consulta y veamos los resultados.
| 100 | 1000 | 10000 | 20000 | |
| MySQL | X | X | X | X |
| PostgreSQL | 0.02 s | 0.02 s | 0.02 s | 0.03 s |
| IRIS | 0.02 s | 0.02 s | 0.02 s | 0.04 s |
Observamos que MySQL no permite este tipo de subqueries sobre la misma tabla que vamos a actualizar, por lo tanto no podemos medir sus tiempos en igualdad de condiciones. Obviaremos en este caso la gráfica al ser tan simple.
Delete
Para el delete vamos a lanzar una query con una subquery asociada dentro de sus condiciones.
Llamada REST: GET http://localhost:8080/performance/tests/delete/{database}
La variable {database} podrá tener los siguientes valores:
- postgres
- mysql
- iris
El método que se invocará se llama DeleteRecords y lo podéis encontrar en el archivo de java PerformanceController.java ubicado en /src/main/java/com/performance/controller/, veamos la consulta:
DELETE test.patient WHERENamein (SELECTNameFROM test.patient whereNamelike'%12')Lancemos la consulta y veamos los resultados.
| 100 | 1000 | 10000 | 20000 | |
| MySQL | X | X | X | X |
| PostgreSQL | 0.01 s | 0.02 s | 0.02 s | 0.03 s |
| IRIS | 0.02 s | 0.02 s | 0.02 s | 0.04 s |
Observamos nuevamente que MySQL no permite este tipo de subqueries sobre la misma tabla de la que vamos a eliminar, por lo tanto no podemos medir sus tiempos en igualdad de condiciones.
Conclusiones
Podemos afirmar que todas ellas están bastante afinadas de cara a la consulta de datos, lo mismo que en las actualizaciones y eliminaciones de resgistros (salvo la incidencia con MySQL). Donde encontramos la mayor diferencia es en la gestión de las inserciones. IRIS es, sin lugar a dudas, la mejor de las 3 con diferencia, es 6 veces más rápida que PostgreSQL y hasta 20 veces más rápida que MySQL en la ingesta de datos.
De cara a operar con grandes conjuntos de datos IRIS es sin duda la mejor opción dentro de las pruebas realizadas.
Así que...¡ya tenemos vencedor! ¡IRIS WINS!
Pd.: Estos son unos pequeños ejemplo de pruebas que podéis realizar, sentíos libres de modificar el código a vuestro antojo.