Apache HBase

Publicado por

HBase es un proyecto open-source mantenido por la Apache Foundation que proporciona una base de datos columnar distribuida creada sobre el sistema de ficheros de Hadoop que puede escalar horizontalmente.

Índice

  1. Introducción
  2. Características
  3. Modelo de datos
  4. Arquitectura
  5. Lectura y escritura
  6. ¿Como implementa el servidor de regiones las divisiones?
  7. Fallo y recuperación
  8. Hotspotting
  9. Shell
  10. API Java

1. Introducción

HBase es un proyecto open-source mantenido por la Apache Foundation que proporciona una base de datos columnar distribuida creada sobre el sistema de ficheros de Hadoop que puede escalar horizontalmente. HBase utiliza un modelo de datos muy similar al de Google Big Table diseñado para proporcionar acceso aleatorio a una cantidad muy grande de datos estructurados.

El objetivo del proyecto HBase es el almacenamiento de tablas muy grandes, de billones de filas por millones de columnas, para ello almacena los datos por pares de clave-valor. Buscar por claves en HBase es muy rápido. La escritura también porque se realiza prácticamente en memoria.

2. Características

  • Lectura y escritura consistente
  • Escalabilidad horizontal
  • Sharding automático: es la partición horizontal de una tabla en regiones para así aumentar el rendimiento de la búsqueda y acceso de datos.
  • Detección de errores automático en los RegionServers.
  • Integración con HDFS, MapReduce, API Java, Thrift/REST API

3. Modelo de datos

En HBase los datos se guardan en tablas que tienen filas y columnas, puede parecer que estemos hablando de una base de datos relacional, pero no tiene nada que ver, sería más acertado pensar en HBase como un mapa multidimensional.

Los elementos del modelo que debemos conocer son:

  • Tabla: agrupación de un conjunto de filas
  • Fila: está compuesta de la clave y una o más columnas con valores asociados a ella. Las filas están ordenadas alfabeticamente por la clave cuando son almacenadas.
  • Familia de columnas: es un conjunto de columnas con valores que se almacenan fisícamente en el mismo archivo. Cada fila de la tabla tiene el mismo número de familias
  • Qualifier: se inserta en una familia de columnas como una columna, generando así un índice para una parte de los datos. Las filas no tienen porque tener el mismo número de qualifiers.
  • Celda: es la combinación de una fila con una familia de columnas y un qualifier. Contiene un valor y un timestamp que representa la versión del valor.
  • Timestamp: es escrito con cada valor y actua como identificador de un versión.

Las operaciones que podemos realizar a través de la API de HBase son:

  • get: devuelve los atributos de una fila por su clave
  • put: añade nuevas filas a la tabla o actualiza ya existentes (si la clave existe)
  • scan: permite iterar sobre múltiple filas para unos atributos dados
  • delete: elimina una fila de la tabla

4. Arquitectura

HBase es una base de datos de tipo NoSQL con la diferencia de que también es distribuida. El termino más acertado sería almacén de datos y no base de datos.

HBase fue diseñado para escalar con el principio de que los datos que se acceden juntos se almacenan juntos. Agrupar los datos por claves es fundamental sobre un cluster. En las particiones horizontales o sharding el rango de claves es utilizado para distribuir los datos sobre los múltiples servidores.

a. HMaster

El HMaster es responsable de monitorizar todas las instancias de los servidores de regiones del cluster, también actúa de interfaz para realizar cambios en los metadatos. En un sistema distribuido el Master suele correr sobre el NameNode.

Si HBase se ejecuta sobre un entorno multi-master todos ellos compiten por administrar el cluster. Si el master activo pierde su enlace con Zookeeper o queda offline, el resto de masters intentará obtener el rol de Master cluster.

En caso de que el HMaster esté offline, como los clientes hablan directamente con los servidores de regiones, el cluster podría seguir funcionando. Adicionalmente, por cada tabla del catálogo, HBase almacena sus metadatos (tabla, columna de familia, región, …), en los servidores de región.

El HMaster controla funciones críticas como el estado de los servidores de región, también completa las divisiones de las regiones. Gestiona la carga de las regiones sobre los servidores, descarga a los que están más ocupados y mueve regiones a máquinas con menos trabajo.

Es responsable de los cambios de esquemas y otras operaciones con metadatos como la creación de tablas y familias de columnas. La tabla de metadatos se llama hbase:meta.

b. Servidor de Regiones

Son los responsables de servir y administrar las regiones. En un cluster distribuido un servidor de región se ejecuta sobre un DataNode.

Tiene comunicación con el cliente y gestiona las operaciones relacionadas con los datos, tanto lecturas como escrituras de sus regiones, también decide el tamaño de cada región. Por este motivo tiene acceso a los metadatos que almacena localmente.

c. Región

Es el resultado de las divisiones de una tabla por su clave de fila, cada tabla está compuesta por un número de regiones que son administradas por el RegionServer.

Cada región esta compuesta por el memstore y múltiples HFile. Los datos se encuentran en los hfiles en forma de familias de columnas. Un RegionServer puede servir sobre 1000 regiones. Cada región por defecto es de 1Gb.

Inicialmente hay una región por tabla, cuando ésta crece, se divide en dos regiones hijas que son abiertas en paralelo en el mismo RegionServer, después se informa al HMaster para que actualice los metadatos. Por repartir la carga el HMaster podrá enviar las regiones a otros servidores.

d. Memstore

Es la cache de escritura. Almacena nuevos datos que todavía no han sido escritos a disco, antes de escribir ordena los datos. Hay un Memstore por cada familia de columnas y region.

Los datos son ordenados por cada familia de columnas en memoria. Cuando se llena un Memstore se desencadena un proceso llamado flush que tiene como misión escribir una parte de los datos de la memoria fisicamente en Hfiles. Se crea un HFile por cada familia de columnas del memstore. Este proceso escribe todos los Memstore de la región.

f.BlockCache

Es la caché de lectura, almacena datos de lectura frecuentes en memoria. Los datos usados menos recientemente son eliminados cuando esta llena.

e.HFile

Los datos son guardados en Hfiles que contienen registros del tipo clave-valor ordenados. Cada familia de columnas puede estar compuesta por varios Hfiles. Cuando el Memstore acumula suficientes datos, el conjunto entero de pares clave-valor es escrito a un nuevo Hfile en HDFS. Es una escritura secuencial, es muy rápida ya que evita mover el cabezal de la unidad de disco.

HBase genera varios índices multinivel para cada Hfile, cada uno de ellos representa un conjunto de claves. Estos índices son árboles b+ donde cada hoja representa un conjunto de claves ordenadas, así cuando se quiere leer un dato su búsqueda es mucho más rápida.

Los índices son cargados en memoria cuando el HFile se abre. Mientras permanece en la memoria es parte de la BlockCache y permite realizar varias búsquedas con una sola lectura de disco.

g. WAL (Write-Ahead Log)

Todas las regiones en un servidor comparten una referencia con el registro de escritura anticipada que es utilizado para guardar datos nuevos que no han sido todavía persistidos a disco y para la recuperación de fallos del RegionServer.

h. Zookeeper

Es utilizado por HBase para la coordinación de servicios distribuidos y mantener el correcto funcionamiento de los servidores en el cluster. Zookeeper mantiene una conexión con el HMaster y con los RegionServers y gestiona los nodos efímeros para las sesiones activas.

hbase-architecture-1024x853

Img: DZone.com

5. Lectura y escritura

Pasos para la lectura

Hemos visto que cada KeyValue perteneciente a una fila puede estar almacenado en múltiples sitios, las filas persistidas se encuentran en Hfiles, la filas actualizadas recientemente en Memstore y las lecturas recientes se encuentran en la Blockcache. Con todo esto, cuando hacemos una lectura a través de un get HBase combina estas fuentes de datos de la siguiente manera:

  1. El Scanner busca en la BlockCache el KeyValue de la fila
  2. Después el Scanner busca en la Memstore
  3. Sino ha encontrado la fila anteriormente, HBase busca en los índices de los Hfiles para cargar los datos en memoria

Pasos para la escritura

Cuando un cliente envía un <code>put</code> esto es lo que sucede:

  1. Escribir los datos en el WAL, que es un fichero almacenado en disco. Es utilizado para recuperar datos que no han sido persistidos en disco por un fallo en el servidor.
  2. Después de escribir en el WAL, escribe en el Memstore. En este punto se le comunica al cliente que su petición se ha procesado con éxito.

Existe un problema asociado al funcionamiento de estos dos procesos de lectura y escritura denominado lectura amplificada. Este problema esta asociado con la creación de multitud de ficheros Hfiles con pocos datos generando problemas de rendimiento en la lectura de datos.

Para solucionar el problema HBase implementa la compactación menor que es reescribir los HFiles mas pequeños en ficheros más grandes.

Existe otro proceso llamado compactación mayor que reescribe en un HFile cada familia de columnas de una región. Este proceso incrementa el rendimiento de las lecturas pero al tener que reescribir todos los ficheros tiene dos desventajas: genera al disco multitud de peticiones de lectura/escritura e incrementa el tráfico de red; este problema se conoce como escritura amplificada.

Cuando se realiza la compactación mayor también se realiza un flush de la memoria y se llevan a cabo las operaciones delete que estuvieran pendientes de escribir en disco. Esta operación se suele realizar una vez al día, preferiblemente por la noche.

6. ¿Como implementa el servidor de regiones las divisiones?

Las peticiones de escritura son manejadas por el servidor de regiónes acumulándolas en el memstore. Cuando se llena, su contenido es escrito a disco como ficheros. Este evento es conocido como memstore flush.  Cuando se generen muchos ficheros, el servidor de región los compactará en menos ficheros más grandes. Cuando el flush o la compactación terminan, la cantidad de datos guardada en cada región cambia. El servidor de región consulta sus política de región para determinar si una región ha crecido demasiado o debe ser dividida por alguna otra razón.

El proceso de dividir una región es sencillo, buscamos un punto adecuado en el espacio de claves de una región donde deberíamos dividirla por la mitad, después partimos los datos en dos nuevas regiones. Cuando se produce una división, las nuevas regiones “hijas” no reescriben todos los datos en ficheros inmediatamente, sino que crea pequeños ficheros, que son como enlaces simbólicos, conocidos como ficheros de referencia, que apuntan a la parte alta o baja del fichero padre en función del punto de división. Estos ficheros son tratados como ficheros de datos normales, solo teniendo en cuenta la mitad de sus registros. Se borraran gradualmente cuando se hagan las compactaciones, así la región dejará de apuntar a los fichero de su padre y podrá ser dividido más adelante.

Aunque el proceso de dividir las regiones sea interno de cada servidor de región, éstos deben coordinarse con varias actores. El RegionServer notifica al HMaster antes y después de realizar una división, actualiza las tablas de metadatos para que los clientes puedan acceder a las nuevas regiones hijas, y reorganiza la estructura de directorios y ficheros en HDFS. La división es un proceso multitarea. Los pasos que lleva a cabo el RegionServer están descritos en la siguiente figura. Las acciones del RegionServer sobre el master están en rojo, mientras que las de los clientes son verdes.

region_split_process

Img: http://hbase.apache.org/book.html

7. Fallo y recuperación

  1. Cuando un servidor de región falla, Zookeeper avisa al HMaster del fallo
  2. El HMaster distribuye las regiones del servidor que ha fallado en otros que estén activos. Para recuperar los datos el HMaster distribuye el WAL por todos los servidores de regiones.
  3. Cada servidor de región ejecuta el WAL para construir el memstore para la columna de familias de la región que fallo.
  4. Los datos son escritos en orden cronológico dentro del WAL. Realizar todos los cambios que se hubieran producido y guardado en el Memstore.
  5. Cuando todos los servidores de región terminan de procesar el WAL, los datos del Memstore para todas las familias de columnas es recuperado.

8. Hotspotting

Los registros en HBase estan ordenados lexicográficamente por la clave de fila. Esto permite acceso rápido a un registro individual por su clave, o a un rango de claves. Puede parecer en algunos casos que escoger una clave secuencial es buena para la escritura por el tipo de consultas que se harán después. Ejemplos de estas claves pueden ser un Timestamp o una secuencia ordenada. Pero escribir los registros con claves tan ingenuas causará un problema de hotspotting por la forma en que HBase escribe sus datos.

Cuando los registros con una clave secuencial se están escribiendo en HBase, todas las escrituras están localizadas en la misma región. Esto no sería un problema si la región se sirviera por varios servidores de región, pero este no es el caso, cada región reside en un  servidor de regiones. Cada región tiene un tamaño máximo configurado y cuando alcanza ese límite la región se divide en dos más pequeñas. Creando una nueva víctima de hotspotting en el servidor donde viaje la nueva región.

La siguiente imagen muestra el uso de CPU de diferentes servidores de regiones cuando se produce un problema de hotspotting.

hbasewd-pic1

Img: sematext.com

EL método más simple para solucionar el problema es distribuir las escrituras sobre varias regiones utilizando un clave aleatoria. Esta solución tiene la desventaja de perder velocidad a la hora de escanear una tabla con una clave de inicio y una de final. Para evitar esto existe una solución descrita en las listas de correo de HBase llamada salting que propone lo siguiente:

new_row_key = (++index % BUCKETS_NUMBER) + original_key

Con esto los registros son divididos en múltiples buckets. Las nuevas claves de los registros ya no estarán secuencialmente ordenadas, pero se encontrarán ordenadas en cada bucket. La siguiente gráfica muestra la diferencia con el método anterior.

hbasewd-pic3

Img: sematext.com

Otra manera de solucionar el problema es utilizar una clave hash que permita repartir las claves añadiendo un prefijo que le sea fácil de reconstruir a un cliente a partir de su clave original y utilizar el <code>get</code> de manera normal. Este método es conocido como hashing.

Otra técnica utilizada para resolver el problema del hotspotting es invertir la clave así la parte que más cambia de la clave es puesta al inicio. Esto aleatoriza las claves de fila pero pierde las propiedades de ordenación de filas.

9. Shell

En los siguientes ejemplos vemos como utilizar los comandos básicos que proporciona HBase: list, put, get, scan y delete; en este caso los estamos enviando por línea de comandos.

hbase(main):002:0> list
TABLE
blog
1 row(s) in 0.0300 seconds
=> ["blog"]
hbase(main):003:0> put 'blog', '20130320162535', 'info:title', 'Why use HBase?'
hbase(main):004:0> put 'blog', '20130320162535', 'info:author', 'Jane Doe'
hbase(main):005:0> put 'blog', '20130320162535', 'info:category', 'Persistence'
hbase(main):006:0> put 'blog', '20130320162535', 'content:', 'HBase is a column-oriented...'
hbase(main):007:0> get 'blog', '20130320162535'
COLUMN CELL
 content: timestamp=1386556660599, value=HBase is a column-oriented...
 info:author timestamp=1386556649116, value=Jane Doe
 info:category timestamp=1386556655032, value=Persistence
 info:title timestamp=1386556643256, value=Why use HBase?
4 row(s) in 0.0380 seconds

hbase(main):008:0> scan 'blog', { STARTROW => '20130300', STOPROW => '20130400' }
ROW COLUMN+CELL
 20130320162535 column=content:, timestamp=1386556660599, value=HBase is a column-oriented...
 20130320162535 column=info:author, timestamp=1386556649116, value=Jane Doe
 20130320162535 column=info:category, timestamp=1386556655032, value=Persistence
 20130320162535 column=info:title, timestamp=1386556643256, value=Why use HBase?
hbase(main):009:0>  delete 'blog', '20130320162535', 'info:category'

10. API Java

Ejemplo de como utilizar la API Java proporcionada por HBase para realizar un conjunto de operaciones sobre los datos.

/**
 * Hello world! Conexión y uso básico
 *
 */

public class HBaseDeveloperApi {
    public static void main(String[] args) throws IOException {
        // Configura la conexión a HBase
        org.apache.hadoop.conf.Configuration conf = HBaseConfiguration.create();
        conf.set("hbase.zookeeper.property.clientPort", "2181");
        conf.set("habse.zookeeper.quorum", "localhost");
        Connection conn = ConnectionFactory.createConnection(conf);
     
        Admin admin = conn.getAdmin();

        // crea la tabla si no existe
        if(!admin.tableExists(TableName.valueOf("ns1:persona"))){
            HTableDescriptor table = new HTableDescriptor(TableName.valueOf("ns1:persona"));
            //creating column family descriptor
            HColumnDescriptor cf = new HColumnDescriptor("datos");
            HColumnDescriptor cf2 = new HColumnDescriptor("trabajo");
            
            //adding coloumn family to HTable
            ((HTableDescriptor) table).addFamily(cf);
            ((HTableDescriptor) table).addFamily(cf2);
            admin.createTable(table);
            System.out.println("Tabla 'ns1:persona' creada correctamente.");
        }
        
        // Obtener la tabla
        Table table = conn.getTable(TableName.valueOf("ns1:persona"));
        
        // Insertar datos
        Put put1 = new Put("1".getBytes());
        put1.addColumn("datos".getBytes(), "nombre".getBytes(), "Juan".getBytes());
        put1.addColumn("datos".getBytes(), "apellido".getBytes(), "Garcia".getBytes());
        put1.addColumn("trabajo".getBytes(), "puesto".getBytes(), "Desarrollador".getBytes());
        put1.addColumn("trabajo".getBytes(), "experiencia".getBytes(), "4".getBytes());
        table.put(put1);    

        Put put2 = new Put("2".getBytes());
        put2.addColumn("datos".getBytes(), "nombre".getBytes(), "Mario".getBytes());
        put2.addColumn("datos".getBytes(), "apellido".getBytes(), "Rossi".getBytes());
        put2.addColumn("trabajo".getBytes(), "puesto".getBytes(), "Desarrollador".getBytes());
        put2.addColumn("trabajo".getBytes(), "experiencia".getBytes(), "3".getBytes());
        table.put(put2);        

        // Leer datos       
        Get get = new Get("1".getBytes());
        Result result = table.get(get);
        byte[] nombre = result.getValue("datos".getBytes(), "nombre".getBytes());
        byte[] puesto = result.getValue("trabajo".getBytes(), "puesto".getBytes());
        System.out.println(new String(nombre) + " trabaja como: " + new String(puesto));

        //Realizar un scan e imprimir cada elemento
        Scan scan = new Scan();
        scan.addColumn(Bytes.toBytes("datos"), Bytes.toBytes("nombre"));

        // Datos del scan
        ResultScanner scanner = table.getScanner(scan);

        // Leer valores de un scanner
        for (Result result1 = scanner.next(); result1 != null; result1 = scanner.next())
            System.out.println("Elemento: " + result1.cellScanner());
        scanner.close();

        // Eliminar una tabla
        //admin.disableTables("ns1:persona");
        //admin.deleteTables("ns1:persona");
       
        //Eliminar una fila
        Delete delete = new Delete("2".getBytes());
        table.delete(delete);
        table.close();
    }
}

Fuentes

Apache HBase Book
Dzone
MapR Arquitecture
Best practices for write environments
HBase Schema Design
SemaText

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión /  Cambiar )

Google photo

Estás comentando usando tu cuenta de Google. Cerrar sesión /  Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión /  Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión /  Cambiar )

Conectando a %s