Developing Frogtek

El blog del Departamento de Tecnología

Utilizando gzip en las comunicaciones

Como ya sabéis hace dos semanas estuvimos de ranatón. En mi equipo, uno de nuestros objetivos era optimizar las comunicaciones entre el servidor y el cliente. El objetivo era optimizar todo lo que no habíamos optimizado todavía, ya que, en Colombia (donde están la mayoría de nuestros usuarios), las tarifas de datos aún son muy restrictivas en cuanto a datos enviados/recibidos.

La comunicación actualmente se estaba realizando usando Json y en texto plano.

Tal como este:

{"D":1,"sale_code":"301","type":"sale",
"local_created":"2011-01-05 09:26:57.042","balance_sheet_code":"501",
"customer_code":"-1","products":{"generic1":{"Q":1,"P":300,"F":1}},
"final_price":300,"paid":1,"cash_operation_code":"401"}

En este ejemplo estabamos enviando una operación de tipo venta. Como primera optimización, decidimos acortar los nombres de las propiedades. De este modo nos quedó:

{"D":1,"SC":"301","T":"sale","LC":"2011-01-05 09:26:57.042",
"BS":"501","CC":"-1","PR":{"generic1":{"Q":1,"P":300,"F":1}},
"FP":300,"PD":1,"CO":"401"}

Así conseguimos reducir bastante el tráfico de datos, pero necesitabamos reducirlo todavía más, así que tomamos la decisión de utilizar la compresión gzip, y comprimir todo el texto enviado y recibido por el cliente. Y después de comprimirlo lo convertimos en una string con BASE64, de este modo formateabamos los datos sin retornos de carro y todo quedaba, si cabe, mas uniforme:

H4sIAAAAAAAAADWOzQoCMQyEX0Xm3JW03SrmaMWT
aHU9KR5EiwiyygpeSt/dtCIE8s3kN+H5AuMwrK3toXCNH5GkBT
fgY8ICrBU6L66t9l7ofX5EwVVxDWndkIQb0YzNhN10TK2R8ryT
sqtDvnQ2hcIOnHCLfRzuF114Wy8EsCVSWIrKWfLfCL8PvLyDVpb
lU/4C8o/at7UAAAA=

No es el caso más optimo para comprimir, pero hemos calculado que enviamos un 50% menos de datos al enviar la información comprimida. Para poder medir esto utilizamos WireShark con un filtro tal como este (Gracias Jose):

(ip.src == 192.168.1.9  &&
           ip.dst == 192.168.1.12 &&
           tcp.port == 8080)

Para poder realizar la compresión en el cliente Android utilizamos la clase GZIPOutputStream de este modo:

public static byte[] zipStringToBytes( String input  ) throws IOException
  {
    ByteArrayOutputStream bos = new ByteArrayOutputStream();
    BufferedOutputStream bufos = new BufferedOutputStream(new GZIPOutputStream(bos));
    bufos.write( input.getBytes() );
    bufos.close();
    byte[] retval= bos.toByteArray();
    bos.close();
    return retval;
  }

Y en la parte del servidor, nosotros utilizamos Google App Engine:

def decode_client_data(request_object):
    if(request_object.META.get('HTTP_CONTENT_ENCODING',None)!=None):
        gzip_string = base64.standard_b64decode(str(request_object.raw_post_data))
        url_file_handle=StringIO(gzip_string)
        gzip_file_handle = gzip.GzipFile(fileobj=url_file_handle)
        decompressed_data = gzip_file_handle.read()
        gzip_file_handle.close()
        return decompressed_data
    else:
        return request_object.raw_post_data

Gracias a la integración continua podemos asegurarnos de que ningún cliente trabajará contra una servidor que no acepte gzip, pero para evitarnos problemas y poder sacar versiones a producción del servidor sin tener que hacerlo del cliente, decidimos programar el servidor para que acepte operaciones sin comprimir y en versión de nombres de propiedades largas.

Para que el servidor nos envie la información comprimida en gzip es mucho más fácil, puesto que lo hace de manera automática siempre y cuando en las peticiones le digamos que nuestro cliente acepta compresión gzip. Nosotros lo hemos hecho de este modo:

HttpPost httppost = connectPOST(url);
   httppost.setHeader("Accept-Encoding", "gzip");
   httppost.setHeader("Content-Encoding", "GZIP");
   httppost.setHeader("User-Agent", "gzip");

Hay que tener muy presente que el gzip automático por parte de GAE no funciona en local.

Si tenéis alguna duda o sugerencia no dudéis en comentarla.

4 Comentarios

  1. Hola Pedro,

    Podeís utilizar un truco “guarrete” previo a la compresión GZIP que consiste en eliminar las comillas de los nombres de las propiedades del objeto JSON que transmitís. En el ejemplo quedaría así:
    {D:1,SC:”301″,T:”sale”,LC:”2011-01-05 09:26:57.042″,BS:”501″,CC:”-1″,PR:{generic1:{Q:1,P:300,F:1}},FP:300,PD:1,CO:”401″}

    OJO: En Javascript el objeto anterior es válido, pero la especificación JSON establece que deben utilizarse comillas (aunque la mayoría de los parsers aceptan las propiedades sin comillas). Si controlais el entorno (sobretodo el parser) cliente podeis ahorrar unos bytes así.

    Saludos,
    Dave

    • Hola David!!

      Gracias por tu comentario. Me parece muy interesante tu sugerencia. Somos nosotros los que controlamos el servidor así que podemos interpretar los json sin comillas. Estudiaremos esta optimización. Ya te contaremos en que queda esto.

      Saludos.

  2. Interesante opción. Comentas que hacéis esto para enviar la menor cantidad de datos posibles debido a la restrictiva tarificación de datos que tienen vuestros usuarios pero… en cuanto a tiempo de sincronización, también compensa comprimir-enviar-descomprirmir en lugar de simplemente enviar? o en ese sentido es más lento?

    Un saludo

    • Hola Esteban!!! Gracias por animarte a comentar.

      Lo que comentas es algo que nos preocupa, y ya lo pensamos en su momento. Las pruebas que hemos hecho han ido bien y no hemos notado incremento de cpu por parte del servidor ni del cliente.

      Hoy por hoy para nosotros es mas prioritario no consumir datos, quizás dentro de un tiempo con cientos de usuarios el sistema consuma mucha cpu (sobre todo por parte del server) y nos penalice. En ese caso escribiremos una nueva entrada con la decisión tomada.

Deja un comentario

Tu dirección de correo electrónico no será publicada.

*