Volver al blog

Síguenos y suscríbete

Cómo trasladamos nuestras estadísticas históricas desde MySQL a Bigtable sin sufrir tiempo de inactividad

Toru Maesaka

Staff Software Engineer

Aprender de las lecciones del pasado es esencial en la toma de decisiones. En Fastly ofrecemos nuestra API de estadísticas históricas para ayudar a los clientes a tomar decisiones mejores y más rápidas a partir de acontecimientos pasados. Esta API te permite recuperar estadísticas históricas de almacenamiento en caché filtradas por minuto, hora y día, lo que ofrece una visión clara sobre los eventos y permite dar forma a las decisiones venideras. En esta entrada de blog, desgranaré paso a paso cómo trasladamos la base de datos API de estadísticas históricas desde la instancia autogestionada de MySQL a Google Cloud Bigtable sin que por ello sufriéramos tiempos de inactividad o interrupciones en el servicio.

Prioridades y tecnología

Entre los múltiples desafíos tecnológicos que supone diseñar una API de estadísticas históricas (y su canalización de distribución internacional), figura el inmenso volumen de referencias pequeñas que se tienen que procesar, almacenar y organizar para su rápida recuperación.

En nuestra arquitectura anterior, optamos por diseñar un servicio sobre una base de datos MySQL para almacenar y proporcionar estadísticas históricas. No tuvimos dudas al escoger MySQL/InnoDB, debido a su sólido ecosistema y a nuestra familiaridad con el software. Estos factores siguen dándose a día de hoy: ejecutamos MySQL en muchos otros servicios. Por tanto, ¿por qué llevarnos las estadísticas históricas de MySQL a otra parte? Aunque la arquitectura original funcionaba bien, éramos conscientes de que pronto se nos quedaría pequeña: el incesante crecimiento del volumen de datos que manejábamos provocó que nos diéramos de bruces con restricciones físicas (como la capacidad de almacenamiento), lo cual también se tradujo en una mayor sensibilidad del esquema a las modificaciones. Nos veíamos abocados a tomar una decisión: continuar ampliando nuestra infraestructura estadística en una base de datos MySQL o buscar otra solución, que incluyera analizar proveedores de bases de datos en la nube. La segunda posibilidad era atractiva porque al transferir la gestión del mecanismo de almacenamiento subyacente podríamos centrarnos más en mejorar nuestros productos analíticos, lo cual a su vez se traduce en la consecución de mayor valor para nuestros clientes. En último término, nos decidimos por Google Cloud Bigtable debido a su capacidad de gestionar altos volúmenes de datos (p. ej., datos de series temporales), su diseño de tablas dispersas y su capacidad de ampliación a través del mecanismo de nodo dinámico. Considerado en su conjunto, Google Cloud Platform (GCP) cobraba sentido para nosotros debido a su compatibilidad con un riguroso cumplimiento normativo y a las interconexiones globales existentes entre Fastly y GCP.

Cloud Bigtable es un servicio de bases de datos de columna ancha multidimensional y masivamente ampliable que se ofrece a través de GCP. Puesto que esta entrada de blog no trata propiamente sobre Cloud Bigtable, me abstendré de analizar los detalles técnicos. Sin embargo, consulta este estupendo artículo si te interesan el diseño y los detalles secundarios de Bigtable.

Anatomía de la API de estadísticas históricas de Fastly

Las estadísticas históricas incluyen datos agregados tales como número de solicitudes, uso de banda ancha y proporción de aciertos, lo que brinda un resumen exhaustivo de tu tráfico. No se incluyen datos personales ni datos de envíos de logs remotos.

De la prestación de la API de estadísticas históricas que ve el público se encarga Ruby, una pequeña aplicación web de solo lectura que consume datos históricos provenientes de un servicio distinto. En cambio, el rellenado de datos corre a cargo de un servicio dedicado que consume y procesa mensajes entrantes provenientes de nuestra canalización de estadísticas de distribución internacional. (En realidad, ocurren más cosas, pero ya te haces una idea). Fundamentalmente, teníamos que crear otro canal que conectara Bigtable con la canalización de estadísticas globales y escribir una capa de abstracción para facilitar a Ruby, nuestra aplicación front end, la extracción de datos de Bigtable de diversas formas, tales como por servicio y centros de datos. No está mal, ¿verdad? Piénsalo un poco más.

Pon en duda cualquier detalle que pueda salir mal

Cambiar el antiguo servicio de datos por uno nuevo y cruzar los dedos habría sido irresponsable. Nos aseguramos de que el cambio no ocasionara interrupciones a nuestros clientes poniendo en duda muchos factores, incluidos los siguientes:

  • ¿Son los datos recogidos en Bigtable coherentes con MySQL?

  • ¿Son correctas las respuestas provenientes de la API de estadísticas históricas?

  • ¿Qué ocurre si surge un fallo en la canalización de estadísticas?

  • ¿Son aceptables las características de rendimiento del sistema nuevo?

  • ¿Existe algún caso en el edge que pudiéramos haber pasado por alto?

  • ¿Es suficiente la monitorización de nuestro sistema para descubrir irregularidades?

Realización de pruebas para lograr la exactitud a escala

Para garantizar que los datos fueran coherentes entre Bigtable y MySQL, diseñamos varios métodos de verificación y los ejecutamos de manera independiente. Uno de estos se ejecuta con periodicidad en la capa de base de datos, comparando así filas dentro de Bigtable, MySQL y otras bases de datos. Otro método de verificación se ejecuta en la capa de API web, en la que todas las solicitudes de la API de estadísticas históricas se transmiten a un servicio de trabajo de verificación asíncrono. Este servicio de trabajo de verificación se encarga de comparar respuestas calculadas con Bigtable y MySQL respecto de la solicitud dada, sin interrumpir su ciclo de vida. Si se detecta una incoherencia, el servicio de trabajo registra el evento en nuestra plataforma de analíticas de datos para analizarla con posterioridad y para descubrir en tiempo real incoherencias de datos. Puesto que nuestro front end de API es una aplicación Ruby, fuimos capaces de sacar provecho de la magnífica biblioteca scientist de GitHub en nuestro servicio de trabajo de verificación, lo cual nos ayudó a probar en condiciones de seguridad nuestro nuevo código de recuperación.

Terminamos por llevar a cabo algo así como un proceso de verificación "de instantáneas" durante meses antes de migrar totalmente las solicitudes de producción al nuevo servicio de datos. Recomendamos encarecidamente adoptar este tipo de enfoque ya que en realidad puede desentrañar todo tipo de detalles interesantes, incluidos costes de cálculo inesperados. Por ejemplo, la supervisión de rendimiento de aplicaciones que hicimos mostraba que la mayoría del tiempo dedicado a calcular respuestas basadas en Bigtable obedecía a la latencia de la red, lo cual nos permitió además darnos cuenta de que necesitábamos migrar nuestros servidores de API de estadística a un lugar más cercano a Bigtable. El entorno de producción tiene algo especial de lo que todos y todas podemos extraer lecciones.

Migración gradual de enrutamientos de API

El desarrollo de pruebas durante meses nos permitía tener una relativa seguridad de que nuestra implementación de Bigtable (y el software pertinente) iba a estar lista para el tráfico de producción. Aun en ese caso, decidimos tomar precauciones extraordinarias y migrar solo uno o dos enrutamientos de API cada vez para garantizar que todo funcionara según lo previsto. Aunque esta estrategia de migración parcial parezca compleja, todo salió a pedir de boca gracias a lo fácil que resultó configurar el enrutamiento de solicitudes con VCL. Te mostramos un ejemplo bien sencillo para que te hagas una idea:

Dados dos servidores back end, api_node y api_experimental_node:

backend api_node {
  .host = "127.0.0.1";
  .port = "443";
  … snip ...  
}

backend api_experimental_node {
  .host = "127.0.0.2";
  .port = "443";
  … snip ...
}

Una opción es dirigir las solicitudes de API frente a un recurso específico al nodo experimental haciendo coincidir la ruta de la solicitud.

sub vcl_recv {
  if (req.url ~ "^/path/to/resource") {
    set req.backend = api_experimental_node;
  } else {
    set req.backend = api_node;
  }
}

Del mismo modo, podrías forzar que una solicitud alcanzara el nodo experimental si un encabezado HTTP concreto figurara en la solicitud. Esto es de especial utilidad para la depuración de extremo a extremo.

sub vcl_recv {
  if (req.http.Api-Debug ~ "experimental" || req.url ~ "^/path/to/resource") {
    set req.backend = api_experimental_node;
  } else {
    set req.backend = api_node;
  }
}

Cualquier lector que conozca bien VCL podría estar preguntándose qué ocurriría si yo tuviera múltiples nodos de API por motivos de rendimiento y redundancia (lo cual suele ser cierto). No temas, esto también es fácil de configurar: échale un vistazo a nuestra documentación relativa a la puesta a punto de tus ajustes de balanceo de carga.

Nuestras conclusiones: piensa con valentía y con estrategia

El traslado de un sistema de bases de datos persistentes correspondiente a un servicio activo, mientras se evitan tiempos de inactividad e interrupciones del servicio, no solo es complicado, sino que además requiere paciencia y un montón de pruebas. Es el típico problema cuya solución es más fácil plantear que poner en práctica. Las metodologías presentadas en esta entrada de blog nos funcionaron y esperamos que te resulten de ayuda si tienes pensado abordar un problema parecido.

Por el momento, el traslado de la base de datos de MySQL a Bigtable y a su nuevo canal de datos ha ido bien: los datos conservan su coherencia y los servicios de cliente continúan comportándose dentro de lo previsto. Sin embargo, mentiríamos si dijéramos que todo salió a la perfección a la primera: hemos efectuado modificaciones sobre la marcha, incluidas la reconsideración y el rediseño del modo en que escribimos código en Bigtable para lograr una mayor producción. Mantente al tanto: esperamos tratar estas lecciones y mejoras secundarias en futuras entradas de blog.

Para obtener más información sobre nuestra API de estadísticas históricas, échale un vistazo a nuestra documentación; también te recomendamos investigar nuestra API de analítica en tiempo real.