Conceptos básicos sobre IOPS / Medir la velocidad de discos duros o RAM (RAMDISK / tmpfs)

El rendimiento de operaciones de entrada y salida están directamente relacionadas con la aplicación que realiza dichas operaciones, el sistema de ficheros en uso, el IO scheduler, configuración de la cola, el dispositivo hardware en uso, su driver, etc. Las herramientas que visualizan estadísticas de entrada y salida suelen hacerlo consultando estos ficheros.

IOPS con iostat (tps): Número de transferencias por segundo = operaciones I/O por segundo.

iostat 
Linux 3.10.0-862.2.3.el7.x86_64 (localhost.localdomain) 	11/25/2018 	_x86_64_	(2 CPU)
 
avg-cpu:  %user   %nice %system %iowait  %steal   %idle
           1.38    0.00    4.04    0.28    0.00   94.30
 
Device:            tps    kB_read/s    kB_wrtn/s    kB_read    kB_wrtn
sda              57.82      2212.83       145.44     191034      12556
 
# La opción -x proporciona una salida más detallada y separa las lecturas y escrituras informando de cuántos datos entran y salen por segundo.
 
iostat -x && iostat
Linux 3.10.0-862.2.3.el7.x86_64 (localhost.localdomain) 	11/25/2018 	_x86_64_	(2 CPU)
 
avg-cpu:  %user   %nice %system %iowait  %steal   %idle
           0.05    0.00    0.16    0.01    0.00   99.78
 
Device:         rrqm/s   wrqm/s     r/s     w/s    rkB/s    wkB/s avgrq-sz avgqu-sz   await r_await w_await  svctm  %util
sda               0.00     0.11    1.78    0.42    78.67     7.18    78.13     0.00    1.86    1.38    3.90   0.36   0.08
 
 
# En discos SSD los campos svctm / %util no muestran los valores reales: https://brooker.co.za/blog/2014/07/04/iostat-pct.html

Si se está usando LVM, se encontrará en la salida de iostat información referente a los mapeados de dispositivos (dm-0, dm-1, etc). Con estos comandos se puede averiguar a qué disco / partición se está refiriendo el mapeado.

dmsetup ls
luks-20b2aaf7-9353-4732-8f62-ff83bb5e54b7	(254:3)   <--- dm-3
Volgroup00-lv_swap	(254:1)                           <--- dm-1
Volgroup00-lv_root	(254:0)                           <--- dm:0
Volgroup00-cifrado	(254:2)                           <--- dm:2
 
lvdisplay|awk  '/LV Name/{n=$3} /Block device/{d=$3; sub(".*:","dm-",d); print d,n;}'
dm-0 lv_root
dm-1 lv_swap
dm-2 cifrado
 
# Conocer a qué dispositivo / partición física se refiere el volumen lógico.
 
lvs -o +seg_pe_ranges
  LV      VG         Attr       LSize    Pool Origin Data%  Meta%  Move Log Cpy%Sync Convert PE Ranges             
  cifrado Volgroup00 -wi-ao---- <549,76g                                                     /dev/sdb4:47682-188419
  lv_root Volgroup00 -wi-a-----  416,88g                                                     /dev/sdb4:0-47103     
  lv_root Volgroup00 -wi-a-----  416,88g                                                     /dev/sda1:0-59617     
  lv_swap Volgroup00 -wc-a-----   <2,26g                                                     /dev/sdb4:47104-47681 

Se debe tener en cuenta que una transferencia no tiene un tamaño determinado y esta influye directamente sobre el número de operaciones. Por ejemplo dos operaciones de 4k que sean secuenciales pueden ser fusionadas en una sola operación I/O. En iostat esa agrupación de transferencias en una sola operación se pueden ver en los campos rrqm/s (lectura) y wrqm/s (escritura), es habitual ver métricas activas en esos campos cuando hay lecturas o escrituras secuenciales.

Lo mejor es tener estadísticas sobre la cantidad de IOPS que se tienen de manera habitual, para así poder detectar problemas una vez que estas métricas varíen. Como se verá más adelante la cantidad de IOPS por si misma no tiene ningún tipo de relevancia ni puede indicar por si sola mejoras de rendimiento, problemas o denotar una falta de recursos.

El porcentaje de espera a transacciones de entrada y salida que tiene la CPU se denomina como %iowait, pero este valor puede ser en algunos casos engañoso. Si por ejemplo se tiene de media un porcentaje %iowait de 5 y se cambia la CPU por otra el doble de potente y se obtienen de nuevo métricas, se verá que ese porcentaje de espera ha aumentado, pese a que las transacciones se han realizado más deprisa (han tardado menos). Esto se debe a que la la nueva CPU permite esperar más rápidamente.

Antes de la actualización de la CPU.

Tiempo de CPU = 40 ms
Tiempo IO = 20 ms
Tiempo total de transacción = CPU + IO = 40 + 20 = 60 ms
% iowait = tiempo IO / tiempo total = 20/60 = 33%

Después de la actualización de la CPU.

Tiempo de CPU = 20 ms
Tiempo IO = 20 ms
Tiempo total de transacción = CPU + IO = 20 + 20 = 40 ms
% iowait = 20/40 = 50% 

Conceptos sobre IOPS / Latencia y velocidad de transferencia.

Hay tres factores a la hora de definir el rendimiento de un dispositivo / servicio de almacenamiento. Estos serían la frecuencia (IOPS), latencia y velocidad de transferencia, la cual puede varias dependiendo del dispositivo servicio e la nube contratado. Actualmente los proveedores no paran de repetir cifras muy altas de IOPS a la hora de querer vender sus productos. Pero si estas cifras no están referenciadas a un uso determinado real (Bases de datos, Backups, etc) del dispositivo de almacenamiento, realmente carecen de credibilidad. La gran mayoría de valores IOPS que muestran los fabricantes son obtenidos mediante tests que poco se asemejan a las operaciones E/S realizadas en el mundo real. Algunos proveedores de nube sí especifican la cantidad de IOPS que se pueden llegar a tener dependiendo del tipo de volumen en uso, por ejemplo teniendo volúmenes especiales para operaciones de I/O grandes y secuenciales y otros más indicados para correr servicios que requieran de muchas y pequeñas operaciones aleatorias (no secuenciales).

IOPS Benchmarks más comunes

La frecuencia con la que un dispositivo de almacenamiento puede realizar tareas de IO se mide en operaciones de entrada / salida por segundo ( IOPS ) y varía según el tipo de operaciones que se realicen (números de bytes en las operaciones), es una unidad de benchmark. Las operaciones se miden en KiB y la tecnología de disco subyacente determina la cantidad máxima de datos que un tipo de volumen cuenta como una operación única de I/O. Depende de la tecnología, si las as operaciones de I/O pequeñas están contiguas físicamente, estas pueden ser combinadas combinadas en una única operación E/S hasta el tamaño máximo. El tamaño de I/O se limita a 256 KiB para los volúmenes SSD y 1.024 KiB para los volúmenes HDD. Por eso los volúmenes SSD controlan las I/O pequeñas o aleatorias con mucha más eficacia que los volúmenes HDD. Por ejemplo en un SSD una única operación de I/O de 1.024 KiB puede suponer 4 IOPS. Por otro lado 8 operaciones de I/O contigua a 32 KiB cuenta como una operación (8×32=256), mientras que si estas fueran aleatorias serían 8 IOPS.

No hay una forma estándar de medir IOPS, y la definición oficial de IOPS no especifica ciertos parámetros extremadamente importantes para poder ser usados como referencia. Por lo que cada fabricante / proveedor lo hace como le parece. Por lo tanto cuando se nos presenta una cifra de IOPS sin estar vinculada a una latencia promedio y un cierto tamaño de solicitud tanto de lectura como de escritura, debemos desconfiar. Por lo tanto cada prueba de IOPS que realicemos está marcando una referencia que solo tiene validez para dicha prueba específica. Un sistema que puede hacer un millón de IOPS de 512 bytes no necesariamente puede hacer un millón de IOPS de 256K, por eso es importante probar los dispositivos antes de hacer una compra importante.

Amazon mantiene una buena documentación sobre sus tipos de volumenes que sirve como ejemplo a lo comentado anteriormente.

Tipos de volumen de Amazon EBS: https://docs.aws.amazon.com/es_es/AWSEC2/latest/UserGuide/EBSVolumeTypes.html

La longitud de la cola del volumen es el número de solicitudes de I/O pendientes de un dispositivo. La latencia es el tiempo que tarda en despacharse una solicitud de E/S desde el punto de vista de la aplicación. Es decir, el tiempo real desde que se solicitan los datos hasta que estos están accesibles. La longitud de cola óptima varía con cada carga de trabajo, dependiendo de la sensibilidad específica de la aplicación a las operaciones de IOPS y la latencia.

Aplicaciones que requieren una alta intensidad de transacciones como las bases de datos son muy sensibles a la latencia. Estas realizan varios tipos de solicitudes que deben reconocerse rápidamente, idealmente 10 ms para lecturas y menos de 5 ms para escrituras. Por otro lado, las aplicaciones que realizan operaciones de E/S secuencial como es la copia de archivos no son tan sensibles a la latencia y no suelen necesitar un valor de IOPS alto (sino que necesitan una alta capacidad de transferencia Mb/s). De ello se deduce que no todos los tamaños y operaciones de E/S están sujetas a los mismos requisitos de latencia.

Medir la latencia de los IOPS en tiempo real se puede hacer con la herramienta IOping.

La velocidad a la que los datos se transfieren desde o hacia el dispositivo de almacenamiento se mide en bytes por segundo, normalmente kilobytes y megabytes por segundo. Esto suele depender, entre otras cosas, del tipo de conexión utilizada IDE/SATA/PCI/PCIe/etc.

Los dispositivos SSD de primera generación a menudo tienen un tamaño de sector de 4KB. Realmente no son sectores si no celdas que se agrupan en páginas de 4Kb. Algunos dispositivos más nuevos tienen un tamaño de 16KB y otros permiten modificar el tamaño. Por lo tanto siempre es bueno conocer el tamaño de sector en uso y así ajustarlo en la aplicación para tener el mejor rendimiento. Por ejemplo el tamaño de página predeterminado de InnoDB en MySQL es 16KB, este encajaría a la perfección si el disco SSD usa ese mismo tamaño de sector (Opción innodb_page_size).De todas maneras se recomenda encarecidamente siempre realizar múltiples pruebas antes de dar nada por sentado ya que kernel del sistema operativo también podría influir.

Medir velocidad de lectura / escritura.

En GNU/Linux hay varias utilidades que se pueden utilizar para medir el rendimiento de dispositivos de bloque o memorias RAM (ramdisks). La recomendación principal es realizar las pruebas desde otro sistema que no esté utilizando el dispositivo de bloque que se quiere comprobar (liveCDs), si eso no es posible, el modo de arranque single permite arrancar GNU/Linux con los mínimos servicios.

Se recomienda realizar las pruebas de lectura y escritura más de una vez y si es posible, usando diferentes valores (comando dd) para tener resultados más certeros.

NOTA: Aquí se mostrarán ejemplos con el comando dd, pero en la parte final del artículo encontrará herramientas mejores dedicas a este tipo de pruebas de rendimiento que permiten obtener valores IOPS exactos.

Para medir la velocidad de lectura se puede utilizar hdparm de la siguiente manera (Independiente de la alineación del particionado).

hdparm -tT /dev/sdb

/dev/sdb:
 Timing cached reads:   6652 MB in  2.00 seconds = 3326.98 MB/sec
 Timing buffered disk reads: 448 MB in  3.01 seconds = 148.91 MB/sec

Otra utilidad muy popular utilizada para este tipo de pruebas es dd. La opción “conv=fdatasync” de dd se usa para asegurar que los datos son escritos a disco y no se usan memorias cachés que puedan dar resultados equivocados al efectuar los tests. Desactivar la cache de escritura con hdparm es también recomendado antes del uso de dd.

Comprobar la velocidad de escritura de un dispositivo en GNU/Linux usando fio. (Recomendado leer el manual)

Fio es la abreviatura de Flexible IO, un generador de carga de trabajo versátil de Entrada / Salida. En 2005, Jens Axboe, autor de la pila I/O en el kernel de Linux, cansado de escribir constantemente programas de prueba únicos para evaluar o verificar los cambios en el subsistema de I/O de Linux escribió fio. Hoy en día, la comunidad de usuarios de fio está activa y comprometida con el desarrollo, y por lo tanto, continuamente desarrollan fio e implementan nuevas funciones. Su flexibilidad permite ejecutar cualquier carga de trabajo deseada y obtener muchas unidades diferentes en el resultado. Al poder usarse en diferentes sistemas operativos lo hacen ideal para comparar el mismo tests entre diferentes sistemas. También permite obtener el resultado en remoto, útil si se está ejecutando en varias VMs.

Se mostrarán unos ejemplo de pruebas de lectura y escritura tanto en posiciones aleatorias como secuenciales.

Lectura / escritura aleatoria: Se crea un fichero de 4Gb, se realizan lecturas y escrituras usando 4Kb. Por cada escritura se realizan 3 lecturas (--rwmixread=75) y 64 operaciones de manera simultanea.

fio --randrepeat=1 --ioengine=libaio --direct=1 --gtod_reduce=1 --name=test --filename=random_read_write.fio --bs=4k --iodepth=64 --size=4G --readwrite=randrw --rwmixread=75

Lectura aleatoria (Como en el anterior caso, pero solo lectura).

fio --randrepeat=1 --ioengine=libaio --direct=1 --gtod_reduce=1 --name=test --filename=random_read.fio --bs=4k --iodepth=64 --size=4G --readwrite=randread

Escritura aleatoria (Como en el anterior caso, pero solo lectura).

fio --randrepeat=1 --ioengine=libaio --direct=1 --gtod_reduce=1 --name=test --filename=random_write.fio --bs=4k --iodepth=64 --size=4G --readwrite=randwrite

Lectura / escritura secuencial: Se crea un fichero de 4Gb, se realizan lecturas y escrituras usando 4Kb. Por cada escritura se realizan 3 lecturas (–rwmixread=75) y 64 operaciones de manera simultanea. Los campos rrqm/s (lectura) y wrqm/s (escritura) de iostat se dispararán al visualizar métricas durante el test.

fio --randrepeat=1 --ioengine=libaio --direct=1 --gtod_reduce=1 --name=test --filename=random_read_write.fio --bs=4k --iodepth=64 --size=4G --readwrite=rw --rwmixread=75

Lectura secuencial (Como en el anterior caso, pero solo lectura).

fio --randrepeat=1 --ioengine=libaio --direct=1 --gtod_reduce=1 --name=test --filename=random_read.fio --bs=4k --iodepth=64 --size=4G --readwrite=read

Escritura secuencial (Como en el anterior caso, pero solo lectura).

fio --randrepeat=1 --ioengine=libaio --direct=1 --gtod_reduce=1 --name=test --filename=random_write.fio --bs=4k --iodepth=64 --size=4G --readwrite=write

NOTA: Una opción también interesante es “numjobs”, que permite crear más hilos con exactamente el mismo test. Si los test son intensos y los multiplicamos aumentará más fácilmente el porcentaje de “IO Waits” del sistema.

Salida de fio.

# Nombre de fichero, grupo, bitrate, motor y número de solicitudes paralelas
test: (g=0): rw=randrw, bs=(R) 4096B-4096B, (W) 4096B-4096B, (T) 4096B-4096B, ioengine=libaio, iodepth=64 
# Versión de fio.
fio-3.12
# Inicia el proceso y como se definió el comando creará un fichero "test" de 1G.
Starting 1 process
test: Laying out IO file (1 file / 1024MiB)
# Hilo (trabajo / job), puede usarse la opción "numjobs" para crear más hilos de procesamiento.
# Métricas en tiempo real, una vez finalizado se muestran las métricas del ultimo segundo.
Jobs: 1 (f=1): [m(1)][100.0%][r=33.0MiB/s,w=11.4MiB/s][r=8702,w=2929 IOPS][eta 00m:00s]
# ID, hilos y fecha de terminación del test.
test: (groupid=0, jobs=1): err= 0: pid=758: Sat Dec 15 00:10:55 2018
# Métricas de CPU, IOPs y ancho de banda (Medias, máximos, mínimos, desviación típica, etc). Tanto de escritura como de lectura.
  read: IOPS=8844, BW=34.5MiB/s (36.2MB/s)(768MiB/22218msec)
   bw (  KiB/s): min=32512, max=37224, per=99.95%, avg=35359.36, stdev=940.08, samples=44
   iops        : min= 8128, max= 9306, avg=8839.77, stdev=235.07, samples=44
  write: IOPS=2954, BW=11.5MiB/s (12.1MB/s)(256MiB/22218msec); 0 zone resets
   bw (  KiB/s): min=10984, max=12808, per=99.97%, avg=11813.95, stdev=404.09, samples=44
   iops        : min= 2746, max= 3202, avg=2953.45, stdev=100.99, samples=44
  cpu          : usr=0.00%, sys=60.94%, ctx=178203, majf=0, minf=7
  IO depths    : 1=0.1%, 2=0.1%, 4=0.1%, 8=0.1%, 16=0.1%, 32=0.1%, >=64=100.0%
     submit    : 0=0.0%, 4=100.0%, 8=0.0%, 16=0.0%, 32=0.0%, 64=0.0%, >=64=0.0%
     complete  : 0=0.0%, 4=100.0%, 8=0.0%, 16=0.0%, 32=0.0%, 64=0.1%, >=64=0.0%
     issued rwts: total=196498,65646,0,0 short=0,0,0,0 dropped=0,0,0,0
     latency   : target=0, window=0, percentile=100.00%, depth=64
# Métricas por hilo (job).
Run status group 0 (all jobs):
   READ: bw=34.5MiB/s (36.2MB/s), 34.5MiB/s-34.5MiB/s (36.2MB/s-36.2MB/s), io=768MiB (805MB), run=22218-22218msec
  WRITE: bw=11.5MiB/s (12.1MB/s), 11.5MiB/s-11.5MiB/s (12.1MB/s-12.1MB/s), io=256MiB (269MB), run=22218-22218msec
# Métricas por disco duro, entre ellas el número de operaciones que se han fusionado (/sys/block/sda/queue/scheduler) y tiempo consumido en la cola del disco.
# Por último el porcentaje de tiempo que el disco ha estado ocupado con el test.
Disk stats (read/write):
  sda: ios=195646/65401, merge=132/16, ticks=10164/3580, in_queue=124973, util=99.63%

Comprobar la velocidad de escritura de un dispositivo en GNU/Linux usando dd. (Si no se tiene a mano otra herramienta)

sync; time dd if=/dev/zero of=~/test.tmp bs=500K count=1024 conv=fdatasync,notrunc

Comprobar la velocidad de lectura de un dispositivo en GNU/Linux.

dd if=~/test.tmp of=/dev/null bs=500K count=1024 conv=fdatasync

Si se quiere desactivar temporalmente la cache de escritura a disco se puede utilizar hdparm.

# Desactivar cache de disco.
hdparm -W0 /dev/sdb
 
/dev/sdb:
 setting drive write-caching to 0 (off)
 write-caching =  0 (off)
 
# Comprobar el uso de cache de escritura a disco.
hdparm -i /dev/sdb
 
/dev/sdb:
 ...
 AdvancedPM=no WriteCache=disabled
 ...

Lectura (SSDs con compresión por hardware): https://www.pugetsystems.com/labs/articles/SSDs-Advertised-vs-Actual-Performance-179/

Se mostrarán a continuación algunos ejemplos midiendo la velocidad de escritura en disco duro y en memoria RAM.


RamDisk: Comando dd.

dd if=/dev/zero of=/mnt/tmpfs/testfile_700MB bs=10485760 count=70 conv=fdatasync,notrunc
734003200 bytes (734 MB) copiados, 0,467708 s, 1,6 GB/s

Disco duro: Comando dd.

dd if=/dev/zero of=/root/testfile_700MB bs=10485760 count=70 conv=fdatasync,notrunc
734003200 bytes (734 MB) copiados, 33,1625 s, 22,1 MB/s

Ramdisk: Comando pv (realiza una copia de un fichero).

pv /mnt/tmpfs/testfile > /mnt/tmpfs/testfile2
 100MB 0:00:00 [1,22GB/s] [=================================================================>] 100%

Disco duro: Comando pv (realiza una copia de un fichero).

pv /root/testfile > /root/testfile2
 100MB 0:00:00 [ 825MB/s] [=================================================================>] 100%

Ramdisk: Comando yes (duración de 2 segundos).

timeout 2s yes "öü§$%&/()=?WQERTZUIOPDFGHJKL" > ramdisk/fichero1
du -m ramdisk/fichero1 
3904	ramdisk/fichero1

Disco duro: Comando yes (duración de 2 segundos).

timeout 2s yes "öü§$%&/()=?WQERTZUIOPDFGHJKL" > fichero1
du -m fichero1 
954	fichero1

NOTA: El comando dd es un comando presente en casi todas las distribuciones pero que en la actualidad no sirve para comprobar el rendimiento de un disco duro de manera correcta. El comando dd hace una escritura secuencia a partir de un solo hilo (no se puede comprobar el rendimiento al usar operaciones en paralelo), si la prueba se realiza sobre una VM las opciones “conv” del comando dd para evitar el uso de la cache solo se aplican a la VM y no al anfitrión. Una mejor herramienta de rendimiento y medida de IOPS, sobre todo al tratar con discos SSD es “fio” (Leer).

Otras herramientas para realizar pruebas de rendimiento sobre discos duros en GNU/Linux.