Un Akamai de andar por casa

[spanish]

En la empresa donde trabajo llevamos las ediciones digitales de varios diarios locales repartidos por toda la geografía española. Ninguno es grande en el sentido de tener distribución nacional, pero la gran mayoría son líderes de difusión en su provincia.

Desde hace bastante tiempo hemos tenido problemas de rendimiento con uno de ellos. Los tiempos de carga desde aquí eran muy buenos (<5s), sin embargo los lectores de la región donde se distribuye éste periódico se quejaban continuamente de problemas de lentitud (tiempos de carga >40s, increíblemente altos). Por más que optimizáramos nuestros servidores, la red, el HTML, CSS, el código… el problema persistía, lo que nos hizo pensar que había algo más que se nos estaba escapando.

Después de mucho investigar averiguamos que el proveedor de acceso a Internet mayoritario de esa región, que por supuesto usaba la mayoría de los lectores del diario, tenía problemas de enrutamiento contra nuestro proveedor: un traceroute mostraba que el tráfico se enrutaba a través de Alemania antes de llegar de vuelta a España y a nuestros servidores, con una latencia y tiempos de respuesta elevados.

Así que el problema no era culpa nuestra, la solución real estaba más allá de nuestro alcance, pero nuestra imagen estaba en juego por lo que era responsabilidad nuestra encontrar una solución. ¿Qué podíamos hacer?

Al final nos vino la inspiración y vimos el camino a seguir: conseguir un housing en el proveedor local de la región del diario, configurar allí un proxy inverso y redirigir a éste proxy a todos los lectores de la zona. Por supuesto el problema de lentitud en el enrutamiento entre el proxy y nuestros servidores persistiría, pero como el contenido se iba a cachear y refrescar en segundo plano, el problema no repercutiría nunca en el usuario final.

Hay dos programas implicados en la solución (aparte del servidor web, claro):

  • squid, el proxy más extendido en el mundo Linux/UNIX.
  • djbdns, nuestro servidor DNS favorito, que entre otras cosas permite devolver valores distintos según la IP del cliente que pregunte.

squid

squid es bastante fácil de configurar como proxy inverso. Tras instalarlo (“apt-get install squid” en nuestro servidor Debian) hay que editar el fichero de configuración /etc/squid/squid.conf y modificar:

[code lang=”bash”]
# Actuar como proxy inverso en el puerto 80 en lugar del 3128
http_port 80 vhost

# Tratar peticiones concurrentes de la misma URI como una,

# reduce ancho de banda y en nuestro caso mejora la velocidad
collapsed_forwarding on

# Definir los dominios que vamos a servir, y
# denegar cualquier otro
acl myDomains dstdomain www.example.com isp2.example.com
http_access deny !myDomains
[/code]

Obviamente la configuración del squid es mucho más compleja que ésto. Éstas son las opciones básicas para poner en marcha nuestra solución, empezar a hacer pruebas y a partir de ahí, optimizar: límites de consumo de memoria, gestión de la caché de objetos, gestión de los cache-expires (que en cualquier caso están mejor en el código de la aplicación que en el proxy), compartir cachés entre proxies “vecinos”, y mucho más. Consulta un buen HOWTO de squid o algún libro si quieres aprender a optimizar al máximo éste servicio.

djbdns

Y ahora la parte delicada: el “truco” de enviar a unos lectores al servidor principal, y a otros al proxy. Por suerte, el servidor DNS que utilizamos (djbdns) permite hacer ésto de serie.

Lo que hemos hecho ha sido definir dos nombres, isp1.example.com apuntando a la IP del servidor principal, e isp2.example.com apuntando a la del proxy, y configurar un CNAME que será quien en función de la IP del cliente apuntará a uno u otro nombre. Es más o menos lo mismo que hace Akamai. De ésta forma, podemos acceder fácilmente a cada uno de los servidores para comprobar su correcto funcionamiento.
[code lang=”bash”]
# Rango IP XXX.YYY.x.x, lo dirigiremos al proxy
%PX:XXX.YYY
# Resto
%RS:

# Campos A para el servidor real y el proxy
=isp1.example.com:A.B.C.D:300
=isp2.example.com:Z.Y.X.W:300

# CNAME que pivotará según la IP del cliente
Cwww.example.com:isp2.example.com.:300::PX
Cwww.example.com:isp1.example.com.:300::RS
[/code]

Por supuesto, siguiendo éste esquema podríamos llegar a utilizar tantos proxies queramos en tantos ISPs como podamos necesitar, creando efectivamente una CDN (Content Delivery Network, red de entrega de contenido) tipo Akamai. Os imagináis cómo sería.

Para más información sobre la sintaxys de djbdns, consultar: http://cr.yp.to/djbdns/tinydns-data.html[/spanish]

[english]

At the company I work for, we manage the digital editions of several local newspapers spread all over Spain. None of them is big in a nation-wide sense, but almost all of them are leaders on their region.

For quite some time, we’ve had performance problems with one of them: performance here was good (<5s load times), but the users from the region that particular newspaper is distributed on kept complaining about poor performance (>40s load times, unbelievable high). The more we optimized our server and network infrastructure, the HTML layout, CSS, code… the more they complained and the more obvious it became that there was something else going on.

After some investigations we discovered that the routing between the major ISP of that region, which almost all of our readers used, and ours was the cause of the problem: a traceroute from a local DSL line there to our servers showed that the traffic went to Germany before coming back to Spain, with quite a high latency and high roundtrip times.

So, it wasn’t our fault, the real solution to the real problem was out of our reach, but in the end, our image was at stake so it was OUR problem. What could we do?

After some inspiration the solution became clear: get a housing on the local ISP which had the problems and set-up a reverse proxy there, and redirect all clients of that ISP to this proxy. Sure, the connection between the proxy and our servers would be as bad as before, but as the content would be cached and refreshed on the background, the final user shouldn’t notice it any more!

There are just two pieces of software involved here:

  • squid, the most used proxy on the Linux/UNIX world.
  • djbdns, our DNS server of choice. Among other things, it has the ability to return different IP addresses to an A query depending on the IP address of the client.

squid

squid is quite easy to set-up as a reverse proxy. After installing it (“apt-get install squid” in our Debian-based server) edit the main config file at /etc/squid/squid.conf and:

[code lang="bash"]
# Make it work as a reverse proxy on port 80 instead of 3128
http_port 80 vhost

# Treat several concurrent queries for the same URI as one,
# reduces bandwidth and in our case improves performance
collapsed_forwarding on

# Define wich domains are we going to serve
# Refuse anything else
acl myDomains dstdomain www.example.com isp2.example.com
http_access deny !myDomains
[/code]

Obviously there’s much more to configuring squid than this. These are just the basic options to get our solution going and do some preliminary tests. Then there’s memory limits, object-cache management, cache-expires management (which you better have on your application code anyway), peer caches, and much much more. Get some good Squid HOW-TO or book if you want to learn how to tweak it for optimum performance.

djbdns

Now the tricky part: directing some users to our servers and some other to the proxy. Luckily, the DNS server we use (djbdns) has a built-in option to do this.

What we’ve done is defining two names, isp1.example.com pointing to our IP, and isp2.example.com pointing to the proxy, and then a CNAME which will point to one or another depending on the client’s IP, much like Akamai does. This way we can easily and individually access each server.

[code lang=”bash”]
# XXX.YYY.x.x IP range, will send it to the proxy
%PX:XXX.YYY
# All the rest
%RS:

# A records for our server and the proxy
=isp1.example.com:A.B.C.D:300
=isp2.example.com:Z.Y.X.W:300

# Pivoting CNAME depending on the client’s IP
Cwww.example.com:isp2.example.com.:300::PX
Cwww.example.com:isp1.example.com.:300::RS
[/code]

Of course, following this scheme we could add as many proxies on as many ISPs more as we wanted, creating an Akamai-like CDN (Content Delivery Network). You get the picture.

For more info on djbdns data syntax, please check: http://cr.yp.to/djbdns/tinydns-data.html [/english]

10 comentarios sobre “Un Akamai de andar por casa”

  1. Yes, Akamai offers much much more than this. But in some cases you may need nothing more than getting your content nearer for just one or two ISPs (or branch offices, etc.) This is an easy, cheap and maintainable way to do it.
    And with some re-working and better caching this could also be used as a high-availability solution for static web sites: keep the cache updated and serve all the content off it in case the main site goes down.
    Thanks for the comment. :)

    1. Yes you can, but you’ll have to be very careful with what you cache and what you don’t, fine tuning it either in squid’s config or on your app using the HTTP cache-control headers. Bear in mind that, by default, squid will NOT cache any dynamic content (your case) unless specified with the HTTP headers, so you’ll only be caching the static files in your web (images, CSSs, JSs, etc.) But if most of your pages, albeit dynamicly generated, won’t change for hours, you can setup an aggressive cache strategy and leverage the load of your web server.

      For a great tutorial about HTTP cache control, take a look at:

      http://www.mnot.net/cache_docs/

  2. Thanks a lot for posting this. This helped me fixing another problem that I was trying to find a solution for. Your reference to collapsed_forwarding helped me crack the issue.

    Cheers
    San

Deja un comentario

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.