Birds of the same feather…

Carlos J. Gil Bellosta

2016-04-14

Motivación: una consulta y muchos ficheros

Leer muchos ficheros pequeños…

  • Un directorio con cientos de ficheros pequeños
  • Ocupan, en total, varios gigas
  • Todos con la misma estructura
  • ¿Cómo consultar la información eficientemente?

read.table: flexible y lenta

  • read.table es óptima
  • Solo que optimiza varios criterios a la vez
  • (O es una optimización restringida, más bien)
  • Quiere ser muy flexible (véanse los parámetros que admite)
  • Léase también ?read.table con detenimiento para descubrir trucos

Moderneces eficientes

  • data.table::fread
  • readr::read_table
  • ¿etc.?
  • Resumen: menos flexibles, más rápidas

El cuello de botella

  • Las moderneces tienen un techo de eficiencia
  • ¡Es el elefante en el salón!

Ficheros orientados a filas

Ficheros de texto

  • Para leer la fila 45 hay que leer las 44 anteriores necesariamente
  • ¡No se sabe de antemano dónde están los EOL!

Bases de datos (tradicionales)

  • Guardan los datos en filas
  • Además, las filas tienen encabezados; p.e., el de Postgres:
  • ¡Son +20 bytes por fila!

Ficheros orientados a columnas

Una tabla es un vector de columnas

  • Ventajas:
    • No siempre se quieren todas las columnas
    • Compresión
  • Inconvenientes:
    • Inserciones y borrados (ETL)

(Eso es lo que veréis en cualquier benchmark).

Uso de tablas orientadas a columnas

  • R (data.frame)
  • Python + pandas (DataFrame)
  • Muchos DBMS modernos (ver https://en.wikipedia.org/wiki/Column-oriented_DBMS)
  • Incluso algunos tradicionales (opcionalmente)
  • Apache Parquet:
    • Un fichero es un conjunto de grupos de columnas.
    • Cada grupo de columnas contiene un column chunk por columna.

Volvemos al cuello de botella

  • Al leer un CSV en R, los registros se leen por filas y se guardan por columnas
  • Lo mismo al leer de una base de datos (p.e., con un cursor)
  • ¡Incluso si se leen datos de una base de datos columnar!

El cuello de botella, gráficamente

Ficheros nativos de R

Ficheros .Rdat

  • Contienen versiones serializadas de objetos de R (comprimidas o no)
  • Se crean con save
  • load restaura los objetos originales del fichero en memoria
  • Una tabla se guarda por columnas

Ficheros .rds

  • Contienen un único objeto de R serializado
  • Se crean con la función saveRDS
  • Se leen con readRDS
saveRDS(women, "women.rds")
women2 <- readRDS("women.rds")
identical(women, women2)

Feather

feather-format

  • Es un formato binario para tablas
  • Las almacena por columnas
  • Es común a R y Python
  • Está basado en (o es un dialecto de) arrow

Arrow: el objetivo

Arrow especifica cómo almacenar vectores

Y feather lo hace

for (int i = 0; i < n; ++i) {
    SEXP xi = STRING_ELT(x, i);

    if (xi == NA_STRING) {
      length = 0;
      ++n_missing;
    } else {
      // Valid
      util::set_bit(nulls, i);
      const char* utf8 = Rf_translateCharUTF8(xi);
      length = strlen(utf8);
      data_builder.Append(reinterpret_cast<const uint8_t*>(utf8), length);
    }

    offsets[i] = offset;
    offset += length;
  }

Tablas en arrow/feather

  • Son listas de grupos de columnas (vectores)
  • Cada grupo de columnas contiene un tajo horizontal de la tabla
  • Dentro de cada grupo de columnas, las columnas se almacenan como vectores
  • Gracias a los metadatos (offsets, etc.) se puede acceder rápidamente a los bloques (contiguos a cachos) donde reside una columna

Tablas en parquet

Feather en funcionamiento

Véase mi blog

Comentarios

  • No tengo claro si para alguien que solo use R, feather es mejor que los formatos nativos de R
  • Puede ser muy útil si en lugar de usar JDBC o similares los contenedores de datos pudiesen generar ficheros feather