Mostrando entradas con la etiqueta cache. Mostrar todas las entradas
Mostrando entradas con la etiqueta cache. Mostrar todas las entradas

lunes, 15 de mayo de 2017

We all understand free command... don't we???

I know I should be writing the next entry of "Understanding SOMAXCONN parameter Part I" (and I am) but instead I decided to do a little detour first. I faced this question about free a few days ago and noticed odd/different answers for it (including my own xD) so decided to go for a deep dive and found some interesting facts worth sharing.

Free


Free Linux command is one of those tools you will be definitely get exposed to as a sysadmin, and understanding its output is extremely important. A bad interpretation of free output could cause unnecessary panic and you don't want to panic for no reason :D (oh wait... maybe you do, I certainly don't ).

Lets have a look at a simple free output:

juan@test:~$ free
             total       used       free     shared    buffers     cached
Mem:       3007572     584872    2422700      14780      55808     291704
-/+ buffers/cache:     237360    2770212
Swap:            0          0          0
juan@test:~$

In the line starting with "Mem:" we get to see the total available physical memory on the system 3007572 KBytes (3GBytes) and then how it is distributed:
  • used: memory being occupied
  • free: memory that is available in the system
  • shared: amount of memory that is shared between processes
  • buffers: memory used for kernel buffers involving IO (disk, network, etc)
  • cached: memory used for data caching
Then we have a line starting with "-/+ buffers/cache:" which looks kind of cryptic, doesn't it?... it turns out to be a kind of summary of the previous line:
  • There are 237360 KBytes being used, this number is the result of the following math "used - (buffers + cached)". This is the amount of memory the system is actually relying on.
  • There are 2770212 KBytes that could be utilized, this is the result of "free + buffers + cached". What this number actually means is that the kernel should be able to reclaim buffer and cached memory if necessary.
So we have a better understanding of free output now, but lets get more details of where these numbers come from.

The power of the force source


Free binary is shipped as part of procps-ng package in Linux and we can inspect its latest code here. What free actually does for you is summarize memory utilization details coming from /proc/meminfo in a more "human way". So lets dissect this...
  • The main source code can be read here free.c, if you have a look at the lines between 359-377, you can see that there are 6 variables kb_main_total, kb_main_used, kb_main_free, kb_main_shared, kb_main_buffers and kb_main_cached and they are actually the ones containing the values that will show up in free's output. However if you look for these variables on the code you won't find them, they are extern variables included from "proc/sysinfo.h" which means the variables are being initialized somewhere else, particularly in "proc/sysinfo.c". The variables are actually being populated by meminfo() function being called in line 355.
  meminfo();
  /* Translation Hint: You can use 9 character words in
   * the header, and the words need to be right align to
   * beginning of a number. */
  if (flags & FREE_WIDE) {
   printf(_("              total        used        free      shared     buffers       cache   available"));
  } else {
   printf(_("              total        used        free      shared  buff/cache   available"));
  }
  printf("\n");
  printf("%-7s", _("Mem:"));
  printf(" %11s", scale_size(kb_main_total, flags, args));
  printf(" %11s", scale_size(kb_main_used, flags, args));
  printf(" %11s", scale_size(kb_main_free, flags, args));
  printf(" %11s", scale_size(kb_main_shared, flags, args));
  if (flags & FREE_WIDE) {
   printf(" %11s", scale_size(kb_main_buffers, flags, args));
   printf(" %11s", scale_size(kb_main_cached, flags, args));
  } else {
   printf(" %11s", scale_size(kb_main_buffers+kb_main_cached, flags, args));
  }
  printf(" %11s", scale_size(kb_main_available, flags, args));
  printf("\n");

  • Now checking the "proc/sysinfo.c" code where meminfo() function exists, we see:
    • kb_main_buffer is populated with the content of Buffers value from /proc/meminfo (line 691), this is memory used by the kernel to temporarily hold data being sent/received by the system (network IO, disk IO, etc).
    • kb_main_cached is populated with the result of "kb_page_cache + kb_slab_reclaimable" (line 762). These two values come from Cached and SReclaimable respectively (in /proc/meminfo). So the cached value presented by free contains the memory used by the page cache and the reclaimable slab memory (slab memory caches dentry and inodes structures to speed up some fs operations).
  {"Bounce",       &kb_bounce},
  {"Buffers",      &kb_main_buffers}, // important
  {"Cached",       &kb_page_cache},  // important
  {"CommitLimit",  &kb_commit_limit},
  ...
  kb_main_cached = kb_page_cache + kb_slab_reclaimable;
  kb_swap_used = kb_swap_total - kb_swap_free;

It is clear now that free relies completely on the details exposed by the kernel through /proc/meminfo, and the math involved in getting the values isn't really rocket science after all. But here it comes the interesting part...

To have in mind


It looks like the information provided by free can be slightly different between different Linux flavors (because they carry different free versions :D) so I thought it might be worth pointing that out here as well. This could lead to some inconsistencies when some other tools rely on the output of free.

Free version shipped with Ubuntu 14.04 (3.3.9), doesn't even include Slabs under the cached memory, you can see here how kb_main_cached value is just populated with Cached value from /proc/meminfo.

juan@test:~$ free -V
free from procps-ng 3.3.9
juan@test:~$ free
             total       used       free     shared    buffers     cached
Mem:       3007572     530948    2476624      14776      53080     244740
-/+ buffers/cache:     233128    2774444
Swap:            0          0          0
juan@test:~$ grep "^Cached\|^SReclaimable\|^Slab" /proc/meminfo
Cached:           244740 kB
Slab:              32308 kB
SReclaimable:      17572 kB
juan@test:~$

Note: cached = Cached

Free version shipped with CentOS 7 (3.3.10), defines kb_main_cached as "kb_page_cache + kb_slab" (706) this seems a minor thing but not all the slab memory is reclaimable therefore part of this cached content is not really available in case of memory pressure.

[juan@server ~]$ free -V
free from procps-ng 3.3.10
[juan@server ~]$ free -w
              total        used        free      shared     buffers       cache   available
Mem:        1016860       74212      818380        6688         948      123320      810600
Swap:             0           0           0
[juan@server ~]$ grep "^Cached\|^SRecl\|^Slab" /proc/meminfo
Cached:            88876 kB
Slab:              34444 kB
SReclaimable:      13928 kB
[juan@server ~]$

Note: cache = Cached + Slab

Interesting, isn't it? That's the power of open source after all right? Having the chance to really understand what certain piece of software is doing for you and how is doing it!

That's all about free, hope it helps (it helped me at least :P).

domingo, 24 de agosto de 2014

Gluster: explotando su cache

Ya que los últimos días estuve trabajando bastante con gluster y su cache, me pareció interesante plasmarlo acá (para cuando me vuelva a olvidar cómo se hace ja!). La idea es mostrarles cómo se configura la cache en los volúmenes de gluster y qué funcionamiento tiene.

En este escenario vamos a estar trabajando con Gluster 3.5.2 (not production ready yet, but good enough :P) sobre CentOS 6.5.

Creando un volumen con  replica 2

Para empezar necesitamos crear un volumen. En este caso creamos un volumen con réplica 2 de la siguiente manera:

[root@gluster-test-1 ~]# gluster vol create volumen_prueba replica 2 gluster-test-1:/ladrillos/l1/l gluster-test-2:/ladrillos/l1/l
volume create: volumen_prueba: success: please start the volume to access data
[root@gluster-test-1 ~]# gluster vol start volumen_prueba
volume start: volumen_prueba: success
[root@gluster-test-1 ~]# 

Una vez creado e iniciado vemos su estado e información

[root@gluster-test-1 ~]# gluster vol status volumen_prueba
Status of volume: volumen_prueba
Gluster process                                         Port    Online  Pid
------------------------------------------------------------------------------
Brick gluster-test-1:/ladrillos/l1/l                    49159   Y       11717
Brick gluster-test-2:/ladrillos/l1/l                    49159   Y       12102
NFS Server on localhost                                 N/A     N       N/A
Self-heal Daemon on localhost                           N/A     Y       11731
NFS Server on gluster-test-3                            N/A     N       N/A
Self-heal Daemon on gluster-test-3                      N/A     Y       11929
NFS Server on gluster-test-2                            N/A     N       N/A
Self-heal Daemon on gluster-test-2                      N/A     Y       12120
 
Task Status of Volume volumen_prueba
------------------------------------------------------------------------------
There are no active volume tasks
 
[root@gluster-test-1 ~]# gluster vol info volumen_prueba
                                                                                                                                                                  
Volume Name: volumen_prueba                                                                                                                                       
Type: Replicate                                                                                                                                                   
Volume ID: 1197881d-ce2a-4f64-af17-53e5529b691c                                                                                                                   
Status: Started                                                                                                                                                   
Number of Bricks: 1 x 2 = 2                                                                                                                                       
Transport-type: tcp                                                                                                                                               
Bricks:                                                                                                                                                           
Brick1: gluster-test-1:/ladrillos/l1/l                                                                                                                            
Brick2: gluster-test-2:/ladrillos/l1/l                                                                                                                            
[root@gluster-test-1 ~]#   

Ahora que vemos que todo se encuentra bien montamos el volumen desde la máquina cliente:

[root@gluster-client-1 ~]# mount.glusterfs gluster-test-1:/volumen_prueba /mnt/gluster_vol                                                                       
[root@gluster-client-1 ~]# 

Dado que el montaje fue exitoso vemos los logs en /var/log/gluster/mnt-gluster_vol.log y encontramos la definición del volumen (todos estos translators y demás valores se cargan por defecto):

Final graph:
+------------------------------------------------------------------------------+
  1: volume volumen_prueba-client-0
  2:     type protocol/client
  3:     option remote-host gluster-test-1
  4:     option remote-subvolume /ladrillos/l1/l
  5:     option transport-type socket
  6:     option send-gids true
  7: end-volume
  8: 
  9: volume volumen_prueba-client-1
 10:     type protocol/client
 11:     option remote-host gluster-test-2
 12:     option remote-subvolume /ladrillos/l1/l
 13:     option transport-type socket
 14:     option send-gids true
 15: end-volume
 16: 
 17: volume volumen_prueba-replicate-0
 18:     type cluster/replicate
 19:     subvolumes volumen_prueba-client-0 volumen_prueba-client-1
 20: end-volume
 21: 
 22: volume volumen_prueba-dht
 23:     type cluster/distribute
 24:     subvolumes volumen_prueba-replicate-0
 25: end-volume
 26: 
 27: volume volumen_prueba-write-behind
 28:     type performance/write-behind
 29:     subvolumes volumen_prueba-dht
 30: end-volume
 31: 
 32: volume volumen_prueba-read-ahead
 33:     type performance/read-ahead
 34:     subvolumes volumen_prueba-write-behind
 35: end-volume
 36: 
 37: volume volumen_prueba-io-cache
 38:     type performance/io-cache
 39:     subvolumes volumen_prueba-read-ahead
 40: end-volume
 41: 
 42: volume volumen_prueba-quick-read
 43:     type performance/quick-read
 44:     subvolumes volumen_prueba-io-cache
 45: end-volume
 46: 
 47: volume volumen_prueba-open-behind
 48:     type performance/open-behind
 49:     subvolumes volumen_prueba-quick-read
 50: end-volume
 51: 
 52: volume volumen_prueba-md-cache
 53:     type performance/md-cache
 54:     subvolumes volumen_prueba-open-behind
 55: end-volume
 56: 
 57: volume volumen_prueba
 58:     type debug/io-stats
 59:     option latency-measurement off
 60:     option count-fop-hits off
 61:     subvolumes volumen_prueba-md-cache
 62: end-volume
 63: 
+------------------------------------------------------------------------------+

Las líneas 37 a 40 hacen referencia a la configuración del performance/IO-cache translator, el cuál se encarga de definir la configuración del cache del volumen.  Como vemos no hay demasiado configurado dado que aún se encuentra todo por defecto. Según la documentación oficial el tamaño default del cache es de 32Mbytes y un tiempo de expiración de 1 segundo. 

Algunos de los parámetros que podremos controlar desde aquí son:

  • cache-size: espacio asignado a cache
  • cache-timeout: tiempo de expiración de los objetos en cache
  • priority: permite asignar prioridades a distintos tipos de archivos para el uso de cache
  • cache-max-file-size: tamaño máximo de archivo que se puede almacenar en cache
  • cache-min-file-size: tamaño mínimo de archivo que se puede almacenar en cache

Probando los default settings:

Ahora que montamos el archivo vamos a probar la configuración por defecto con que se arman los volúmenes en gluster.

Primero escribimos un archivo de 25Mbytes en el directorio montado:

[root@gluster-client-1 ~]# dd if=/dev/zero of=/mnt/gluster_vol/25MB bs=1M count=25
25+0 records in
25+0 records out
26214400 bytes (26 MB) copied, 0,0286961 s, 914 MB/s
[root@gluster-client-1 ~]# ll -h /mnt/gluster_vol/
total 25M
-rw-r--r--. 1 root root 25M ago 24 20:54 25MB
[root@gluster-client-1 ~]# 

Ahora lo leeremos con intervalos de 10 segundos limpiando los bufferes del sistema entre cada operación:

[root@gluster-client-1 ~]# for i in 1 2 3 4 5; do echo 3 > /proc/sys/vm/drop_caches; dd of=/dev/null if=/mnt/gluster_vol/25MB bs=1M; date; sleep 10;done
25+0 records in
25+0 records out
26214400 bytes (26 MB) copied, 0,271792 s, 96,5 MB/s
dom ago 24 21:20:12 ART 2014
25+0 records in
25+0 records out
26214400 bytes (26 MB) copied, 0,0568677 s, 461 MB/s
dom ago 24 21:20:22 ART 2014
25+0 records in
25+0 records out
26214400 bytes (26 MB) copied, 0,0402717 s, 651 MB/s
dom ago 24 21:20:32 ART 2014
25+0 records in
25+0 records out
26214400 bytes (26 MB) copied, 0,0392092 s, 669 MB/s
dom ago 24 21:20:43 ART 2014
25+0 records in
25+0 records out
26214400 bytes (26 MB) copied, 0,0420014 s, 624 MB/s
dom ago 24 21:20:53 ART 2014
[root@gluster-client-1 ~]# 

Interesante! La primer lectura demora 0.27 segundos, mientras que las 4 siguientes demoran entre 0.03 y 0.05! Claramente se trata de la intervención de la cache. Podría parecer confuso por los 10 segundos que esperamos entre lectura y lectura, pero la documentación define cache-timeout como un tiempo de expiración que fuerza una de re-validación, así que este es un comportamiento esperable. Probablemente estos 10 segundos de espera hubiesen sido demasiado en un entorno de mayor uso del cache.
 
Ahora corremos la misma prueba pero removiendo el tiempo de espera de 10 segundos:

[root@gluster-client-1 ~]# for i in 1 2 3 4 5; do echo 3 > /proc/sys/vm/drop_caches; dd of=/dev/null if=/mnt/gluster_vol/25MB bs=1M; date; done
25+0 records in
25+0 records out
26214400 bytes (26 MB) copied, 0,0485229 s, 540 MB/s
dom ago 24 21:25:18 ART 2014
25+0 records in
25+0 records out
26214400 bytes (26 MB) copied, 0,0471389 s, 556 MB/s
dom ago 24 21:25:18 ART 2014
25+0 records in
25+0 records out
26214400 bytes (26 MB) copied, 0,0360902 s, 726 MB/s
dom ago 24 21:25:18 ART 2014
25+0 records in
25+0 records out
26214400 bytes (26 MB) copied, 0,0359891 s, 728 MB/s
dom ago 24 21:25:18 ART 2014
25+0 records in
25+0 records out
26214400 bytes (26 MB) copied, 0,0355376 s, 738 MB/s
dom ago 24 21:25:18 ART 2014
[root@gluster-client-1 ~]# 

Podemos ver que los tiempos no son demasiado diferentes a los que obtuvimos anteriormente. 

Qué pasaría con un archivo mayor a 32Mbytes...? Escribimos un archivo de 128Mbytes para ver qué sucede:

[root@gluster-client-1 ~]# dd if=/dev/zero of=/mnt/gluster_vol/128MB bs=1M count=128
128+0 records in
128+0 records out
134217728 bytes (134 MB) copied, 2,403 s, 55,9 MB/s
[root@gluster-client-1 ~]# ll -h /mnt/gluster_vol/
total 153M
-rw-r--r--. 1 root root 128M ago 24 21:27 128MB
-rw-r--r--. 1 root root  25M ago 24 21:19 25MB
[root@gluster-client-1 ~]# 

Ahora lo leemos:

[root@gluster-client-1 ~]# for i in 1 2 3 4 5; do echo 3 > /proc/sys/vm/drop_caches; dd of=/dev/null if=/mnt/gluster_vol/128MB bs=1M; date; done
128+0 records in
128+0 records out
134217728 bytes (134 MB) copied, 1,95299 s, 68,7 MB/s
dom ago 24 21:29:09 ART 2014
128+0 records in
128+0 records out
134217728 bytes (134 MB) copied, 1,95518 s, 68,6 MB/s
dom ago 24 21:29:11 ART 2014
128+0 records in
128+0 records out
134217728 bytes (134 MB) copied, 1,96224 s, 68,4 MB/s
dom ago 24 21:29:13 ART 2014
128+0 records in
128+0 records out
134217728 bytes (134 MB) copied, 1,94886 s, 68,9 MB/s
dom ago 24 21:29:15 ART 2014
128+0 records in
128+0 records out
134217728 bytes (134 MB) copied, 1,98702 s, 67,5 MB/s
dom ago 24 21:29:17 ART 2014
[root@gluster-client-1 ~]#

En este caso vemos claramente que estamos teniendo velocidades de lectura muy bajas, lo que nos da la pauta de que no estamos utilizando la cache.

Modificando la configuración de cache:

Ahora vamos a aumentar nuestra capacidad de cache para lograr una buena velocidad de lectura en el caso del archivo de 128Mbytes. Seteamos la cache a 256Mbytes de la siguiente manera:

[root@gluster-test-1 ~]# gluster vol set volumen_prueba performance.cache-size 256MB                                                                             
volume set: success                                                                                                                                               
[root@gluster-test-1 ~]# gluster vol info volumen_prueba
Volume Name: volumen_prueba
Type: Replicate
Volume ID: 1197881d-ce2a-4f64-af17-53e5529b691c
Status: Started
Number of Bricks: 1 x 2 = 2
Transport-type: tcp
Bricks:
Brick1: gluster-test-1:/ladrillos/l1/l
Brick2: gluster-test-2:/ladrillos/l1/l
Options Reconfigured:
performance.cache-size: 256MB
[root@gluster-test-1 ~]# 

Podríamos pensar que no sucedió nada, pero...

[root@gluster-client-1 ~]# for i in 1 2 3 4 5; do echo 3 > /proc/sys/vm/drop_caches; dd of=/dev/null if=/mnt/gluster_vol/128MB bs=1M; date; done
128+0 records in
128+0 records out
134217728 bytes (134 MB) copied, 1,59836 s, 84,0 MB/s
dom ago 24 21:34:25 ART 2014
128+0 records in
128+0 records out
134217728 bytes (134 MB) copied, 1,26796 s, 106 MB/s
dom ago 24 21:34:26 ART 2014
128+0 records in
128+0 records out
134217728 bytes (134 MB) copied, 0,159935 s, 839 MB/s
dom ago 24 21:34:26 ART 2014
128+0 records in
128+0 records out
134217728 bytes (134 MB) copied, 0,152958 s, 877 MB/s
dom ago 24 21:34:27 ART 2014
128+0 records in
128+0 records out
134217728 bytes (134 MB) copied, 0,164556 s, 816 MB/s
dom ago 24 21:34:27 ART 2014
[root@gluster-client-1 ~]# 

Sin necesidad de reiniciar absolútamente nada, ni de desmontar el volumen, vemos como gluster fue capaz de reconocer el aumento del tamaño de cache y permitirnos leer el archivo de 128Mbytes hasta mas de 10 veces mas rápido!!!

El resto es cuento, solo hay que ponerse a jugar con los valores para obtener la política de cache que uno quiere. 

Dónde se almacena la cache?

Hice un par de preguntas al respecto en la lista de usuarios de gluster, dado que no me quedaba del todo claro dónde se almacena la cache. Según la documentación el translator performance/io-cache puede encontrarse presente tanto en el servidor como en el cliente con objetivos distintos. 
  • Estando en el servidor permite compartir la cache con varios clientes de manera simultánea.
  • Estando en el cliente permite ahorrar ancho de banda y CPU (del servidor).
En la lista me dijieron que el translator performance/io-cache se carga durante el proceso de montado del volumen así que la cache por defecto se encuentra en el cliente.

sábado, 18 de enero de 2014

Memcached replicado en Ubuntu 12.04 LTS (haciendo el paquete)

No estaba seguro si ameritaba o no un nuevo post, pero ante la duda así será. La idea de este post es en lugar de hacer una compilación clásica configure && make && make install, crear un paquete que luego podamos transportar sencillamente e instalar en otros servidores similares. Agradezco de antemano a Tomás por la colaboración y la paciencia!

Los pasos que hay que seguir son similares a los del post anterior, (de hecho los pasos del 1 al 4 no cambian, ver http://viviendolared.blogspot.com.ar/2014/01/memcached-replicado-en-ubuntu-1204-lts.html), y por lo tanto comenzaremos a partir del punto 5.

5-Cambios en las reglas de compilación
Para poder crear exitosamente nuestro paquete de memcached con replicación debemos indicarle a dpkg-buildpackage que esta opción debe estar activa y esto lo logramos editando el archivo debian/rules en el directorio de código de fuente obtenido previamente. Es necesario agregar la linea --enable-replication como se indica a continuación (NOTESE la \ al final de la linea previa)


...
config.status: configure
        dh_testdir
        CFLAGS="$(CFLAGS)" ./configure --host=$(DEB_HOST_GNU_TYPE) \
                                       --build=$(DEB_BUILD_GNU_TYPE) \
                                       --prefix=/usr \
                                       --mandir=\$${prefix}/share/man \
                                       --infodir=\$${prefix}/share/info \
                                       --enable-replication


6-Generando el .deb
Ahora nos ubicamos dentro del directorio del código de fuente y creamos el paquete.


root@memcached1:~/memcached-1.4.13# dpkg-buildpackage -us -uc -nc
...
...
dpkg-buildpackage: binary and diff upload (original source NOT included)

Si vemos el directorio superior encontramos el archivo memcached_1.4.13-0ubuntu2.1_amd64.deb que se acaba de generar.

root@memcached1:~/memcached-1.4.13# ls ..
memcached-1.4.13                     memcached_1.4.13-0ubuntu2.1.dsc            memcached_1.4.13-0ubuntu2.1_amd64.deb  repcached-2.3.1-1.4.13.patch
memcached_1.4.13-0ubuntu2.1.diff.gz  memcached_1.4.13-0ubuntu2.1_amd64.changes  memcached_1.4.13.orig.tar.gz           tools
root@memcached1:~/memcached-1.4.13#

7-Instalamos el paquete
Ahora sencillamente instalamos el paquete con dpkg -i


root@memcached1:~# dpkg -i memcached_1.4.13-0ubuntu2.1_amd64.deb 
Selecting previously unselected package memcached.
(Reading database ... 75101 files and directories currently installed.)
Unpacking memcached (from memcached_1.4.13-0ubuntu2.1_amd64.deb) ...
Setting up memcached (1.4.13-0ubuntu2.1) ...
Starting memcached: memcached.
Processing triggers for ureadahead ...
Processing triggers for man-db ...
root@memcached1:~#

Y listo, ahora solo nos queda editar el archivo de configuración /etc/memcached.conf con las opciones correspondientes a nuestro entorno y ya tenemos nuevamente memcached replicado de manera mas sencilla y hasta me atrevería a decir mas limpia.
Este mismo paquete generado podemos copiarlo en el servidor memcached2 e instalar memcached con replicación simplemente haciendo dpkg -i memcached_1.4.13-0ubuntu2.1_amd64.deb simpático, no :D?

jueves, 16 de enero de 2014

Memcached replicado en Ubuntu 12.04 LTS

Como parte del despliegue de OpenStack que estamos haciendo hace ya unos meses en el trabajo surgió la necesidad de contar con alta disponibilidad en el servicio de cache provisto por memcached (Memcached es una sencilla pero muy poderosa solución de cache en memoria, utilizada por grandes personajes de Internet como Wikipedia, Youtube, Twitter, etc). En OpenStack, memcached se utiliza principalmente para proveer agilidad a la interfaz web Horizon.

Los siguientes 6 pasos deben ser aplicados a ambos servidores (cabe aclarar que este método solo permite 2 servidores, es una restricción del propio repcached).

1-Preparar el entorno
Debemos instalar una serie de paquetes para poder compilar nuestra propia versión de memcached con replicación.

root@memcached1:~/# apt-get build-dep memcached

2-Obtener el código de fuente
La forma mas sencilla de obtener el código de fuente es:

root@memcached1:~/# apt-get source memcached

3. Obteniendo el código de repcached
Repcached es un parche que aplicamos sobre el código de memcached, que nos da la capacidad de poder replicar nuestra cache en 2 servidores. Obtener el código de https://github.com/usecide/repcached/blob/master/repcached-2.3.1-1.4.13.patch

root@memcached2:~# wget https://raw.github.com/usecide/repcached/master/repcached-2.3.1-1.4.13.patch

4-Aplicar el parche

root@memcached1:~/# mv repcached-2.3.1-1.4.13.patch memcached-1.4.13/
root@memcached1:~/# cd memcached-1.4.13/
root@memcached1:~/memcached-1.4.13# patch -p1 -i repcached-2.3.1-1.4.13.patch

5-Configurar, Compilar e Instalar

root@memcached1:~/memcached-1.4.13# ./configure –enable-replication
root@memcached1:~/memcached-1.4.13# make
root@memcached1:~/memcached-1.4.13# make install

"make install", si todo sale bien nos dejará nuestra versión de memcached en el directorio /usr/local/bin/.

6-Puesta en marcha
Necesitamos una serie de retoques para poner a punto nuestro memcached con replicación.
Primero debemos crear un script de arranque que nos permite iniciar/reiniciar/detener el servicio a gusto y placer y ubicarlo en /etc/init.d/ con el nombre memcachedrep, el archivo se puede obtener de http://pastebin.com/WfxPV81W ; luego es necesario hacer el archivo ejecutable:

root@memcached1:~/memcached-1.4.13# chmod +x /etc/init.d/memcachedrep

Como segundo paso creamos un archivo de configuración para el servicio en /etc/default/ con nombre memcachedrep y escribimos la siguiente linea en él:


DAEMON_ARGS="-m 256 -p 11211 -u root -P /var/run/memcachedrep.pid -d -v -t 4 -x 172.16.254.118"

Siendo:
-m 256: utilizar 256Mb de memoria para almacen.
-p 11211: escucha peticiones en el puerto 11211, TCP y UDP
-u root: usuario con el que ejecutará el demonio
-P /var/run/memcachedrep.pid: archivo que contendrá el PID del demonio en ejecución
-d: lanza memcached en modo demonio
-v: modo verborrágico, nos permitirá ver advertencias y errores en la ejecución
-t 4: cantidad de hilos que utilizará memcached   
-x 172.16.254.118: este parámetro es propio del parche repcached e indica quien es el servidor con el que se replicará. En nuestro caso los servidores son 172.16.254.117 (memcached1) y 172.16.254.118 (memcached2). Por defecto los servidores se sincronizaran utilizando una conexión dedicada en el puerto 11212.

Por último agregamos memcachedrep a los servicios del sistema:

root@memcached1:~/memcached-1.4.13# update-rc.d memcachedrep defaults

7-Probando la replicación
Bien, ahora llega la parte divertida, ya tenemos ambos servidores listos para correr nuestra versión de memcached con replicación. 


Iniciamos memcachedrep en el nodo memcached1:


root@memcached1:~# service memcachedrep start
root@memcached1:~# replication: connect (peer=172.16.254.118:11212)
replication: marugoto copying
replication: close
replication: listen
root@memcached1:~# 


podemos ver que la replicación se encuentra "close", esto se debe a que la instancia de memcached que lanzamos no pudo conectarse con el otro nodo (172.16.254.118 memcached2). Podriamos utilizar solamente este nodo, en caso de que el otro no funcione.
Ahora iniciamos el segundo nodo:

root@memcached2:~# replication: connect (peer=172.16.254.117:11212)
replication: marugoto copying
replication: start

root@memcached2:~#  

Perfecto, ambas instancias están levantadas y conectadas. Podemos verificar que se han conectado con el siguiente comando:


root@memcached2:~# netstat -ntap|grep ESTA|grep 11212
tcp        0      0 172.16.254.118:35734    172.16.254.117:11212    ESTABLISHED 31503/memcached 
root@memcached2:~# 

También podemos ver en el nodo memcached1 un mensaje indicandolo:


root@memcached1:~# replication: accept


Ahora viene la prueba de fuego, cargamos un dato en el nodo memcached1 y lo buscamos en memcached2:

root@memcached1:~# telnet 127.0.0.1 11211
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
set juan 0 0 4
jose
STORED
quit
Connection closed by foreign host.
root@memcached1:~# 

root@memcached2:~# telnet 127.0.0.1 11211
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
get juan
VALUE juan 0 4
jose
END
quit
Connection closed by foreign host.
root@memcached2:~# 

Y así podemos apreciar el efecto de la replicación. Si sumada a la replicación, ubicamos delante de ambos servidores un balanceador TCP como haproxy logramos sencillamente un mecanismo de cache con alta disponibilidad y balanceo de carga.

Lamentablemente el proyecto repcached se encuentra en un estado no muy activo y por lo tanto dificilmente haya novedades al respecto, pero sin lugar a dudas es una buena opción para lograr estos objetivos. 

miércoles, 31 de agosto de 2011

Usando tmpfs

Bien, entrada corta si las hay!!! Esto va a ser una PEQUEÑA demostración del uso de tmpfs. Qué es tmpfs??? Wiki knows (http://es.wikipedia.org/wiki/Tmpfs#Linux). Es una suerte de sistema de archivos en memoria volátil (RAM y swap si es necesario, ojo!!!).

Ventajas:
-Las operaciones de lectura y escritura son MUCHO mas rápidas que en otros medios de IO como los discos rígidos (IDEAL para montar directorios de CACHEs).
-Es sencillo de utilizar.
 Desventajas:
-Es volátil!!! no se mantiene luego de un reinicio del equipo por ejemplo.
-Su espacio está limitado por la cantidad de memoria RAM disponible y la swap del sistema.

Demostración simple de uso y rendimiento en escritura:

Creamos un directorio llamado prueba_tmpfs

root@moon:~# mkdir prueba_tmpfs

Antes de montar veamos cómo se el consumo de memoria con free -m.

root@moon:~# free -m
                     total       used       free     shared    buffers     cached
Mem:          1975       1167        807          0         25        538
-/+ buffers/cache:        603       1371
Swap:         5789          0       5789

Montamos un sistema de archivos tipo tmpfs (-t tmpfs) de 1GigaByte en el directorio que acabamos de crear.

root@moon:~# mount -t tmpfs -o size=1G tmpfs prueba_tmpfs/
Vemos los puntos de montaje existentes y nos encontramos con nuestro tmpfs recien montado.

root@moon:~# mount 
...
 tmpfs on /root/prueba_tmpfs type tmpfs (rw,size=1G)


Pegamos una mirada con free para ver qué pasó y vemos que si bien el directorio se montó, no se ocupó memoria del sistema aún.

root@moon:~# free -m
                     total       used       free     shared    buffers     cached
Mem:          1975       1167        807          0         25        538
-/+ buffers/cache:        603       1371
Swap:         5789          0       5789

Veamos qué beneficios nos trae tmpfs, escribamos algo!!! Para probar utilizamos un simple dd.

root@moon:~# dd if=/dev/zero of=prueba_tmpfs/prueba bs=1M count=768
768+0 registros de entrada
768+0 registros de salida
805306368 bytes (805 MB) copiados, 1,58104 s, 509 MB/s

Nos encontramos con una velocidad de escritura de 509 Mbytes por segundo!!! (509MBytes*8 = 4072Mbits por seg aprox 4Gbits por segundo!!! lo cual es consistente con el ancho de banda teórico de las memorias DDR2 de 667 Mhz que tiene mi laptop :D). Una velocidad MAS que interesante.

Podemos ver un cambio significativo en free -m. Pasamos de 807 a 37 Mbytes libres, es decir consumimos 807-37=770 (consistente con los 768 que escribimos con el dd).

root@moon:~# free -m
                     total       used       free     shared    buffers     cached
Mem:          1975       1937         37          0         25       1306
-/+ buffers/cache:        605       1369
Swap:         5789          0       5789

mmm ahora le toca al sata de la laptop hacer su mejor esfuerzo xD (competencia un tanto injusta jajaja). Escribimos de la misma manera que antes, pero a un archivo fuera del directorio donde montamos el tmpfs, en este caso el archivo se llama simplemente prueba.

root@moon:~# dd if=/dev/zero of=prueba bs=1M count=768
768+0 registros de entrada
768+0 registros de salida
805306368 bytes (805 MB) copiados, 12,3028 s, 65,5 MB/s

La velocidad fue de 65,5 Mbytes por segundo (65,5Mbytes * 8 = 524 Mbits por segundo, bastante por debajo del máximo teórico de los SATA II, por debajo inclusive del SATA I). Con esto queda claro que la diferencia es IMPORTANTE, la escritura sobre tmpfs fue casi 8 veces mas rápida (509/65,5=7,7).

Demostración simple de uso y rendimiento en lectura:

Todo muy lindo en la escritura, pero en la lectura qué será? :O.

Primero leemos del tmpfs y vemos una velocidad bastante mayor a la que obtuvimos en escritura. Pasando los 7Gbits por segundo!!!, esto seguramente se debe a algún manejo extraño de caches por parte del kernel, porque esta velocidad excede el límite teórico de las memorias de la laptop.

root@moon:~# dd if=prueba_tmpfs/prueba of=/dev/null bs=1M
768+0 registros de entrada
768+0 registros de salida
805306368 bytes (805 MB) copiados, 0,873211 s, 922 MB/s

Con respecto a la lectura en disco... Obtuve una velocidad muy similar a la de escritura, lo cual NO es muy común en este tipo de dispositivos.

root@moon:~# dd if=prueba of=/dev/null bs=1M
768+0 registros de entrada
768+0 registros de salida
805306368 bytes (805 MB) copiados, 13,5713 s, 59,3 MB/s

Por lo general los discos rígidos leen a mayor velocidad de la que escriben, por lo tanto podríamos suponer que la velocidad de escritura obtenida anteriormente es producto de algún manejo eficiente de cache o una suerte de inteligencia en la escritura de datos repetidos (solamente ceros, obtenidos de /dev/zero).

Qué pasa si...?:

Intentamos escribir mas bytes de los que tiene asignado nuestro directorio en tmpfs:

root@moon:~# dd if=/dev/zero of=prueba_tmpfs/prueba bs=1M count=1024
dd: escribiendo «prueba_tmpfs/prueba»: No hay espacio libre en el dispositivo
1022+0 registros de entrada
1021+0 registros de salida
1071640576 bytes (1,1 GB) copiados, 2,47776 s, 433 MB/s
root@moon:~#

Como era de esperarse no pudimos escribir mas de lo que definimos como tamaño del tmpfs :D.

Creamos un tmpfs mayor que nuestra RAM (mi laptop tiene 2Gbytes de RAM):

Es absolutamente posible hacerlo, PERO...

root@moon:~# mount -t tmpfs -o size=3G tmpfs prueba_tmpfs
root@moon:~# mount | grep prueba_tmpfs
tmpfs on /root/prueba_tmpfs type tmpfs (rw,size=3G)
a medida que comencemos a ocupar el espacio del tmpfs, nos comeremos (si, casi literalmente) la memoria ram del equipo, este comenzará a intercambiar páginas con poco uso a la memoria de intercambio (swap) y eventualmente el sistema podría quedar inutilizable. Moraleja? no es recomendable hacerlo (cosas que pasan xD).


Conclusión:

Definitivamente es una opción muy interesante principalmente para utilizar como directorio para caches.

En mi caso en particular utilicé tmpfs para el directorio donde munin crea/actualiza los archivos rrd, en un servidor que monitorea mas de 100 servidores, y el cambio fue notable. Antes de esto los gráficos se cortaban porque el proceso de graficar no terminaba antes de que llegaran las nuevas mediciones.