Monitoritzant cassandra, dades rellevants que s’han de vigilar (i com enviar-les a graphite)

Segons els anteriors posts ja sabem com instal·lar cassandra i tunejar-lo mínimament  Però no n’hi ha prou, perquè per posar-ho en un entorn productiu hem de poder garantir que el cassandra funciona correctament, i tenir alertes si no ho fa. Per això hem d’integrar-ho al nostre sistema de monitorització, ja sigui Nagios, Zabbix, Zenoss, Graphite, el que sigui. El que farem ara és identificar tots els punts que han d’estar vigilats per detectar possibles incidències al nostre clúster.

D’entrada voldrem assegurar-nos que la màquina disposa de recursos suficients i que el servei està en marxa i escoltant. Per mesurar els recursos ja vam veure el script que feia la monitorització bàsica d’un sistema linux. Per mirar si el procés està en marxa podem fer el típic /etc/init.d/cassandra status si hem instal·lat el script init.d, o “ps uax|grep CassandraDaemon|grep -v grep” si no ho hem fet. Per mirar si els ports que toquen estan escoltant, podem fer localment un netstat per cadascun dels dos: 7000 pel gossip (netstat -l –numeric-ports|grep “:7000 “) i 9160 pel thrift (netstat -l –numeric-ports|grep “:9160 “) o el mateix amb un netcat (que pot ser local o remot) nc -z -w 3 9160nc -z -w 3 7000. Amb això ja tindrem una monitorització bàsica que ens avisa quan les coses no estan funcionant. 

Però el cassandra és molt gran i té molts paràmetres configurables, i no és fàcil saber per què falla quan falla. I també estaria be poder predir que està a punt de passar alguna cosa greu abans que passi. Per tot això volem més dades, dades útils que ens facilitin diagnosticar problemes, veure que està passant internament i trobar quins paràmetres es poden tocar per optimitzar el seu funcionament. Aquestes dades les trobarem a la consola JMX de cassandra.

Hi ha varies maneres de connectar-nos a aquesta consola. La típica es amb el jconsole, i datastax ens ofereix el opscenter, però normalment voldrem incorporar aquestes dades al nostre propi sistema de monitorització. Hi ha gent que ha desenvolupat un pont entre el jmx i altres sistemes (munin, per exemple, a https://github.com/tcurdt/jmx2munin, o snmp amb https://github.com/tcurdt/jmx2snmp o appdynamics). A mi m’interessava integrar-ho amb graphite, per tant necessitava accedir a aquestes dades des de la consola. Amb el nodetool ja podia fer algunes coses (tpstats, cfstats, netstats, info, etc) però no totes, i a més tantes crides carregaves massa el sistema pel meu gust. Aleshores vaig descobrir mx4j, que és un pont que agafa les dades de la jmx i les serveix per HTTP, és a dir, posant-les a l’abast del nostre curl, on hi tenim fàcil l’accés. Perfecte! Just el que necessitava!

La instal·lació es senzilla (http://wiki.apache.org/cassandra/Operations#Monitoring_with_MX4J), només s’ha de descarregar el jar, posar-lo a la carpeta “lib” de cassandra i reiniciar. Normalment serà tan senzill com:


wget "http://downloads.sourceforge.net/project/mx4j/MX4J%20Binary/3.0.2/mx4j-3.0.2.tar.gz"
tar zxf mx4j-3.0.2.tar.gz
mv mx4j-3.0.2/lib/mx4j-tools.jar /opt/cassandra/lib/
/etc/init.d/cassandra restart

Un cop reiniciat el cassandra, ja podrem accedir al mx4j al port 8081 des del nostre navegador.

Trobarem que hi ha moltes MOLTES dades, i que moltes d’elles no sabem què signifiquen (ho sento, no sóc desenvolupador java, hi ha moltes coses que se m’escapen :P). Tenim una descripció de moltes de les mètriques a la documentació de cassandra  i d’aquí hem de destriar quines d’aquestes dades ens interessen. Vaig llegir a uns quants blogs (mireu la bibliografia al final) i finalment vaig decidir que les dades que interessen (i que es poden recollir fàcilment) són les següents:

  1. Tasques en ReadStage, MutationStage, GossipStage
  2. Amb aquests valors podem mesurar l’activitat de cada servidor, mesurant el número d’operacions que realitza. Els tres tipus d’operacions diferents son lectura, modificació i “gossip” (comunicació inter-nodes) . D’aquí recollirem el total de tasques completades (CompletedTasks) on podrem veure quantes tasques per minut es realitzen  les tasques actives (ActiveTasks) on podrem veure en quantes tasques alhora està treballant el node, i les tasques pendents (PendingTasks) on podrem veure la cua de missatges que queden per fer. Amb això podrem veure moltes coses: per exemple si el numero de “PendingTasks” creix de manera ininterrompuda pot ser que el nostre node rep més peticions de les que pot processar, o també pot ser que ens haguem quedat sense espai en disc i, en no poder escriure al commitlog, les estigui acumulant (sigui com sigui, si aquesta mètrica puja, alguna cosa dolenta està passant). Si veiem que la carrega dels nostres servidors ha augmentat i també veiem que les CompletedTasks ho ha fet, significa que aquest augment es “normal”.
    Aquestes dades les podem trobar a
    http://$host:8081/mbean?objectname=org.apache.cassandra.request%3Atype%3DReadStage
    http://$host:8081/mbean?objectname=org.apache.cassandra.request%3Atype%3DMutationStage
    http://$host:8081/mbean?objectname=org.apache.cassandra.internal%3Atype%3DGossipStage

  3. Tasques de compactació
  4. Normalment estan relacionades amb l’activitat del clúster. Si hi ha moltes escriptures, normalment es dispararan compactacions. D’aquí recollirem quantes tasques de compactació hi ha pendents (PendingTasks) i completades (CompletedTasks), per veure quantes es fan i si s’acumulen. Per exemple, si ens trobem que algun server està molt carregat, i la cua de compactació és molt llarga, haurem de valorar treure-li prioritat a les mateixes (nodetool setcompactionthroughput 1), o si veiem que la cua no para de créixer valorarem fer-li un disable del thrift (nodetool disablethrift) perquè no li arribin peticions noves i donar-li prioritat màxima a les compactacions perquè les acabi com més aviat millor (nodetool setcompactionthroughput 999). Aquestes mètriques també ens ajudaran a saber quan ha acabat un repair (ara per fi hi ha un indicador de progrés de repair disponible, a partir de la v1.1.9 i la 1.2.2), o un scrub/rebuild, o upgradesstables, etc. En qualsevol cas, si aquests valors són diferents de zero molt sovint, tindrem motius de preocupació. L’enllaç:
    http://$host:8081/mbean?objectname=org.apache.cassandra.db%3Atype%3DCompactionManager

  5. Latència
  6. Amb aquest valor recollirem la latència que experimenten les operacions. Voldrem que aquest valor sigui el més petit possible, i si augmenta sense motiu haurem d’investigar per què. Disposem de la mesura de tres tipus de latència per diferents operacions: Range (RecentRangeLatencyMicros), Read (RecentReadLatencyMicros) i Write (RecentWriteLatencyMicros).
    http://$host:8081/mbean?objectname=org.apache.cassandra.db%3Atype%3DStorageProxy

  7. Ús de memoria Heap i NoHeap
  8. Per veure quanta memòria té disponible la màquina virtual de Java per treballar i quanta ocupada. Aquí recollirem HeapMemoryUsage i NoHeapMemoryUsage. 
    http://$host:8081/mbean?objectname=java.lang%3Atype%3DMemory -s

  9. Número de GarbageCollections
  10. Aquí prendrem mesures del número de GarbageCollections  que es fan al sistema. Això està relacionat amb el valor anterior, perquè cada cop que es faci el GarbageCollection s’alliberarà memòria. Ens servira per diagnosticar, per exemple, quan qualsevol procés java fa garbage collection molt sovint i acaba dedicant més temps a aquesta tasca que a la seva principal. Haurem de controlar la freqüència de les GC (ConcurrentMarkSweep). Si és massa freqüent, potser hem de destinar més memòria al nostre java. En qualsevol cas, voldrem que aquest valor sigui el més petit possible.
    http://$host:8081/mbean?objectname=java.lang%3Atype%3DGarbageCollector%2Cname%3DConcurrentMarkSweep

    Fora de la JMX també hi ha coses interessants

  11. Numero de connexions 
  12. Amb això volem saber quantes connexions concurrents està servint el Cassandra. Així en cas de creixement de la càrrega al cassandra, podrem veure si es correspon amb un creixement en el número d’usuaris. Si la nostra aplicació no augmenta el número d’usuaris però el cassandra si que augmenta en el número de connexions, significa que hi ha alguna cosa que no funciona correctament (les peticions es processen més lentament, per exemple). Si veiem que el número de connexions de cassandra augmenta, i també ho fa el número d’usuaris de la nostra aplicació, aleshores l’augment es “normal” i haurem de millorar el Cassandra (donant-hi recursos o tunejant la configuració) per solucionar-ho. És una dada molt útil. Tot i que es podria millorar, estaria molt be que poguéssim veure quines transaccions està fent el cassandra (com un show processlist de mysql ) per poder veure si hi ha alguna petició mal construïda o que es pugui millorar. Però donades les característiques de Cassandra, això sembla bastant inviable, així que ens conformarem amb el número de connexions.. Vaig demanar a la llista de cassandra-users si hi havia alguna manera d’aconseguir-ho  i em van dir que no, però que semblava una cosa interessant perquè es demanava força sovint, i per això van crear un ticket als desenvolupadors. Algun dia ho implementaran, espero, i podrem consultar aquest valor via JMX. Mentrestant, la única via que tenim es el netstat:

    connections=netstat -tn|grep ESTABLISHED|awk '{print $4}'|grep 9160|wc -l

  13. Dades a per cada ColumnFamily
  14. Per treure més suc al Cassandra també és recomanable analitzar dades per cada ColumnFamily. Així podrem veure el tamany, l’activitat, la taxa d’èxit que té la caché, quins indexs secundaris tenen… Però això són moltes peticions al mx4j (unes 21 per cada column family, a mi em surten gairebé 2000 peticions HTTP!), i la informació no varia tan sovint, així que per ara no recolliré aquesta informació, i quan ho faci la recolliré cada cinc minuts o cada quart d’hora per no sobrecarregar el cassandra amb la monitorització, per tant la posaré en un script apart.

I bé, tot això són les parts interessants de monitoritzar de cassandra. Per recollir tota aquesta informació vaig fer un script que podeu trobar al meu perfil github, amb el nom de cassandra-monitoring  que es connectarà al mx4j dels servidors i traurà aquesta informació (excepte les connexions establertes, que per fer-ho necessita fer un netstat). Tota la informació que hem descrit, la recollirà i la posarà en una cadena de text (“data“) que després enviarà al graphite.

Aquí el teniu:


#!/bin/bash

# WARNING - ATENCIO
# This script requires mx4j to be installed on cassandra monitored nodes. For more instructions visit:
# Aquest script necessita que el mx4j estigui instal·lat als nodes monitoritzats. Per mes instruccions visita:
# http://wiki.apache.org/cassandra/Operations#Monitoring_with_MX4J

#Carbon server where data should be stored for graphite to show - El servidor carbon on s'han de guardar les dades que mostra el graphite
carbon_server=graphite.domain.tld
# Tree structure where we want information to be stored - L'estructura de l'arbre on volem que es guardin les dades a graphite.
tree=servers

now=date +%s
host=${1:-localhost}

#Number of connections - Numero de connexions
if [ $host == "localhost" ];then
connections=netstat -tn|grep ESTABLISHED|awk '{print $4}'|grep 9160|wc -l
else
connections=ssh $host netstat -tn|grep ESTABLISHED|awk '{print $4}'|grep 9160|wc -l
fi
data="$tree.$host.cassandra.connections $connections $nown"

#Tasks in ReadStage - Tasques en ReadStage
data="$data curl http://$host:8081/mbean?objectname=org.apache.cassandra.request%3Atype%3DReadStage -s |egrep "CompletedTasks|PendingTasks|ActiveCount"|cut -d">" -f8|cut -d"<" -f1|awk -v tree=$tree -v now=$now -v host=$host '(NR == 1) {printf("%s.%s.cassandra.ReadStage.ActiveCount %s %s\n",tree, host, $0, now)} (NR == 2) {printf("%s.%s.cassandra.ReadStage.CompletedTasks %s %s\n",tree, host, $0, now)} (NR ==3) {printf("%s.%s.cassandra.ReadStage.PendingTasks %s %s\n",tree, host, $0, now)}'"

#Tasks in MutationStage - Tasques en MutationStage (writes)
data="$data curl http://$host:8081/mbean?objectname=org.apache.cassandra.request%3Atype%3DMutationStage -s |egrep "CompletedTasks|PendingTasks|ActiveCount"|cut -d">" -f8|cut -d"<" -f1|awk -v tree=$tree -v now=$now -v host=$host '(NR == 1) {printf("%s.%s.cassandra.MutationStage.ActiveCount %s %s\n",tree,host, $0, now)} (NR == 2) {printf("%s.%s.cassandra.MutationStage.CompletedTasks %s %s\n",tree,host, $0, now)} (NR ==3) {printf("%s.%s.cassandra.MutationStage.PendingTasks %s %s\n",tree, host, $0, now)}'"

#Tasks in GossipStage - Tasques en GossipStage (comunicacio interna entre cassandras)
data="$data curl http://$host:8081/mbean?objectname=org.apache.cassandra.internal%3Atype%3DGossipStage -s |egrep "CompletedTasks|PendingTasks|ActiveCount"|cut -d">" -f8|cut -d"<" -f1|awk -v tree=$tree -v now=$now -v host=$host '(NR == 1) {printf("%s.%s.cassandra.GossipStage.ActiveCount %s %s\n",tree, host, $0, now)} (NR == 2) {printf("%s.%s.cassandra.GossipStage.CompletedTasks %s %s\n",tree, host, $0, now)} (NR ==3) {printf("%s.%s.cassandra.GossipStage.PendingTasks %s %s\n",tree, host, $0, now)}'"

#Compaction tasks - Tasques de compactacio
data="$data curl http://$host:8081/mbean?objectname=org.apache.cassandra.db%3Atype%3DCompactionManager -s|egrep "CompletedTasks|PendingTasks"|cut -d">" -f8|cut -d"<" -f1|awk -v tree=$tree -v now=$now -v host=$host '(NR == 1) {printf("%s.%s.cassandra.Compaction.CompletedTasks %s %s\n",tree, host, $0, now)} (NR == 2) {printf("%s.%s.cassandra.Compaction.PendingTasks %s %s\n",tree, host, $0, now)}'"

#Operation Latency - Latencia d'operacions
data="$data curl http://$host:8081/mbean?objectname=org.apache.cassandra.db%3Atype%3DStorageProxy -s |egrep "RecentRangeLatencyMicros|RecentReadLatencyMicros|RecentWriteLatencyMicros"|cut -d">" -f8|cut -d"<" -f1|awk -v tree=$tree -v now=$now -v host=$host '(NR == 1) {printf("%s.%s.cassandra.Latency.Range %s %s\n",tree, host, $0, now)} (NR == 2) {printf("%s.%s.cassandra.Latency.Read %s %s\n",tree,host, $0, now)} (NR ==3) {printf("%s.%s.cassandra.Latency.Write %s %s\n",tree,host, $0, now)}'"

#Heap and non-heap memory - Us de Memoria Heap i NoHeap
data="$data curl http://$host:8081/mbean?objectname=java.lang%3Atype%3DMemory -s|grep HeapMemoryUsage|awk -F"max=" '{print $2}'|cut -d"}" -f1|sed -e 's/, used=/n/g'|awk -v tree=$tree -v now=$now -v host=$host '(NR == 1) {printf("%s.%s.cassandra.internals.MaxJavaHeap %s %s\n",tree, host, $0, now)} (NR == 2) {printf("%s.%s.cassandra.internals.JavaHeapUsed %s %s\n",tree, host, $0, now)} (NR ==3) {printf("%s.%s.cassandra.internals.MaxJavaNoHeap %s %s\n",tree, host, $0, now)} (NR == 4) {printf("%s.%s.cassandra.internals.JavaNoHeapUsed %s %s\n",tree, host, $0, now)}'"

#Number of GarbageCollections - Numero de GarbageCollections
data="$data curl http://$host:8081/mbean?objectname=java.lang%3Atype%3DGarbageCollector%2Cname%3DConcurrentMarkSweep -s| grep CollectionCount|cut -d">" -f8|cut -d"<" -f1|awk -v tree=$tree -v now=$now -v host=$host '{printf("%s.%s.cassandra.internals.GarbageCollections %s %s\n",tree, host, $0, now)}'"

echo $data
echo -e $data|nc -w 5 $carbon_server 2003
exit $?

Si, és força lleig. Això en python o perl seria molt més maco. Però com sempre, comença amb una cosa petita que es fa amb una crida senzilla, i després va creixent i creixent fins que ens trobem amb això... Que hi farem. Ja intentaré fer una versió millorada.

Un cop tenim aquests valors al graphite els hem de donar sentit. Hi ha valors que són útils tal i com els recollim, com per exemple el número de connexions o les operacions pendents, però a tots els que siguin acumulatius, és a dir, el número de GarbageCollections o totes les CompletedTasks (tant les de Compaction com les ReadStage, WriteStage, GossipStage) els haurem d'aplicar la funció derivative  per saber quantes es fan per interval de temps i detectar així increments inusuals.

I finalment, si fem un dashboard on els posem tots, podrem esbrinar en un cop d'ull si els problemes que han aparegut a la nostra aplicació tenen a veure amb el cassandra o en realitat és innocent. Un possible dashboard podria ser com aquest:

I fins ara res més. En un futur faré la segona part on inclouré la informació sobre les columnfamilies.

Bibliografia:

Monitoritzant l’estat d’un sistema linux amb graphite

Ja vam veure en anteriors posts com instal·lar graphite. Ara haure’m d’enviar-li algunes dades perquè les emmagatzemi i les poguem consultar, per començar a jugar amb la webapp (si no, no ens serveix de res!). El graphite es pot fer servir per fer gràfiques de moltes coses, però començarem pel més bàsic que ens pot interessar: l’estat d’un servidor linux.

Per decidir si l’estat d’un servidor és correcte, ens fixarem en les coses típiques que es monitoritzen a un linux: memòria, espai en disc, CPU, iowait, i operacions read/write a disc. Hi ha moltes maneres de recollir aquestes dades. Jo he escollit un shell-script que executa comandes standard (free, sar i awk, bàsicament) i les envia al carbon via netcat. Podeu trobar el codi del script a github dintre del repositori graphite-monitoring , però també el poso aquí perquè es senzill i curtet:

Ara només hem de fer que això s’executi periòdicament, per exemple posant un cron al sistema. Jo ho executo amb monit (del que ja en parlaré en un altre moment), perquè em gestioni els errors en l’execució. Amb això ja disposarem de dades útils al graphite per començar a jugar.