← blog

Construcción de vistas front-end en Hadoop

Uno de los casos de uso más habituales de Hadoop es la construcción de “vistas”. El típico caso es el de un sitio web que sirve sus datos en un front-end que usa un índice de búsqueda. ¿Por qué usar Hadoop para generar el índice del cual tirará la web? Hay varias razones:

  • Paralelismo: Cuando el front-end ha de servir muchos datos, conviene repartirlos en “shards”. Con Hadoop podemos paralelizar la creación de la vista de cada “shard” para que tanto la generación de la vista como su servicio escalen y sean eficientes.
  • Eficiencia: Para maximizar la eficiencia y velocidad de un front-end, es conveniente separar la generación de la vista de su servicio. La generación se hará en back-end con Hadoop y el servicio en front-end, liberando así a este último de la carga que pueda producir el indexado mismo.
  • Atomicidad: A menudo es conveniente disponer de un generado y despliegue de vistas atómico. De este modo, si el despliegue de la vista ha ido mal, podemos volver a versiones anteriores (rollback) fácilmente, o bien si la generación ha ido mal podemos generar una nueva vista completa donde el error se solucionará para todos los registros. Hadoop permite la generación atómica de una vista por su orientación batch. Algunos motores / bases de datos permiten un despliegue atómico (hot-swap) de sus datos completos.

El esquema de la arquitectura a seguir el siguiente:

Un proceso Hadoop recibe los datos – completos – que la vista contendrá. Éste proceso se paraleliza para generar tantos “shards” como sean necesarios para el front-end.

Ejemplos de uso

Caso de uso #1

Apache SOLR

A pesar de la poca información clara al respecto, usar el patch SOLR-1303 es muy sencillo. Si nuestro Map/Reduce recibe los datos a indexar, sólo tenemos que implementar un Reducer (no hace falta Mapper) de la siguiente manera:

public static class SolrBatchIndexerReducer {
  protected void setup(Context context) throws IOException, InterruptedException {
    super.setup(context);
    SolrRecordWriter.addReducerContext(context);
  }
}

Lo siguiente que tenemos que hacer es implementar un SolrDocumentConverter que convierta los datos (llave, valor) que nuestro Map/Reduce recibirá en documentos indexables (SolrInputDocument). Supongamos que nuestro Map/Reduce recibe pares [Text, Text] con una llave y un valor:

public class MyDocumentConverter extends SolrDocumentConverter<Text, Text> {
  @Override
  public Collection<SolrInputDocument> convert(Text key, Text value) {
    ArrayList<SolrInputDocument> list = new ArrayList<SolrInputDocument>();
    SolrInputDocument document = new SolrInputDocument();
    document.addField("key", key);
    document.addField("value", value);
    list.add(document);
    return list;
  }
}

¡Ojo! Tenemos que tener una configuración para SOLR (schema.xml) acorde con lo que estamos haciendo en el SolrDocumentConverter. El patch instanciará un EmbeddedSolrServer que leerá esa configuración. Finalmente, para configurar nuestro Job, además de lo habitual especificaremos lo siguiente:

// ...
SolrDocumentConverter.setSolrDocumentConverter(MyDocumentConverter.class, job.getConfiguration());
job.setReducerClass(SolrBatchIndexerReducer.class);
job.setOutputFormatClass(SolrOutputFormat.class);
File solrHome = new File("solr-conf/my-core");
SolrOutputFormat.setupSolrHomeCache(solrHome, job.getConfiguration());

¿Cómo se distribuye la configuración SOLR a todo el cluster Hadoop? -> En la máquina en la que lancemos la indexación tenemos que tener la configuración SOLR usada para indexar. El patch ya la zipeará y la distribuirá a todo el cluster a través de la DistributedCache.

Para información más avanzada sobre SOLR-1301, ver éste link.

  • Despliegue con API de SOLR:

Para el despliegue podemos usar la librería SolrJ de la API de SOLR. Contiene un conjunto de instrucciones sencillo para poder manipular la creación, intercambio y destrucción de “cores”.

CoreAdminRequest.Create req = new CoreAdminRequest.Create();
// ...
adminServer.request(req);
CoreAdminRequest aReq = new CoreAdminRequest();
aReq.setAction(CoreAdminAction.SWAP);
aReq.setCoreName("liveCore");
aReq.setOtherCoreName("tmpCore");
// ...
adminServer.request(aReq);

Por tanto, un requerimiento es que nuestro SOLR esté configurado en modo multi-core.

Caso de uso #2

Project Voldemort

  • Front-end que sirve sus datos en Voldemort (una base de datos llave-valor rápida desarrollada por el equipo de SNA – LinkedIn).
  • Creación de la vista con AbstractHadoopStoreBuilderMapper (ver éste link).
  • Script de despliegue swap-store.sh también descrito en éste link.

Caso de uso #3

Un caso particular que sólo tú necesites.

  • Front-end particular que sirve tus datos en un formato particular.
  • Creación de la vista:

¿Cómo distribuir la carga de la generación de varios frontales, uno para cada “shard”? Para éste paralelismo tenemos varias opciones:

  • Un shard por Reducer: Por ejemplo, en un típico caso de particionado equitativo. Usamos una función hash para distribuir equitativamente los datos a “N” Reducers – por ejemplo, el Partitioner por defecto de Hadoop – y hacemos que cada uno de ellos genere una vista. Es el mecanismo que emplea el SOLR-1301 que hemos mencionado antes
  • Un shard por grupo de Reducer: Por ejemplo, si queremos agrupar lógicamente los shards por alguna llave. Si quisiéramos generar un shard por país, ésta opción podría ser la más sencilla. En este caso agruparíamos los registros por país en el Mapper y generaríamos la vista en el Reducer para cada grupo que llegara.

En éstos casos lo conveniente es implementarse un OutputFormat propio.

¿Cómo escribir cualquier tipo de fichero para front-end como salida de una tarea Hadoop? En estos casos lo que se hace es usar la API de Hadoop para crear ficheros de cualquier tipo en el directorio de salida de una tarea. Por ejemplo:

Path perm = FileOutputFormat.getOutputPath(context);
Path tmpLocalFile = new Path("shard-1");
Path localFileToUse = fS.startLocalOutput(perm, tmpLocalFile);
// ...
fS.completeLocalOutput(perm, tmpLocalFile);
  • Despliegue de la vista: Con la API Java de Hadoop, con un script, con r-sync…

Desventajas / Casos de no uso

  • Este método es un método eficiente de generación y despliegue atómico de vistas completas. No sirve, por ejemplo, para mantener una vista que se actualiza en tiempo real de forma incremental.
  • Front-ends a los que a día de este artículo no podemos aplicarlo: ElasticSearch (a pesar de Wonderdog y éste buen artículo, ya que no podemos conseguir atomicidad en el despliegue. La manera de desplegar los datos es a través de peticiones incrementales HTTP lo cual también convierte el indexado en un proceso potencialmente poco eficiente).
  • MongoDB: En espera de lo que suceda en éste proyecto, a día de este artículo no es posible generar los datos completos de una tabla Mongo y desplegarla atómicamente. Tiene el mismo problema que ElasticSearch.

Para finalizar

En esta presentación Marc Sturlese de Trovit cuenta algunos detalles de cómo Trovit genera sus índices con Hadoop.

Y un caso de uso avanzado peculiar expuesto por Marc de Palol: generación de vistas con archivos HFile.



Comentar