26 mayo, 2009

Compresión JSON con JSON.hpack

JSON.hpack es un compresor de conjuntos de datos JSON. Es capaz de reducir hasta un 70% el número de bytes necesarios para representar una colección genérica y homogénea (entendiendo por homogénea la que suele ser producida por una consulta a una base de datos relacional en la que se repite una serie de filas con el mismo número de campos).

Así como un resultado con XML como el siguiente...

  <result>
    <item>
      <name>Andrea</name>
      <age>31</age>
      <gender>Male</gender>
      <skilled>true</skilled>
    </item>
    <item>
      <name>Eva</name>
      <age>27</age>
      <gender>Female</gender>
      <skilled>true</skilled>
    </item>
    <item>
      <name>Daniele</name>
      <age>26</age>
      <gender>Male</gender>
      <skilled>false</skilled>
    </item>
  </result>
  <!-- 286 caracteres (sin contar espacios) -->

... puede ser compactado con JSON a la cadena siguiente...

[
  {
    "name":"Andrea",
    "age":31,
    "gender":"Male",
    "skilled":true
  },
  {
    "name":"Eva",
    "age":27,
    "gender":"Female",
    "skilled":true
  },
  {
    "name":"Daniele",
    "age":26,
    "gender":"Male",
    "skilled":false
  }
]
// 177 caracteres (sin contar espacios)

... podemos ver fácilmente que JSON admite una compresión aún mayor, puesto que cada registro repite los nombres de los campos.

El nivel 0 de compresión con JSON.hpack propone reunir los nombres de los campos en un primer registro:

[["name","age","gender","skilled"],["Andrea",31,"Male",true],["Eva",27,"Female",true],["Daniele",26,"Male",false]]
// 115 caracteres

Aún así, se repiten valores (como true, false, male y female) que podrían ser sustituidos por índices para ahorrar aún más espacio (aunque en este ejemplo con 3 registros el resultado no es mejor que el anterior):

[["name",["Andrea","Eva","Daniele"],"age","gender",["Male","Female"],"skilled",[true,false]],[0,31,0,0],[1,27,1,0],[2,26,0,1]]
// 167 caracteres

Como se puede ver en este nivel 1 de compresión, en el primer registro se indican los nombres de los campos y los posibles valores que pueden adoptar siempre que no sean numéricos (el autor cree que no vale la pena realizar la conversión de unos números por otros (índices)). Por ejemplo, Male o True se sustituyen por 0 y Female o False por 1.

Esto aún puede optimizarse más con el nivel 2 de compresión, comparando si vale la pena enumerar los valores en la cabecera y sustituirlos en los datos por índices o si es mejor mantener los datos (en el siguiente caso, los nombres no se enumeran):

[["name","age","gender",["Male","Female"],"skilled",[true,false]],["Andrea",31,0,0],["Eva",27,1,0],["Daniele",26,0,1]]
// 119 caracteres

Esta comprobación se realiza a nivel de la colección entera. Es posible realizarla a nivel de cada campo, comparando la longitud de la enumeración más los índices que serán necesarios con los de indicar directamente los valores:

// Comparando "gender":
  ["Male","Female",0,1,0] < ["Male","Female","Male"] (23 caracteres es menos que 24)
// comparando "skilled" :
  [true,false,0,0,1]      > [true,true,false]        (18 caracteres es menos que 17)

Según esto, el resultado de aplicar el nivel 3 de compresión será:

[["name","age","gender",["Male","Female"],"skilled"],["Andrea",31,0,true],["Eva",27,1,true],["Daniele",26,0,false]]
// 116 caracteres

Y aún se puede extraer un último nivel 4 de compresión comparando todos los anteriores y seleccionando el que menos ocupe. Es posible hacer pruebas en la página de ejemplos.

Aunque parece que la compresión es importante, hay que tener en cuenta que si el contenido se está comprimiendo con gzip desde el servidor, la reducción real en información enviada será mucho menor. Para conjuntos grandes de datos, sí que puede haber una diferencia importante, aunque se pague el precio del procesamiento necesario para comprimir y descomprimir los datos.
Existen librerías para JavaScript, PHP y C#, aunque según el wiki del proyecto, es posible que se amplíe a Python, Ruby o plugin PHP mediante C.

En concreto, la versión JavaScript cuenta con los siguientes métodos:

  • hpack( Array[, Int[0-4]] ):HArray, convierte una colección homogénea a otra comprimida con hpack
  • hunpack( HArray ):Array, convierte una colección hpack a su original (no es necesario indicar qué compresión se usó)
  • hbest( Array ):Int[0-4], devuelve el nivel de compresión ideal para la colección homogénea especificada

3 comentarios:

fcodiaz dijo...

es una buena forma de servir los json.. trate de implementarlo, pero al final desidi retirar la implementación, ya que comparamos bites ahoradosy si son muchos, pero hay que verificar si tu server tiene un compresor en el servidor en mi caso tiene gz y el ahoro era muy pequeño en bites finales, gz ya hace este minado de datos de forma automatica y trasparente, mientras que este metodo nos afecta la programación y el performace... es una muy buena practica si estamos seguros que nuestro server no tiene un sistema de compresion.. si lo tiene el aplicarlo casi tiene un veneficio nulo, igual se puede buscar una forma facil de activar y desactivar para que cuando nuestra aplicacion este en un server sin compresion entre esta forma de compresion, si nuestro server cuenta con compresion no tiene caso y el json deveria de ser tratado normalmente

Anónimo dijo...

Hola fcodiaz Gzip + Hpack es la opción mas factible Gzip no hace lo de Hpack si no se complementan.

Aca un articulo con estadisticas.

http://web-resource-optimization.blogspot.com/

Àl dijo...

Muchas gracias por el enlace. :-)

Publicar un comentario en la entrada

Últimos links en indiza.com