Skip to content
Rendimiento en Aplicaciones Web
Branch: master
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
.gitignore
README.md

README.md

Rendimiento en Aplicaciones Web

  • 1.- Fundamentos

    • 1.1- Rendimiento en el Software
    • 1.2- Rendimiento en Web
      • 1.2.1 - Qué optimizar
      • 1.2.2 - Análisis de rendimiento: Profiling
    • 1.3 - Rendimiento en PHP
    • 1.4 - Pruebas de rendimiento y estrés
  • 2.- Caché

    • 2.1 Conceptos básicos
    • 2.2 Llenar la Caché
    • 2.3 Mantener la Caché y el Sistema Remoto sincronizados
    • 2.4 Gestionando el tamaño de la Caché
    • 2.5 Caché distribuidas vs Data Grid vs NoSQL
    • 2.6 Proxy Inverso
    • 2.7 Web Caching
    • 2.8 Service Workers y Caché
  • 3.- Paralelismo y Concurrencia

    • 3.1 Introducción
    • 3.2 Colas de Mensajes
    • 3.3 Síncrono vs Asíncrono en Javascript
  • 4.- Big Data

    • 4.1 Conceptos básicos
    • 4.1 MapReduce e Indexación
    • 4.3 Motores de Búsqueda
    • 4.4 Escalabilidad

1.- Fundamentos

1.1 - Rendimiento en el Software

En términos generales, llamamos rendimiento (performance) en el software a la cantidad de trabajo completado por un sistema computacional en relación al tiempo empleado.

Las métricas para evaluar el rendimiento del software incluyen:

  • Disponibilidad (Availability): La alta disponibilidad representa la capacidad de un sistema que permite estar ejecutándose la mayor parte del tiempo (less downtime). La forma más básica para medir la disponibilidad es calcular el porcentaje de tiempo que el sistema está funcionando. Se dice que un sistema es tolerante a fallos (fault tolerant) cuando su disponibilidad es del 100%. Una técnica común para aumentar la disponibilidad es el escalado (scaling).

  • Tiempo de Respuesta (Response Time): Se trata del tiempo tiempo total que un sistema necesita para responder a una petición de unidad de trabajo. Es la suma de tres variables:

    • Tiempo de Servicio (Service Time): Tiempo total.

    • Tiempo de Espera (Wait Time): Es el tiempo que la petición ha de permanecer en una cola de peticiones antes de ser procesada.

    • Tiempo de Transmisión (Transmission Time): El tiempo que se necesita para desplazar los datos de la petición hasta el componente encargado de procesarla, así como los datos de la respuesta.

  • Velocidad de Procesamiento (Processing Speed): Depende principalmente de las características de hardware del sistema. La forma más general de medirlo es en instrucciones por segundo, y su versión más precisa: FLOPS (Floating Point Operations Per Second)

  • Capacidad del Canal (Channel Capacity): Representa la cantidad de información que se puede transmitir de forma confiable a través de una canal de comunicación.

  • Latencia (Latency): La latencia es el desfase de tiempo entre la causa y el efecto de un sistema observado. Es físicamente una consecuencia del límite de velocidad con la cual cualquier interacción física puede propagarse. También se suele llamar lag, cuando nos referimos a online gaming.

  • Ancho de Banda (Bandwidth): Es una medida que expresa la tasa máxima de bits por segundo (o alguno de sus múltiplos) que un sistema es capaz de consumir. Es una medida teórica.

  • Caudal (Throughput): Cantidad de información que viaja a través del canal un canal de comunicaciones de forma exitosa. Esta métrica depende del protocolo utilizado, la latencia y otros aspectos. Se diferencia del ancho de banda, en que esta no es una medida teórica, es una medida real en la que influyen más factores que la capacidad teórica.

  • Eficiencia (Efficiency): Describe aquel proceso que utiliza la menor cantidad de recursos posibles para generar la mayor cantidad de resultados.

  • Escalabilidad (Scalability): Es la capacidad de adaptación de un sistema o proceso para poder manejar una creciente demanda de trabajo. La objetivo de la escalabilidad es mantener (o mejorar) el rendimiento a pesar el incremento de la demanda. Existen dos tipos (no son excluyentes):

    • Vertical: Significa aumentar la potencia de cálculo de uno de los nodos del sistema, como añadir RAM o CPU. Este crecimiento está ligado al hardware, por lo que tendrá un límite. No permite la alta disponibilidad, aunque es mucho más fácil de implementar que el escalado horizontal ya que normalmente no requiere cambios en el software.
    • Horizontal: Se basa fundamentalmente en tener varios nodos idénticos que actúan en conjunción para repartirse el trabajo, creando algo que se conoce como cluster. Teóricamente se pueden añadir infinitos nodos así que el escalado no tiene limites. Es más complejo de implementar ya que require de un software que reparta y organice el trabajo de todos los nodos como si fuese uno solo. Normalmente los nodos se crean y se destruyen bajo demanda para ahorrar costes innecesarios.

    El término Elasticidad (referido a computación en la nube) hace referencia a la capacidad de un sistema para adaptar sus recursos a la demanda de trabajo necesaria de forma automática.

  • Ratio de Compresión (Compression Rate): La compresión es útil porque permite reducir el uso de recursos (almacenamiento en disco o la capacidad de transmisión). Pero esto no es gratis, comprimir y descomprimir información requiere un coste computacional, así que se trata de trade-off.

Dependiendo del contexto, el concepto de alto rendimiento incluye los siguientes aspectos:

  • Tiempo de respuesta corto para una determinada unidad de trabajo.

  • Caudal elevado (high throughput) para una determinada unidad de trabajo.

  • Baja utilización de recursos computacionales.

  • Rápida (y optimizada) compresión y descompresión de datos.

  • Mayor ancho de banda (high bandwidth) y cercanía de los recursos al usuario final

  • Tiempo de transmisión reducido.

Un concepto importante es también el Rendimiento Percibido (perceived performance). Se refiere a lo rápido que parece que una tarea está siendo completada. Es una medida subjetiva. Normalmente el rendimiento percibido mejora cuando mejora el rendimiento objetivo, aunque en casos en los que el rendimiento real no puede mejorar existen técnicas para mejorar el rendimiento percibido. Por ejemplo, durante la descarga de un archivo, si el usuario ve una barra de progreso con el porcentaje descargado el rendimiento percibido será mejor que si el sistema no proporciona ningún tipo de feedback. Es decir, una aplicación no es más rápida por mostrar un splash screen, pero sin duda satisface una necesidad humana al aparentarlo.

1.2- Rendimiento en Aplicaciones Web

El Rendimiento en Web se refiere fundamentalmente a la velocidad de las a Aplicaciones Web. WPO (Web Performance Optimisation) es el campo de conocimiento que intenta mejorar el rendimiento en web. La optimización del rendimiento en web mejora la Experiencia de Usuario (User Experience).

Muchos desarrolladores se centran en optimizar los procesos en el backend de las aplicaciones, cuando en realidad representan un 10-20% del tiempo total de carga (end user time) siendo el 80-90% restantes procesos que se ejecutan en el cliente.

1.2.1 - Qué optimizar

  • Mejorar el tiempo de descarga RTT ( Round-trip Time): El RTT total es el tiempo que transcurre entre que solicitamos datos y el momento en el que se nos muestran por completo. El hecho de alojar la aplicación web lejos del usuario final puede incrementar el RTT de las peticiones. Es muy importante servir la aplicación en las regiones en las que se encuentran los usuarios finales. Minimizar las resoluciones DNS o las redirecciones HTTP también ayudan a mejorar el tiempo de descarga.

    Para un navegador, la primera conexión con un servidor web incluye un mínimo de tres RTTs: una para la resolución del nombre DNS, otra para la configuración TCP y otra para la petición HTTP (hasta recibir el primer byte de la respuesta). En otros escenarios más complejos el tiempo necesario para obtener la respuesta deseada puede llegar a desgranarse en docenas de RTTs, así que reducir el número de RTTs necesarios se traduce en una mejora del rendimiento.

  • Reducir el número de peticiones HTTP: Un sitio web con más peticiones http no es necesariamente menos rápido que otra que realiza más, la clave está en que no todas las peticiones son iguales, algunas pueden bloquear partes de la UX en la página. Algunas técnicas dentro de este bloque de optimización son:

    • Combinación de recursos (bundling): Se refiere a la fusión del contenido de varios archivos en un paquete para poder ser descargado en una sola petición. El Bundling se suele aplicar a archivos CSS y JS. La ventaja principal es que es más rápido requerir un solo archivo que varios, liberamos recursos de paralelismo del navegador y normalmente se provee mejor ratio de compresión de los recursos solicitados. La desventaja principal es enviar código en ese bundle que no es necesario para la página que estamos mostrando. Otra desventaja sería la invalidación del bundle tras realizar cualquier cambio, incluso un cambio pequeño, sería necesario invalidar la cache del bundle y volver a descargarlo. Además, e importante para el futuro, el nuevo protocolo HTTP/2 tiene un overhead mucho menor a la hora de realizar múltiples peticiones desde el mismo sitio web, reduciendo la necesidad de combinación de recursos en algunos casos.

    • Sprites CSS y SVG: Técnica milenaria que une varias imágenes en una sola imagen. Al renderizar la imagen utilizaremos posiciones relativas al contenedor para mostrar solamente una sección concreta. Es posible también utilizar sprites con archivos SVG.

    • CSS en lugar de imágenes: En ocasiones, utilizando CSS, podemos ahorrarnos el uso de imágenes, reduciendo así el número de peticiones al servidor web. Se puede pintar con CSS y es asombroso lo que se puede llegar a conseguir con poco esfuerzo.

    • GraphQL vs REST: REST y más concretamente las APIs RESTful son un estándar de facto para la creación de interfaces públicas de servicios web. Es un protocolo muy fácil de implementar y mantener, con unas reglas de construcción bien definidas. Tiene tres componentes principales que son los recursos, las colecciones y las acciones. Los recursos y las colecciones se acceden a través de endpoint diferentes, y los verbos HTTP (POST, PUT, GET...) representan las diferentes acciones que podemos realizar sobre ellos. Es muy común que para mostrar todo el contenido de una página web haga falta más de una llamada HTTP para conseguir todos los recursos necesarios (ya que previsiblemente se encontrarán en endpoints diferentes, por ejemplo /usuarios y /productos). GraphQL es una alternativa para la creación de APIs que intenta resolver el problema de tener que enviar múltiples peticiones al servidor para obtener la información agregada que necesitamos. Normalmente solo necesitaremos una sola petición y la respuesta contendrá exactamente la información que necesitamos (usuarios y productos). Esto ayuda de forma drástica a la reducción de las peticiones HTTP a nuestro servidor y por tanto, mejorará el rendimiento de la aplicación.

  • Caché: Estableciendo una política de caché se propicia que el navegador no tenga que repetir peticiones al servidor. Es útil fundamentalmente para el contenido estático que no suele cambiar. Es posible implementar un sistema de invalidación de cachés (cuando el archivo estático cambia) utilizando cabeceras HTTP como “last-modified” , “E-tag”, o incluso añadiendo un número de versión al propio recurso.

  • Evitar las redirecciones 301: Enlazar de forma consistente el sitio web es muy importante para evitar peticiones extra debido a redirecciones permanentes.

  • Service Workers: Los Service Workers son un proxy server que intercepta peticiones realizadas inicialmente al servidor web para devolver una respuesta. Usando Service Workers podremos responder al cliente inmediatamente cuando, por ejemplo, no hay conexión. Es uno de los componentes clave que permite que una aplicación web cumpla la especificación PWA (Progressive Web Application)

  • Mejorar el renderizado del navegador: Algunas técnicas utilizadas son:

    • Incluir JS después del CSS: Los archivos JS bloquearán la interpretación de los archivos CSS debido a la necesidad de ejecutar el código JS, antes de aplicar los estilos CSS al documento. Cargando el CSS antes del JS el navegador puede empezar a renderizar el documento antes.

    • JS no bloqueante: Existen varias técnicas para evitar que la ejecución del código JavaScript bloquee el navegador. El primer método consiste en utilizar el atributo defer en las etiquetas <script>. Añadiendo defer, el navegador comienza la descarga del archivo Javascript inmediatamente pero no bloquea el renderizado o la descarga del resto de elementos de la página. El segundo método utiliza el atributo async de las etiquetas <script> en HTML5. Igual que el anterior, los scripts se descargan inmediatamente de un modo no bloqueante. La diferencia entre los atributos async y defer es que el primero se ejecuta tan pronto como el script es descargado, por lo que la página puede estar aún descargando otros recursos. Una segunda diferencia es que el orden de ejecución para los archivos cargados mediante async no está garantizado. El último método, y el más popular, es usar etiquetas <script> dinámicas creadas con Javascript.

    • Precarga de recursos: Es una buen idea para mejorar la UX descargar recursos previamente al momento en el que el usuario realmente los necesita si sabemos de antemano como se comportan normalmente. Tenemos que tener cuidado de no sobrecargar la memoria del navegador o de afectar a la UX de la propia página.

    • Especificar las dimensiones de las imágenes: Si especificamos las dimensiones de las imágenes podemos ayudar al navegador a renderizar más eficientemente, evitando repintados. Se puede hacer mediante CSS o HTML. Es altamente recomendable evitar el uso del rescalado de imágenes, si el tamaño real de la imagen es de 60 x 60 pixels, no debemos rescalarlo en el navegador estableciendo las propiedades del contenedor a 30x30 pixels. La solución correcta es transformar su tamaño antes de ser enviadas.

    • CDNs (Content Delivery Network): Los CDNs permiten obtener los recursos más rápido utilizando una red de servidores. El contenido estático de nuestra aplicaciones (imágenes, código javascript, estilos CSS, ficheros de audio, documentos, etc...) se replican varios nodos geográficamente diferentes de ese CDN para intentar estar más cerca físicamente del cliente que realiza la petición. Es responsabilidad del desarrollador la carga de esos recursos en el servicio de CDN, pero es responsabilidad del CDN el asegurar la distribución, replicación y la alta disponibilidad de dichos recursos.

    • Web Workers: Los Web Workers proveen una forma de ejecutar código JS en hilos en background. El objetivo es realizar tareas intensivas que requieren consumo de CPU sin bloquear el hilo en el que se ejecuta la interfaz del usuario. Es una característica de HTML5.

1.2.2 - Análisis de rendimiento: Profiling

En ingeniería de software, el profiling es el análisis dinámico de un programa, que mide, por ejemplo la complejidad del mismo. Significa medir las características de una aplicación en tiempo de ejecución y realizar un análisis de rendimiento, para su posterior optimización. El profiling se consigue a través de la instrumentalización del código, pero hay que tener en cuenta que dicha instrumentalización conlleva un impacto en el rendimiento en la ejecución del propio programa.

Los profilers son una fuente de conocimiento respecto a dónde, en qué operaciones, y cuántos recursos se consumen durante la ejecución de la aplicación.

Las métricas más interesantes son:

  • Tiempo de Ejecución: El wall time de una llamada a una función es la medida del tiempo real que tarda el lenguaje en completar su ejecución. Existen dos tipos de wall time:

    • El tiempo inclusivo:cuando el tiempo total de ejecución de una función incluye el tiempo inclusivo de todas las llamadas a funciones hijas. Permite determinar el path crítico de la aplicación.

    • El tiempo exclusivo: se refiere al tiempo total de ejecución de una función, excluyendo el tiempo de ejecución de todas las llamadas a funciones hijas. El tiempo exclusivo informa acerca de los nodos que consume el mayor tiempo por ellos mismos, y son generalmente los primeros que debemos optimizar.

El wall time se compone de dos partes:

  • El tiempo de CPU, que es la cantidad de tiempo que la CPU está ocupada procesando las instrucciones que desencadena la llamada a la función.

  • El tiempo de I/O (Input/Output): es el tiempo que la CPU está esperando por operaciones de input/output (red, disco, …)

  • Rendimiento de Persistencia de Datos: Un rendimiento pobre de la Base de Datos es normalmente la causa más importante de los problemas de rendimiento de una aplicación. Muchas de las herramientas de profiling hacen especial hincapié en la medición de las operaciones de persistencia de datos.

  • Consumo de Memoria: A medida que una aplicación va aumentando su complejidad, también es probable que empiecen los errores de falta o asignación de memoria. Una solución es incrementar la cantidad de memoria disponible cambiando la configuración del compilador o del hardware, pero siempre debe ser una mejor opción optimizar el código para intentar que consuma menos memoria.

En el lado del cliente, algunos navegadores web incluyen diversas herramientas para registrar y analizar toda la actividad de tu aplicación mientras se ejecuta. Es el mejor punto de partida para investigar los problemas percibidos sobre el rendimiento de la aplicación en su parte frontend.

En el lado del servidor, la práctica totalidad de los lenguajes incluyen (algunos de forma nativa y otros no) herramientas para analizar el rendimiento del código. Si programas en PHP las herramientas más populares son:

  • El propio PHP: La instrucción microtime es muy útil en muchos escenarios. El mayor inconveniente es que es necesario instrumentalizar de forma explícita el código de la aplicación.

    $start = microtime(true);  
    $this->check();  
    $time = microtime(true) - $start;
  • XDebug: Permite detectar problemas y cuellos de botella en la ejecución de código PHP, así como información sobre el uso de memoria y otras métricas. No es realmente un profiler, en realidad es un debugger con algunas características de profiler. Totalmente desaconsejado su uso en producción, consume muchísimos recursos (el overhead es ~x2 en el tiempo de ejecución).

  • XHProf: Creado por Facebook. XHProf realiza un seguimiento de las llamadas a las funciones y, por consiguiente, de la cadena de eventos durante la ejecución del código. De esta manera podemos llegar a saber el tiempo real transcurrido para ejecutar la aplicación, tiempo de la CPU y la memoria de su aplicación utiliza. Una gran herramienta para determinar los cuellos de botella y lo mejor es que puede ejecutarse en servidores de producción con un bajo coste de recursos (el overhead es de ~10%). No está soportado oficialmente por la versión 7 de PHP.

  • Blackfire Profiler: Es otra herramienta muy popular de profiling en PHP. Inicialmente se basó en otra herramienta llamada XHProf, pero finalmente se reescribió por completo. Realiza medidas inteligentes proporcionando más precisión al análisis. Existe una versión de pago con variedad de funcionalidades muy interesantes. Se puede usar en producción sin apenas overhead. Una alternativa a Blackfire es Tideways, el cual sí está basado en XHProf, de hecho gracias a ellos existe soporte compatible con PHP7.

1.3 - Rendimiento en PHP

PHP es un lenguaje interpretado, quiere decir que su código no necesita ser preprocesado mediante un compilador. Significa que el ordenador es capaz de ejecutar la sucesión de instrucciones dadas por el programador sin necesidad de leer y traducir exhaustivamente todo el código.

Para que esto sea posible hace falta un intermediario, un programa encargado de traducir cada instrucción escrita con una semantica humana a código máquina (instrucciones de la CPU del ordenador), este programa recibe el nombre de interprete (parser). El interprete se encarga de leer una a una las instrucciones textuales del programa conforme estas necesitan ser ejecutadas y descomponerlas en instrucciones del sistema, además se encarga de automatizar algunas de las tareas típicas de un programador como declaraciones de variables o dependencias, de esta manera el proceso de programar se suele agilizar mucho lo cual repercute en la eficiencia del que tiene que escribir el código.

La principal ventaja de un lenguaje interpretado es que es independiente de la máquina y del sistema operativo ya que no contiene instrucciones propias de un procesador sino que contiene llamadas a funciones que el interprete deberá reconocer. Basta que exista un interprete de un lenguaje para dicho sistema y todos los programas escrito en ese lenguaje funcionaran.

Además un lenguaje interpretado permite modificar en tiempo de ejecución el código que se está ejecutando así como añadirle nuevo, algo que resulta idóneo cuando queremos hacer pequeñas modificaciones en una aplicación y no queremos tener que recompilarla toda cada vez.

Durante años PHP creció en popularidad por su fácil implementación, su agilidad y versatilidad para desarrollar todo tipo de proyectos. Algunos, tan importantes como Facebook, Flickr o Wikipedia, lo eligieron por este motivo.

Pero toda esta agilidad de desarrollo viene con una contrapartida: el impacto en el rendimiento es notable cuando las visitas se incrementan. Facebook lo solucionó de una manera ingeniosa; creó una máquina virtual que convertía el código PHP en lenguaje máquina, estableciendo una sinergia entre el código PHP y el hardware que lo ejecutaba, a la que llamó HipHop Virtual Machine (HHVM). Es decir, creó un nuevo intérprete de PHP, que compilaba el código, para luego ejecutarlo como si fuera C++. Esto aumentó el la capacidad de tráfico entre un 500% y un 600% sin tocar el código (o lo que es lo mismo, ahorró 6 veces el coste en servidores).

Y ¿qué tiene que ver esto con PHP 7? La respuesta es que Facebook publicó esta máquina HHVM como código abierto y la comunidad la utilizó para implementar muchas de sus mejoras en el motor interno de PHP 7.

1.4 - Pruebas de rendimiento y estrés

Uno de los análisis que suelen integrar cualquier plan de QA es la prueba de estrés. Esta evaluación pone a prueba la robustez y la confiabilidad del software sometiéndolo a condiciones de uso extremas. Entre estas condiciones se incluyen el envío excesivo de peticiones y la ejecución en condiciones de hardware limitadas. El objetivo es saturar el programa hasta un punto de quiebre donde aparezcan bugs (defectos) potencialmente peligrosos.

Cuando hablamos de aplicaciones web, una posible condición extrema puede ser el acceso de un enorme número de usuarios en poco tiempo. Efectos similares pueden obtenerse con un ataque de denegación de servicio (DDoS) a través de un software malicioso. Los efectos de la saturación pueden ser la pérdida o adulteración de datos, el uso excesivo de recursos incluso una vez finalizada la situación de estrés, un mal funcionamiento de componentes de la aplicación o la aparición de errores inesperados.

Un buen plan de pruebas de stress debe contemplar el desarrollo de no uno, sino varios casos de stress. Cada caso diferirá en el volumen del estímulo a aplicar sobre la aplicación (cantidad de usuarios, cantidad de peticiones, etc.), el tiempo que durará cada estímulo y la duración total del experimento, entre otras variables. Además, deberá contar con una serie de resultados esperados. Todos los casos deben ponerse en práctica, registrándose al término de cada uno estadísticas sobre el uso de CPU, memoria, conexión y otros recursos. Al finalizar, se comparan los resultados obtenidos con los esperados y se obtienen conclusiones sobre el rendimiento de la aplicación. Si se encontraron problemas, es necesario revisar el diseño o el código de la aplicación para descubrir el origen del conflicto.

La importancia de detectar errores a tiempo es tal, que las pruebas de stress suelen realizarse en las primeras instancias del plan de QA, incluso antes de verificar que la aplicación cumpla con los requerimientos solicitados. De esta manera se le entrega al cliente un software que puede no ser el definitivo, pero sí goza de la robustez adecuada para su uso diario.

Las pruebas de estrés se pueden utilizar para:

  • Detectar cuellos de botella simulando gran tráfico

  • Obtener información crítica sobre la velocidad de procesamiento de la aplicación

  • Medir la potencia de la combinación de hardware-software

  • Obtener métricas como por ejemplo: - Tiempo por petición (velocidad)

  • Cantidad de bytes/kb transferidos (carga/optimización)

La metodología usual para realizar pruebas de rendimiento es la siguiente:

  • Identificar el entorno de pruebas. Identificar el entorno físico de pruebas y el entorno de producción, así como las herramientas y recursos de que dispone el equipo de prueba.

  • Identificar los criterios de aceptación de rendimiento. Determinar el tiempo de respuesta, el rendimiento, la utilización de los recursos y los objetivos y limitaciones. En general, el tiempo de respuesta concierne al usuario, el rendimiento al negocio, y la utilización de los recursos al sistema. Además, identificar criterios de éxito del proyecto que no hayan sido recogidos por los objetivos y limitaciones, por ejemplo, mediante pruebas de rendimiento para evaluar qué combinación de la configuración da lugar a un funcionamiento óptimo.

  • Planificar y diseñar las pruebas. Identificar los principales escenarios, determinar la variabilidad de los usuarios y la forma de simular esa variabilidad, definir los datos de las pruebas, y establecer las métricas a recoger. Consolidar esta información en uno o más modelos de uso del sistema a implantar, ejecutarlo y analizarlo.

  • Configurar el entorno de prueba. Preparar el entorno de prueba, herramientas y recursos necesarios para ejecutar cada una de las estrategias, así como las características y componentes disponibles para la prueba. Asegurarse de que el entorno de prueba se ha preparado para la monitorización de los recursos según sea necesario.

  • Aplicar el diseño de la prueba. Desarrollar las pruebas de rendimiento de acuerdo con el diseño del plan.

  • Ejecutar la prueba. Ejecutar y monitorizar las pruebas. Validar las pruebas, los datos de las pruebas, y recoger los resultados. Ejecutar pruebas válidas para analizar, mientras se monitoriza la prueba y su entorno.

  • Analizar los resultados, realizar un informe y repetir el proceso. Consolidar y compartir los resultados de la prueba. Analizar los datos, tanto individualmente, como con un equipo multidisciplinario. Volver a priorizar el resto de las pruebas y volver a ejecutarlas de ser necesario. Cuando todas las métricas estén dentro de los límites aceptados, ninguno de los umbrales establecidos han sido rebasados, y toda la información deseada se ha reunido, las pruebas han acabado para el escenario definido por la configuración.

2.- Caché

2.1 Conceptos básicos

La caché es un mecanismo que permite acelerar el acceso a la información. En lugar de leer los datos directamente de su origen, que podría ser una base de datos, ficheros o cualquier otro sistema remoto, se leen desde una fuente que proporciona acceso más rápido.

La información cacheada se guarda fundamentalmente en memoria o en disco. El acceso a memoria es más rápido, pero a la vez más volátil (la información no suele sobrevivir a un reinicio de la máquina que la alberga).

El cacheo de datos puede ocurrir a diferentes niveles. En una aplicación web moderna se puede cachear información en el navegador, el servidor, y/o en la Base de Datos. Es muy frecuente que algunas operaciones de Base de Datos se guarden en memoria para optimizar las lecturas. El servidor web también cacheará archivos estáticos en memoria como CSS y Javascript o de configuración (como hace Composer con el autoload, por ejemplo). Y finalmente, el navegador cacheará también archivos estáticos. Con HTML5 los navegadores tiene la capacidad de almacenar información de la propia aplicación en el componente local storage (una especie de Base de Datos (key/value) en el lado del cliente), utilizar el estándar web SQL database o emplear frameworks completos como los Service Workers.

Los beneficios de usar sistemas de caché son:

  • Decrementar el coste de transmisión de información a través de la red: o lo que es lo mismo, el consumo de ancho de banda o de señal de nuestra conexión a internet. El contenido puede ser cacheado en diferentes lugares. Aquellos lugares que estén más cerca del cliente no producirán una carga adicional en la red de comunicaciones, y por tanto, ahorrarán caudal en la transferencia de datos.

  • Mejora en los tiempos de respuesta: Habilita el acceso al contenido de manera más rápida. Por ejemplo, la caché que mantiene el navegador web puede obtener la información requerida instantáneamente.

  • Mejora el rendimiento sin mejorar el hardware: Desde la perspectiva del servidor, el rendimiento puede ser mejorado si éste recibe menos peticiones debido a que el contenido se ha cacheado en algún otro lugar. El propietario del contenido puede delegar la carga de acceso a la información en otras máquinas, incluida la del propio cliente.

  • Disponibilidad del contenido incluso en interrupciones de la red: En algunos casos, mantener la información cacheada en el cliente puede beneficiar un posible uso de la aplicación cuando se detiene el acceso a la red. Por ejemplo, la especificación de las PWA (Progressive Web Applications) requiere que la aplicación web sea usable (no en todos los contextos será posible) incluso si ésta se ejecuta de forma offline.

Algunos términos comunes cuando hablamos de caché son:

  • Servidor origen: se trata de la fuente original de los datos. Es el responsable de servir el contenido que no puede ser accesible directamente desde el sistema de caché, así como de establecer la política de la propia caché (invalidación, llenado, etc…)

  • Cache hit ratio: La efectividad de la caché se mide fundamentalmente a través del término hit rate o hit ratio. Se trata de la proporción de datos que se pueden obtener directamente de la caché frente al conjunto total de información requerida.

  • Frescura de la información: Responde a la pregunta de cuándo un elemento almacenado en la caché es todavía válido para ser servido al cliente. El contenido de la caché es válido dependiendo de la política de caché. Por ejemplo, el post de un blog puede ser todavía fresco aunque haya pasado una semana desde su almacenamiento en la caché, por el contrario un contador de visitas se mantendrá fresco durante muy poco tiempo.

  • Validación: Algunos sistemas de caché son capaces de auto-validarse contra la fuente original para comprobar si todavía siguen siendo los candidatos óptimos para ser servidos al cliente, es decir, si todavía tienen la versión más reciente del dato almacenado.

  • Invalidación: La invalidación de la caché es el término utilizado para describir el proceso de desalojar algunos datos para dejar espacio a otros, o simplemente porque ya no poseen la frescura requerida. Normalmente, después de ser invalidado, la petición subsiguiente debe acceder a la fuente original y aprovechar ese viaje para almacenar la información obtenida en la caché.

Al usar mecanismos de caché es necesario tener en cuenta:

  • Cómo se llena la caché.
  • Cómo mantener la caché y el sistema remoto sincronizados.
  • Cómo gestionar el tamaño de la caché.

2.2 Llenar la Caché

El primer desafío al cachear es llenar la caché con datos de la fuente original. Hay básicamente dos técnicas para conseguirlo:

  • Upfront Population: Llenar la caché cuando el sistema se inicializa, antes de que se reciba ninguna petición asociada a esa información. Muchas veces esta técnica se conoce como cache warmup. Requiere saber de antemano los datos que será necesario almacenar en la caché, y no siempre es posible saberlo.

    La ventaja principal es que, con este método, no existirá retardo entre la información requerida y el camino hasta su fuente original durante las primeras peticiones. En cambio, quizá se está llenando la caché con información que nunca se utiliza, y además la inicialización de un sistema com upfront population suele ser significativamente más lenta.

  • Lazy Evaluation: Significa que se llena la caché a medida que la información va siendo requerida. El flujo es el siguiente: se comprueba en la caché si existe la información requerida, si es así se devuelve usando la propia caché. En caso contrario se va a buscar a su fuente original, se cachean los datos obtenidos y se envían al cliente. Así, en la siguiente petición, esa información ya estará en la caché.

    Las ventajas principales son que no existe el overhead durante el startup time del sistema en cuestión (no se llena la caché previamente), y además, la caché solo contendrá información que realmente se ha requerido. En cuanto a desventaja, al tener que seguir el flujo arriba indicado, los usuarios pueden tener una experiencia diferente dependiendo de si la información está en caché o no (para los usuarios que requieran datos que todavía no están almacenados en caché sera peor)

Es posible combinar estas técnicas. Quizá es buena idea pre llenar la caché con los datos más requeridos estadísticamente, y dejar como lazy las peticiones posteriores.

2.3 Mantener la Caché y el Sistema Remoto sincronizados

Otro desafío significativamente importante es mantener una sincronía entre los datos cacheados y la fuente de datos original, es decir, que la información contenida en los dos almacenes sea la misma, o al menos, lo más parecida posible. Dependiendo de la arquitectura del sistema, existen diferentes formas de mantener esta sincronía. Algunas de las técnicas más utilizadas son:

  • Write-through Caching: es un tipo de caché que permite tanto leer como escribir. Consiste en que los datos modificados se registren primero en la caché y posteriormente en la fuente original. Esto funciona si el sistema remoto solo puede ser modificado a través del sistema de caché. Si todas las escrituras pasan por el sistema de caché, entonces sería fácil reproducir dichas modificaciones.

  • Time Based Expiry: Si el sistema remoto puede ser actualizado independientemente del sistema de caché, mantener la sincronía se convierte en algo más problemático. Una forma de solucionarlo es dejar que los datos almacenados en la caché se invaliden al transcurrir un intervalo de tiempo. Cuando los datos vuelven a ser requeridos, el sistema los volverá a cargar y quizá esa nueva versión contenga información actualizada sobre lo que se está consultando.

    El tiempo de expiración de un dato de la caché dependerá de las necesidades de cada escenario. Habrá datos que no sea necesario invalidar de la caché prácticamente nunca (como el post de un blog) ya que su contenido no varía, o que si se produce un cambio no es grave que todos los usuarios tengan esa modificación en tiempo real. O bien, existen datos que se actualizan tan frecuentemente que es necesario quitar de la cache cada pocos minutos.

    La consecuencia es que un tiempo de expiración corto requerirá más accesos a la fuente original y escrituras correspondientes en la caché con lo que las consultas serán más lentas, pero en cambio un tiempo de expiración elevado puede llevar a problemas de sincronización con la fuente original.

  • Active Expiry: La expiración activa se refiere a la capacidad de invalidar un dato de la caché cuando el sistema ha detectado que ese dato ha cambiado. La ventaja principal es que la caché siempre estará en sincronía con la fuente original, además no habrá expiraciones de datos fortuitas o basadas en el tiempo que en muchos casos son innecesarias.

    La principal desventaja es la complejidad que dicha técnica significa para la aplicación, que tiene que detectar los cambios en los datos (sea cual sea la vía utilizada para modificarlos) e informar a la caché de que algo ha cambiado y el qué.

2.4 Gestionando el tamaño de la Caché

En la mayoría de sistemas, se presenta un escenario en el que es inviable mantener una caché con la totalidad de los datos de la aplicación. Se necesitan mecanismos para gestionar la cantidad de forma lo más óptima posible la cantidad de información que residirá en la caché. Gestionar el tamaño de la caché es básicamente desalojar información, y hacer espacio para nuevos datos. Algunas técnicas son:

  • Desalojo basado en el tiempo: Es similar al concepto de tiempo de expiración basado en el tiempo, pero haciendo énfasis en la cantidad de memoria utilizada.

  • El primero que entra, es el primero en salir (FIFO): Significa que cuando se intenta almacenar un nuevo dato en la caché se desaloja otro dato que ha sido recientemente añadido para dejar espacio al nuevo. No se elimina ningún dato de la caché hasta que esta esté completamente llena.

  • El primero que entra es el último en salir (LIFO): Es lo opuesto a FIFO. Es útil cuando los primeros datos que se insertan en la caché son usualmente lo más accedidos.

  • Desalojar los menos accedidos: Los datos que menos accesos tengan en un rango de tiempo se desalojan. La motivación principal es evitar tener que leer de la fuente original datos que en realidad se requieren mucho. Para poder implementar este mecanismo, la caché debe llegar un contador con el número de accesos de cada elemento.

    Algo a tener en cuenta respecto a esta técnica es que el rango de tiempo fijo que establezcamos puede provocar que los datos más viejos se conviertan automáticamente en los más accedidos aunque se hayan dejado de requerir recientemente.

  • Desalojar en base al tiempo entre accesos: Este mecanismo resuelve el problema anterior. Se calcula el tiempo medio entre dos accesos al mismo dato y además se incrementa el contador de accesos. Aunque el elemento sea viejo y tenga muchos accesos acumulados, si el tiempo medio ha aumentado mucho recientemente quiere decir que ese dato ha perdido “popularidad” y podemos desalojarlo.

Es importante recordar, que aunque se utilicen algunas de estas técnicas de gestión del tamaño de la caché, sigue siendo primordial en muchos casos que la información esté siempre sincronizada con la fuente original. Aunque un dato sea accedido muchas veces, ese dato merece estar en sincronía con las modificaciones que se produzcan del mismo.

2.5 Caché distribuidas vs Data Grid vs NoSQL

El término caché distribuida se refiere a la habilidad, dentro de un sistema distribuido, de acceder a la información cacheada desde varios nodos.

Generalmente, el cacheo se produce en la memoria de dichas máquinas. La información es distribuida utilizando algún mecanismo de particionado y replicado. La información se replica en un sistema distribuido cuando alguno de los nodos, de forma proactiva, envía ese dato a otro nodo para que también lo almacene. En cambio, la información se particiona cuando un nodo solamente guarda parte de la información que se pretende cachear, y los demás nodos se reparten el resto. Estas dos aproximaciones son totalmente compatibles. Cuando se invalida algún dato de la caché, normalmente todos los nodos deben de enterarse para desalojar su copia local en caso de que la tengan.

Los sistemas de caché distribuidos contienen información a la que se accede a través de claves primarias, aunque algunas pueden ser consultadas usando otros criterios de búsqueda.

Este concepto de caché distribuida es muy cercano al concepto de Data Grid. Un data grid es un sistema compuesto por múltiples nodos o servidores que trabajan de forma colaborativa para guardar información, pero también para procesarla. Un data grid en memoria (IMDG o In-Memory Data Grid) guarda la información en la memoria de esas máquinas para conseguir un mejor rendimiento. Guarda copias de la información en cada uno de los servidores asegurando también la alta disponibilidad de los datos.

La ventaja principal de un data grid es que ofrece la posibilidad de realizar queries a los datos como de un RDBMS se tratase, de hecho se realiza una especie de volcado de la estructura definida en el RDBMS en la memoria, que además se va distribuyendo entre los nodos existentes, siendo muy fácil de escalar horizontalmente.

Otra característica importante es que los data grids permiten el desarrollo de una arquitectura orientada a eventos, incluso para la gestión de los datos. Esto quiere decir que además de las características RDBS se añade la comunicación mediante eventos entre los nodos del sistema propagando las modificaciones de los propios datos. Los clientes de esa información también pueden ser notificados sobre las modificaciones escuchando los dichos eventos.Otra característica importante es que pueden ofrecer atomicidad y transacciones (bajo ciertas restricciones).

Muchas de las características de los in-memory data grids coinciden con las características de las bases de datos no relacionales modernas (NoSQL). Las bases de datos NoSQL son sistemas de almacenamiento de información que no cumplen con el esquema entidad-relación, no imponen una estructura de datos en forma de tablas y relaciones entre ellas , en ese sentido son más flexibles, ya que suelen permitir almacenar información en otros formatos como clave-valor (similar a tablas Hash), Mapeo de Columnas, Documentos o Grafos. La práctica totalidad ofrecen el particionado de datos en varios nodos a partir de una clave de partición y mecanismos de replicación y distribución. Además, ofrecen avanzados mecanismos de querying (más allá de requerir datos mediante la clave primaria). Existen diferencias sutiles entre ambas aproximaciones, una de las más destacables es que los data grids están pensados para ofrecer una latencia mínima al utilizar principalmente la memoria para almacenar la mayor parte de la información, mientras que las soluciones NoSQL utilizan almacenamiento en disco ofreciendo rápido y fácil escalado para soportar un aumento de peticiones. En este último caso, prima la necesidad de responder a más y más peticiones en lugar de optimizar la velocidad de respuesta.

2.6 Proxy Inverso

Un proxy inverso es un tipo de servidor proxy que recupera recursos en nombre de un cliente desde uno o más servidores. Estos recursos son entonces regresados al cliente como si se originaran en el propio servidor Web.

Un proxy inverso puede reducir carga de sus servidores de origen mediante el uso de caché web de contenido estático, así como contenido dinámico. Los cachés de proxy de este tipo a menudo pueden satisfacer un número considerable de peticiones de sitios web, reduciendo enormemente la carga en el servidor de origen. Además, puede optimizar el contenido comprimiéndolo para acelerar los tiempos de carga.

Los dos proxys inversos más populares son:

  • Nginx: es un servidor web open-source que además puede ser utilizado como proxy inverso. Nginx puede servir contenido estático directamente de una forma muy eficiente y puede actuar como caché colocado como frontal.

  • Varnish: Al contrario que Nginx, Varnish no puede ser utilizado como servidor web. Su único cometido es de ser utilizado para acelerar la entrega de contenido estático almacenado.

    Requiere un servidor dedicado y su comportamiento se especifica mediante su propio lenguaje (VCL o Varnish Configuration Language), el cual permite dotar a este sistema de una gran versatilidad. VCL permite determinar las políticas a tomar sobre las peticiones de entrada. En esta política se puede decidir qué contenido desea servir, desde donde se desea obtener el contenido y la forma en que la solicitud o la respuesta debe ser alterada. Esto hace a Varnish Cache mucho más configurable y flexible que otros aceleradores HTTP.

    Se instala delante de cualquier servidor HTTP y se configura para almacenar en el caché del servidor una copia del recurso solicitado. Está ideado para aumentar el rendimiento de aplicaciones web con contenidos pesados y APIs altamente consumidas.

Tanto Varnish como Nginx pueden ser utilizados y configurados también como balanceadores de carga: Un Balanceador de Carga (Load Balancer) fundamentalmente es un dispositivo de hardware o software que se pone al frente de un conjunto de servidores que atienden una aplicación y, tal como su nombre lo indica, asigna o balancea las solicitudes que llegan de los clientes a los servidores usando algún algoritmo (desde un simple round-robin hasta algoritmos más sofisticados).

2.7 Web Caching

Se llama caché web a la caché que almacena documentos web (es decir, páginas, imágenes, etcétera) para reducir el ancho de banda consumido, la carga de los servidores y el retardo en la descarga. Un caché web almacena copias de los documentos que pasan por él, de forma que subsiguientes peticiones pueden ser respondidas por el propio caché, si se cumplen ciertas condiciones. En todos los navegadores se incluye una implementación de un caché HTTP.

La mayoría de las políticas de caché las configura el propietario de la información. Estas políticas se articulan a través de cabeceras HTTP. Tras varias evoluciones y cambios en el protocolo, las cabeceras más importantes en relación a la caché son:

  • Expires: La cabecera Expires es muy sencilla, aunque limitada. Establece un tiempo en el futuro en el que la información caducará. En este punto, cualquier petición que requiera el mismo contenido tendrá que ir a buscarlo al servidor origen.

  • Cache-Control: Es el reemplazo moderno de Expires con un diseño más flexible, no obstante es compatible con la cabecera anterior. Se pueden especificar varias opciones que afectan a esta cabecera, separados por comas. Algunas de esas opciones (también llamados flags) son:

  • no-cache: Este flag indica que ningún contenido se cacheará para el recurso concreto solicitado.

  • no-store: Prohíbe que la información solicitada se almacene en la caché o en ningún tipo de mecanismo de persistencia. Normalmente esto se aplica a información sensible.

  • public: Marca el contenido como público, esto quiere decir que la información puede ser cacheada tanto en el cliente como en cualquier nodo intermediario en la comunicación. Para las conexiones que utilizan autenticación HTTP, este flag será private por defecto, cuya aplicación impide que los nodos intermediarios puedan cachear el contenido, tan solo el cliente final puede hacerlo.

  • max-age: Establece el tiempo máximo que un recurso puede ser cacheado antes de tener que ser revalidado contra el servidor origen. Es el reemplazo de Expires para navegadores modernos. Se especifica en segundos y tiene como valor máximo un año.

  • s-maxage: Muy similar a max-age con la diferencia de que se aplica también a cachés intermedias situadas en otros nodos que participan en la comunicación.

  • must-revalidate: Dicta que en cualquier caso (sobrescribiendo otros flags como max-age o Expires) el recurso siempre ha de ser revalidado, es decir, contenido viejo nunca debe ser servido, incluso cuando se corta la conexión.

  • proxy-revalidate: También similar al anterior, pero solo aplica a las cachés situadas en los nodos intermediarios. Aquí sí se puede utilizar para servir información cuando se producen interrupciones en la conexión entre los proxies y el servidor origen.

  • no-transform: Indica que el contenido no puede ser modificado bajo ninguna circunstancia por razones de performance u otra razón. En algunos casos la caché del navegador puede comprimir la información para consumir menos espacio de almacenamiento, con esta cabecera, si la información no ha llegado comprimida desde el servidor origen, no se podrá comprimir en el cliente.

  • Etag: Su objetivo es la validación de caché. El servidor origen puede proporcionar un Etag único asociado al contenido. Cuando el cliente quiere comprobar la validez de la caché ha de enviar una petición al servidor de origen y este responderá indicando que la caché es todavía válida, o bien responderá con la información fresca y un nuevo Etag en caso contrario.

  • Last-Modified: Especifica la última vez que un dato o recurso ha cambiado. Es usado como parte de la estrategia de validación de caché.

  • Content-Length: No está directamente relacionado con el manejo de caché, pero es importante para definir políticas de caché. Algunos clientes rechazarán realizar caché de contenido si no conozcan de antemano el tamaño real del espacio que necesitan reservar para tal fin.

  • Vary: El rol de la cabecera Vary es indicar en qué casos se debería mostrar una versión diferente de tu sitio. Si tu cabecera Vary tiene uno de los siguientes valores: User-Agent, Cookie, Referrer o incluso * (wildcard) puede reducir de forma significativa el efecto de tu sistema de caché. Esto ocurre porque estos valores indican que puede mostrarse una versión diferente de tus páginas según el tipo de navegador que uses (user-agent), la presencia de una cookie única o una URL referida o, en el peor de los casos, cuando * se usa, esto significa que a cada una de las visitas se les mostrará un contenido reciente, lo que deshabilitará totalmente la caché.

    Otro de los valores de Vary disponibles es Accept-Encoding, utilizado para servir correctamente contenido comprimido. Accept-Encoding tendrá como valor los formatos de compresión que admite ese navegador. Actualmente todos los navegadores modernos aceptan compresión con GZIP y Deflate. El servidor que recibe esa cabecera, podrá entonces enviar los archivos solicitados al cliente en formato comprimido, con la certeza de que el cliente los podrá procesar perfectamente. Sin embargo, dependiendo de cómo se configure el servidor, los archivos se podrán enviar comprimidos o no.

Fingerprinting

Todas las solicitudes HTTP que realiza el navegador primero se redireccionan al caché para comprobar si hay una respuesta válida almacenada en caché que pueda usarse para responder a la solicitud. Si hay una coincidencia, se lee la respuesta desde la caché y, de esta manera, se eliminan la latencia de la red y los costos por datos de la transferencia.

No obstante, ¿qué ocurre si deseamos actualizar o invalidar una respuesta almacenada en caché? Por ejemplo, supongamos que se ha pedido a los visitantes almacenar en caché una hoja de estilo CSS durante hasta 24 horas (max-age=86400), pero el diseñador acaba de agregar una actualización que deseas poner a disposición de todos los usuarios ¿Cómo indicas a todos los visitantes, a través de lo que ahora es una copia “caduca” almacenada en caché de tu CSS, que actualicen sus cachés?.

Para solucionar este problema basta cambiar la URL del recurso y hacer que el usuario deba descargar la nueva respuesta cada vez que cambie su contenido. Generalmente, esto se logra incorporando una huella digital del archivo, o un número de versión, en su nombre de archivo; por ejemplo, style.x234dff.css. Esta técnica se conoce como fingerprinting.

Consejos y sugerencias

Sugerencias y técnicas en la estrategia de almacenamiento en caché:

  • URLs consistentes: si se proporciona el mismo contenido en diferentes URL, ese contenido se obtendrá y se almacenará varias veces.

  • El servidor proporciona un token de validación (ETag): los tokens de validación eliminan la necesidad de transferir los mismos bytes cuando un recurso no sufre cambios en el servidor.

  • Identificar los recursos que pueden almacenarse en caché a través de intermediarios: aquellos cuyas respuestas son idénticas para todos los usuarios son excelentes candidatos para almacenarse en caché a través de una CDN y otros intermediarios.

  • Determina la mejor jerarquía de caché: la combinación de URLs de recursos con huellas digitales (fingerprint) de contenido y las vidas útiles breves o sin almacenamiento en caché (no-cache) para documentos HTML permiten controlar la rapidez con la cual el cliente recibe las actualizaciones.

  • Minimizar la migración de clientes: algunos recursos se actualizan con más frecuencia que otros. Si una parte específica del recurso (por ejemplo, una función JavaScript o un conjunto de estilos CSS) se actualiza con frecuencia, considera proporcionar ese código como un archivo independiente. Esto permitirá que el resto del contenido (por ejemplo, códigos de biblioteca que no cambien con mucha frecuencia) se obtenga de la caché y minimiza la cantidad de contenido descargado cada vez que se obtiene una actualización.

2.8 Service Workers y Caché

Están llegando a la Web experiencias sin conexión, sincronizaciones periódicas en segundo plano y notificaciones push: funcionalidad que normalmente requiere una app nativa. Los Service Workers brindan la base técnica en la que descansan todas estas funciones y, en definitiva, son los facilitadores de muchas de las asombrosas características de las Progressive Web Apps (PWA).

Un Service Worker es una secuencia de comandos que tu navegador ejecuta en segundo plano, separado de una página web, abriéndoles la puerta a funciones que no necesitan una página web ni interacción de usuario. La función principal es la capacidad de interceptar y manejar solicitudes de red, incluida la administración programática de un caché de respuestas.

Al utilizar un Service Worker, se puede configurar fácilmente una aplicación para que utilice primero los recursos en caché antes de obtener alguna información de la red (Comúnmente conocido como offline first) lo que proporciona la experiencia de uso predeterminada incluso cuando se está offline. Este paradigma se usa en aplicaciones nativas, y es una de las principales razones por las que a menudo éstas se eligen por encima de las aplicaciones web.

El API de un Service Worker incluye una interfaz de caché, que permite crear un almacén de respuestas representadas por peticiones (las peticiones actúan como claves en este almacén). El desarrollador es el responsable de gestionar esta caché. Cualquier actualización en los elementos contenidos debe realizarse de forma explícita. Los elementos nunca caducarán, es necesario borrarlos, sin embargo si la cantidad de información almacenada en esta caché excede el límite del navegador se empezará a desalojar de forma automática.

Para servir contenido desde la caché es necesario interceptar las peticiones http y responder con los archivos almacenados. Existen diferentes aproximaciones para resolver esto:

  • Sólo caché: Es adecuado cuando se sirve contenido estático que es parte del núcleo de la aplicación. Se pueden cachear estos archivos justo cuando se instala el Service Worker. Si se requiere un archivo que no está en la caché, la respuesta será un error de conexión.

  • Sólo red: Se utiliza para cosas que no se pueden obtener offline, como peticiones http diferentes a GET. Normalmente, no se gestiona este caso de forma explícita, ya que es el comportamiento por defecto.

  • Caché y red como fallback: Si el objetivo es realizar una aplicación offline first, entonces este es el método adecuado. Se intentarán interceptar las peticiones y servir el contenido desde la caché, y si no es posible se enviarán a la red.

  • Red y caché como fallback: Es un buen método para recursos que se actualizan con mucha frecuencia y que no son parte de la “versión” de la aplicación (por ejemplo artículos, avatares, listas...). Significa que los usuarios offline obtendrán un contenido menos actualizado que los usuarios online. Uno de los problemas de este método es que para que la caché entre en acción, la conexión se ha de cortar en algún momento, es por ello que si simplemente la conexión es lenta, puede conllevar a una experiencia de usuario mediocre.

  • Primero caché y después red: También se utiliza para información que se actualiza mucho. Se intentará mostrar el contenido desde la caché y a su vez pedir a la red una actualización del mismo, una vez llegué, se sustituirá por el contenido que se ha mostrado desde la caché. Esto requiere a la página realizar dos peticiones, una a la caché y otra a la red. Hay que tener mucho cuidado con la actualización de la información con la que el usuario está interactuando.

3.- Paralelismo y Concurrencia

3.1 Introducción

La concurrencia es la capacidad del CPU para ejecutar más de un proceso al mismo tiempo. Un procesador puede ejecutar al mismo tiempo un número de procesos determinado por el número de cores que tiene, de esta forma, si un procesador tiene un core, entonces solo podrá ejecutar un proceso a la vez, por otro parte, si tenemos ocho cores, entonces podremos ejecutar hasta ocho procesos al mismo tiempo. Los procesos en ejecución no tienen por qué estar relacionados, es decir, cualquiera puede iniciar y terminar en el momento que sea, y el resultado de uno no afecta al otro.

El paralelismo sigue la filosofía de “divide y vencerás”, ya que consiste en tomar un único problema, y mediante concurrencia llegar a una solución más rápido. El paralelismo lo que hace es tomar el problema inicial, dividir el problema en fracciones más pequeñas, y luego cada fracción es procesada de forma concurrente, aprovechando al máximo la capacidad del procesador para resolver el problema. La principal diferencia del paralelismo contra la concurrencia es que, en el paralelismo, todos los procesos concurrentes están íntimamente relacionados a resolver el mismo problema, de tal forma que el resultado de los demás procesos afecta al resultado final. En el paralelismo debe de haber un paso final que se encargue de unir los resultados de todos los procesos para poder arrojar un resultado final.

Por ejemplo, una aplicación de descarga de música, en la cual puedes descargar un número determinado de canciones al mismo tiempo, cada canción es independiente de la otra, por lo que la velocidad y el tiempo que tarda en descargarse cada una no afectará al resto de canciones. Esto lo podemos ver como un proceso concurrente, ya que cada descarga es un proceso totalmente independiente del resto.

En el caso del paralelismo podemos imaginar la clásica página de viajes, donde nos ayudan a buscar el vuelo más barato o las mejores promociones, para hacer esto, la página debe de buscar al momento en cada aerolínea el vuelo más barato, con menos conexiones, etc. Para esto puedo hacerlo de dos formas, buscar de forma secuencial en cada aerolínea las mejores promociones o utilizar el paralelismo para buscar al mismo tiempo las mejores promociones en todas las aerolíneas. Una vez que los cuatro procesos terminan, hay un subproceso adicional encargado de unir los resultados y arrojar un resultado final.

El uso de múltiples hilos dentro de un mismo programa es uno de los enfoques más comunes cuando se plantea resolver un problema de concurrencia. En las mayor parte de los sistemas modernos los hilos creados por la aplicación son conocidos y gestionados por el núcleo (modelo uno a uno) lo que les permite acceder simultáneamente al mismo y ser planificados en diferentes procesadores, pudiendo así aprovechar el paralelismo ofrecido por el hardware en los sistemas multiprocesador y/o multinúcleo.

Las principales características de la programación multihilo son:

  • Es muy sencilla, ya que permite gestionar diferentes hilos de ejecución dentro del espacio de direcciones virtual de un mismo proceso, lo que facilita la compartición de datos y otros recursos comunes.

  • Se puede volver muy compleja, debido al cuidadoso control que es necesario hacer sobre el acceso a los recursos compartidos, a poco que el problema crezca.

En los sistemas operativos que implementan una llamada al sistema tipo fork se pueden crear de forma sencilla múltiples procesos, en lugar de hilos, para manejar la concurrencia. La forma de hacerlo no difiere mucho de la programación multihilo, aunque es necesario tener en cuenta los siguientes aspectos:

  • Escala peor, ya que soportar cientos de clientes implica la creación de cientos de procesos y estos son más costosos que los hilos, en lo que respecta al consumo de recursos del sistema.

  • No es tan sencilla como la programación multihilo, porque hay que indicar explícitamente que regiones de la memoria se desea compartir entre procesos. Además, normalmente sólo se puede establecer que recursos van a ser compartidos durante la creación de cada proceso, no pudiendo compartir otros recursos posteriormente.

  • Los procesos ofrecen mejor aislamiento que los hilos y por tanto mayor robustez y seguridad.

Respecto a esto último debemos de tener en cuenta que un acceso indebido a la memoria en uno de los procesos, quizás ocasionada por una petición de un cliente mal formateada, no tiene por qué provocar la caída de toda la aplicación; a diferencia de lo que pasaría en una aplicación multihilo. De igual manera, si un atacante tomara el control de uno de los procesos tendría más difícil tener acceso a los otros. Además, los procesos tienen características interesantes de las que carecen los hilos, como la posibilidad de ejecutarse como un usuario concreto del sistema o en un jaula "chroot".

3.2 Colas de Mensajes

Dentro de un proyecto en ocasiones hay que integrarse con otros actores, componentes o sistemas internos y externos, surgiendo la necesidad de aportar o recibir información de ellos. En la mayoría de los casos, estas comunicaciones tienen que estar permanente disponibles, ser rápidas, seguras, asíncronas y fiables entre otros requisitos.

Las colas de mensajes solucionan estas necesidades, actuando como un middleware entre emisores y destinatarios, o en un contexto más definido, productores y consumidores de mensajes.

Aportan a su vez más beneficios:

  • Garantía de entrega y orden: los mensajes se consumen, en el mismo orden que se llegaron a la cola, y son consumidos una única vez.

  • Redundancia: Las colas persisten los mensajes hasta que son procesados por completo.

  • Desacoplamiento: siendo capas intermedias de comunicación entre procesos, aportan la flexibilidad en la definición de arquitectura de cada uno de ellos de manera separada, siempre que se mantenga una interfaz común.

  • Escalabilidad: con más unidades de procesamiento, las colas balancean su respectiva carga.

Las colas de mensajes permiten a diferentes partes de un sistema comunicarse y procesar las operaciones de forma asíncrona. Una cola de mensajes ofrece un buffer ligero que almacena temporalmente los mensajes, y puntos de enlace que permiten a los componentes de software conectarse a la cola para enviar y recibir mensajes.

Los mensajes suelen ser pequeños y pueden ser cosas como solicitudes, respuestas, mensajes de error o, sencillamente, información. Para enviar un mensaje, un componente llamado productor añade un mensaje a la cola. El mensaje se almacena en la cola hasta que otro componente, llamado consumidor, lo recupera y hace algo con él. Muchos productores y consumidores pueden utilizar la cola, pero solo un consumidor procesa cada mensaje una sola vez. Por este motivo, este patrón de mensajería suele llamarse comunicación de uno a uno, o de punto a punto.

Se pueden usar para reducir las cargas y los tiempos de entrega por parte de los servidores de aplicaciones web, ya que las tareas, que normalmente tardarían bastante tiempo en procesarse, se pueden delegar a un tercero cuyo único trabajo es realizarlas.

RabbitMQ es un software de mensajería. Los principales elementos que conforman un broker de RabbitMQ, son las colas, los exchanges y los enlaces (bindings) que se establecen entre ambos. Los mensajes, al llegar al sistema, son recibidos por los exchanges, que son entidades AMQP (Advanced Message Queueing Protocol) desde donde se enrutan los mensajes hacia las colas. Rabbit tiene 4 tipos de exchange predefinidos:

  • Direct: Redirigen los mensajes sólo a aquellas colas a las que coincida el routing key de mismo, con el establecido en el binding a la cola.

  • Fanout: Redirige los mensajes recibidos a todas las colas enlazadas al mismo.

  • Topic: Redirige aquellos mensajes cuyo routing key coincida con el patrón de enlace (por ejemplo, la primera o la última palabra del routing key).

  • Headers: Enrutan los mensajes en función de la cabecera del mensaje, la cual no tiene por qué ser un string, y puede representar, por ejemplo, un número entero.

Las colas son las entidades sobre las que el broker almacena los mensajes que recibe de los productores, desde donde los consumidores obtienen dichos mensajes. A la hora de configurarlas, admiten multitud de parámetros (nombre, persistencia, TTL, exchange de reenvio por error/expiración del mensaje (Conocido como DLX), capacidad de la cola, etc.). Eso sí, una vez generadas no se pueden modificar, por lo que variar sus parámetros implica volver a crearlas.

Los mensajes no se publican directamente en una cola, en lugar de eso, el productor envía mensajes a un exchange. Los exchanges son agentes de enrutamiento de mensajes, definidos por virtual host dentro de RabbitMQ. Un exchange es responsable del enrutamiento de los mensajes a las diferentes colas: acepta mensajes del productor y los dirige a colas de mensajes con ayuda de atributos de cabeceras, bindings y routing keys.

  • Un binding es un “enlace” que se configura para vincular una cola a un exchange

  • La routing key es un atributo del mensaje. El exchange podría usar esta clave para decidir cómo enrutar el mensaje a las colas (según el tipo de exchange)

Los exchanges, las conexiones y las colas pueden configurarse con parámetros tales como durable, temporary, y auto delete en el momento de su creación. Los exchanges declarados como durable sobrevivirán a los reinicios del servidor y durarán hasta que se eliminen explícitamente. Aquellos de tipo temporary existen hasta que RabbitMQ se cierre. Por último, los exchanges configurados como auto delete se eliminan una vez que el último objeto vinculado se ha liberado del exchange.

Por otro lado, y más sofisticado que RabbitMQ, está Kafka. Es un sistema de mensajería basado en el modelo publicador/suscriptor, persistente, escalable, replicado, tolerante a fallos, capaz de atender cientos de megas por segundo de lecturas y escrituras provenientes de miles de clientes.

Sus componentes principales son:

  • Topic: Categorías en las que clasificar los mensajes enviados a Kafka.

  • Producer: Clientes conectados a Kafka responsables de publicar los mensajes. Estos mensajes son publicados sobre uno o varios topics.

  • Consumer: Clientes conectados a Kafka suscritos a uno o varios topics responsables de consumir los mensajes.

  • Broker: Cada uno de los nodos de kafka que forman el cluster.

Cada topic Kafka los mantiene en un log particionado. Cada partición es una secuencia de mensajes inalterable, donde los mensajes son añadidos a una de las particiones según van llegando, y se les va asignando un número secuencial, llamado offset, que identifica de forma única a cada mensaje dentro de su partición.

Todos los mensajes son mantenidos en las particiones para su consumo durante un periodo de tiempo configurado en a nivel de cluster. Una vez se supera ese periodo de tiempo los mensajes son eliminados para liberar espacio.

Kafka proporciona tolerancia a fallos replicando cada partición del log en un número configurable de servidores dentro del cluster. La caída de uno de estos nodos no afecta al servicio. Uno de estos nodos actúa como líder mientras que el resto actúan de followers. Este líder atienden todas las lecturas y escrituras de la partición mientras que los followers actúan como consumidores del líder replicando su contenido. Si el líder falla automáticamente uno de los followers toma el rol del líder.

En un cluster de Kafka cada servidor actúa como líder de una partición y como follower de una partición diferente, por lo que la carga del sistema se encuentra bien balanceada en todo el cluster.

Normalmente los sistemas de mensajería como JMS guardan y van entregando los mensajes en el mismo orden en el que llegan. Sin embargo una vez que llegan uno a uno al consumidor, el proceso del mensaje es asíncrono y por tanto el orden de salida del mensaje se puede llegar a perder. Para garantizar el orden en la entrega de los mensajes se asigna un único consumidor, lo que hace que se pierda la capacidad de escalado.

Kafka aporta una solución a este problema, permitiendo el escalado y la garantía de la entrega de los mensajes en el orden en el que llegan. Como sabemos, podemos dividir un topic en varias particiones (cada partición podría estar replicada, para garantizar la tolerancia al fallo), con lo que logramos escalar el sistema. Los productores de mensajes pueden publicar los mensajes en base a una clave y por tanto son enviados a la misma partición. Finalmente a cada partición se subscribe un grupo consumidor, momento en el que solo un consumidor del grupo comienza a procesar los mensajes de esa partición, mientras se procesa también el resto de mensajes de las otras particiones consumidos por otros consumidores, logrando garantizar el orden de entrega de los mensajes, mientras se sigue balanceando y escalando el sistema.

Por ejemplo, asumiendo que los mensajes están particionados en base al user_id, recibimos cuatro mensajes user_id 1, 2, 3 y 4. El mensaje recibido para el usuario 1 irá a la partición 1, el mensaje recibido para el usuario 2 irá a la partición 2 y así sucesivamente. También se asume que tenemos cuatro consumidores del topic user, Kafka asignará entonces un consumidor para cada partición. Si en lugar de cuatro consumidores hubiese tan solo dos disponibles, esos dos tendrían que repartirse el trabajo de dos particiones respectivamente. Se infiere entonces que Kafka solo provee seguridad en el orden dentro del concepto de partición, no entre particiones dentro de un mismo topic.

3.3 Síncrono vs Asíncrono en Javascript

Cuando un navegador carga una página web, entre otras cosas, va recibiendo el código fuente HTML a mostrar. A medida que lo va recibiendo, lo lee y procesa. Cuando llega a un fichero Javascript, el navegador detiene la lectura y procesamiento del HTML, hasta que el navegador descarga el fichero javascript y lo ejecuta. Una vez concluye este proceso, continuará la carga de la página.

Para solucionar los daños colaterales que tiene la carga síncrona de javascript, en HTML tenemos el atributo async, para indicar al navegador que el código javascript de esa línea, se tiene que cargar de forma asíncrona, por lo que ese código javascript se descargará de forma paralela, sin detener la carga de la página, y se ejecutará nada más estar disponible.

De esta forma, el código javascript no bloquea la carga del DOM y, por lo tanto, la página está disponible mucho antes evitando la eterna página en blanco o la carga a saltos.

Cuando cargamos el Javascript de forma síncrona, el navegador llega a la primera línea de fichero Javascript y detiene el proceso de carga mientras descarga y procesa ese Javascript. Cuando ya termina de ejecutar ese fichero, continúa la carga.

Al hacerlo de forma asíncrona, el proceso es el mismo, pero sin detener en ningún momento la carga, lo que significa que todos nuestros ficheros javascript iniciarán su descarga prácticamente al mismo tiempo y se ejecutarán cada uno a su tiempo (cuando esté disponible). Pero, ¿por qué esto es malo? Pensemos en cómo usamos el Javascript!

Normalmente utilizamos jQuery u otro framework de Javascript y nuestro código depende del framework. Además, nuestro código suele ser más liviano que el framework (lo que es normal claro), por lo que pesa menos. A la hora de descargar ambos ficheros en paralelo, nuestro javascript se descargará antes que el framework, lo que significa que se ejecutará también antes.

¿Cómo podemos solucionar el problema? Lo primero es recordar por qué queremos cargar el javascript de forma asíncrona. Con ese objetivo en mente, podemos valorar una solución alternativa: Que el javascript que necesitamos, se descargue y ejecute una vez esté el DOM cargado. Con esta idea en mente, podemos encontrar muchas soluciones en la red, aplicando el patrón lazy loading como por ejemplo hacen el framework AngularJS o la librería RequireJS.

Promesas

Una promise (Promesa) se usa para computaciones asíncronas. Una promesa representa un valor que puede estar disponible ahora, en el futuro, o nunca.

Es un proxy para un valor no necesariamente conocido en el momento que es creada la promesa. Permite asociar manejadores que actuarán de manera asíncrona sobre un eventual valor en caso de éxito, o la razón de falla en caso de una falla. Esto permite que métodos asíncronos devuelvan valores como si fueran síncronos, en vez de inmediatamente retornar el valor final, el método asíncrono devuelve una promesa de suministrar el valor en algún momento en el futuro.

Una Promesa se encuentra siempre en alguno de los siguientes estados:

  • pendiente (pending): estado inicial, no cumplida o rechazada.

  • cumplida (fulfilled): significa que la operación se completó satisfactoriamente.

  • rechazada (rejected): significa que la operación falló.

Una promesa pendiente puede ser cumplida con un valor, o rechazada con una razón (error). Cuando cualquiera de estas dos opciones sucede, los métodos asociados, encolados por el método then de la promesa, son llamados.

4.- Big Data

4.1 Conceptos básicos

Big data o macro datos es un término que hace referencia a una cantidad de datos tal que supera la capacidad del software convencional para ser capturados, administrados y procesados en un tiempo razonable.

Existen muchas herramientas para tratar con big data. Algunos ejemplos incluyen Hadoop, bases de datos NoSQL como Cassandra, inteligencia empresarial, aprendizaje automático y MapReduce.

El NoSQL engloba una gran clase de sistemas de gestión de bases de datos que se caracterizan porque no requieren estructuras fijas tales como tablas. Por el contrario, se basan en otros sistemas de almacenamiento como clave-valor, mapeo de columnas o grafos.

A diferencia de los modelos tradicionales de almacenamiento de información, el NoSQL permite manejar un mayor volumen de datos y evita que se generen cuellos de botella. Además, no requiere de apenas computación, por lo que ahorra costes en maquinaria

Para poder procesar y analizar grandes cantidades de datos masivos es necesario software libre. Existen muchas herramientas, pero mayoría se basan en el Hadoop Distributed File System (HDFS), un sistema de archivos distribuido, escalable y portátil.

El HDFS está escrito en Java para Hadoop, un framework que permite a las aplicaciones trabajar con miles de nodos y petabytes de datos.

Estas herramientas tratan con algunos de los tres tipos de big data:

  • Datos estructurados: datos que tienen bien definidos su longitud y su formato, como las fechas, los números o las cadenas de caracteres. Se almacenan en tablas. Un ejemplo son las bases de datos relacionales y los almacenes de datos.

  • Datos no estructurados: datos en el formato tal y como fueron recolectados, carecen de un formato específico. No se pueden almacenar dentro de una tabla ya que no se puede desgranar su información a tipos básicos de datos. Algunos ejemplos son los PDF, documentos multimedia, correos electrónicos o documentos de texto.

  • Datos semi estructurados: datos que no se limitan a campos determinados, pero que contiene marcadores para separar los diferentes elementos. Es una información poco regular como para ser gestionada de una forma estándar. Estos datos poseen sus propios metadatos semi estructurados que describen los objetos y las relaciones entre ellos, y pueden acabar siendo aceptados por convención. Como ejemplos tenemos los archivos tipo hojas de cálculo, HTML, XML o JSON.

4.2 - MapReduce e Indexación

4.2.2 - MapReduce

El objetivo principal de MapReduce es permitir la computación paralela sobre grandes colecciones de datos permitiendo abstraerse de los grandes problemas de la computación distribuida. MapReduce consta de 2 fases: Map y Reduce. Las funciones Map y Reduce se aplican sobre pares de datos (clave, valor).

Map toma como entrada un par (clave, valor) y devuelve una lista de pares (clave2, valor2). Esta operación se realiza en paralelo para cada par de datos de entrada.

Luego el framework MapReduce (como Hadoop MapReduce) agrupa todos los pares generados con la misma clave de todas las listas, creando una lista por cada una de las claves generadas.

Reduce se realiza en paralelo tomando como entrada cada lista de las obtenidas en el Map y produciendo una colección de valores.

Apache Hadoop es un framework de código abierto que permite el procesamiento distribuido de grandes conjuntos de datos en varios clusters de ordenadores, pero que a ojos del usuario parece un único ordenador. Hadoop separa y distribuye automáticamente los archivos que contienen los datos, además de dividir el trabajo en tareas más pequeñas y ejecutarlas de manera distribuida y recuperarse de posibles fallos automáticamente y de forma transparente al usuario. El proyecto Apache Hadoop consta de los siguientes módulos:

  • Hadoop Common: proporciona el acceso a los sistemas de archivos soportados por Hadoop y contiene el código necesario para poder ejecutar el framework. Además, se pone a disposición del usuario el código fuente y la documentación necesaria para aprender a utilizar la herramienta.

  • Hadoop Distributed File System: HDFS es un sistema de ficheros distribuido altamente tolerante a fallos y diseñado para utilizarse en hardware de bajo coste. Proporciona un alto rendimiento en el acceso a datos y se adapta sin dificultad a aplicaciones con grandes conjuntos de datos. Fue desarrollado originalmente para el proyecto Apache Nutch, un motor de búsquedas web.

  • Hadoop YARN (Yet Another Resource Negociator): YARN es una tecnología de gestión de clusters. Se compone de un gestor de recursos central y un gestor por cada nodo, que se ocupa de controlar un único nodo.

  • Hadoop MapReduce: fue desarrollado para hacer frente a problemas de manera distribuida, los cuales comparten ciertas similitudes pero que requieren desarrollos completos desde el inicio. En datos distribuidos es común tener dos fases: fase de mapeado (map phase) y fase de reducción (reduce phase). La fase de mapeado trabaja sobre datos sin procesar y produce valores intermedios que pasan a la fase de reducción para producir la salida final de MapReduce. Con esta tecnología se evita el tener que diseñar distintos modelos para resolver problemas similares.

4.2.3 - Indexación

Cuando se trata con una cantidad pequeña de documentos, es posible realizar una búsqueda de texto completa (full-text search) directamente analizando el contenido de los documentos con cada petición. a esta estrategia se la conoce como serial scanning. Es lo que hacen herramientas tan populares como grep. Sin embargo, cuando el número de documentos es potencialmente grande, o la cantidad de búsquedas es alta el rendimiento del sistema puede verse gravemente afectado al escanear los documentos con cada petición.

Para ello se hace uso de la indexación. En la fase de indexado se crea una lista de todas las unidades (palabras) contenidas en todos los documentos, así en la fase de búsqueda se consultará exclusivamente este índice para encontrar el documento original referenciado.

Si, por ejemplo, se realiza una búsqueda de documentos que contentan la palabra “elección”, es mucho más rápido consultar el índice que el contenído de todos los documentos. Esto es básicamente el principio fundamental de los motores de búsqueda. En el mundo real, es también muy frecuente buscar contenido a través de más de una palabra, por ejemplo “elecciones en Cataluña”, y es aquí cuando entra en juego el concepto de relevancia: la palabra “en” es probablemente menos relevante que la palabra “Cataluña” y eso se reflejará en los resultados de búsqueda obtenidos.

4.3 Motores de Búsqueda

Cada vez que un usuario realiza una búsqueda en un motor de búsqueda, éste último consulta su índice con el fin de entregar el resultado que considera más relevante. La relevancia es una puntuación que se asigna a cada documento a partir de una búsqueda determinada. Uno de los algoritmos más populares es TF-IDF (Term Frequency - Inverse Document Frequency) donde:

  • Term Frecuency: Cuantas más veces aparezcan las palabras buscadas en un documento, mayor es la puntuación asignada a ese documento.

  • Inverse Document Frecuency: El peso de cada palabra en el documento es mayor si la palabra no es común en el resto de documentos.

Si se busca “carrera de bicicletas” en un blog de ciclistas, la palabra “bicicletas” es menos relevante que la palabra “carrera”. Así mismo cuantas más veces aparezcan esas dos palabras en el mismo post mayor será la puntuación de relevancia del documento.

También resulta común combinar este algoritmo con otras técnicas. Se podría determinar que el título de un post tiene más relevancia que su contenido. O quizá, puede también influir el número de likes que tengan los posts.

Se puede ir más allá de la coincidencia exacta (exact matching) en las búsquedas. Existen técnicas que implementan algunos buscadores para ir más allá y aportar más valor a los resultados obtenidos. Algunas son:

  • Manejo de Erratas (Handling Typos): El motor se puede configurar para que sea tolerante con las variaciones en lugar de buscar la coincidencia exacta. También se conoce como Fuzzy Search.

  • Permitir Derivaciones (Support Derivatives): Se puede entrenar a un buscador para que reconozca palabras derivadas y devuelva documentos relevantes. Por ejemplo, si se busca “ciclismo” quizá el motor devuelva documentos en los que solamente aparece la palabra “bicicleta”.

  • Uso de Estadísticas (Using Statistics): Cuando los usuarios no saben exactamente lo que están buscando, el motor puede ayudarles con estadísticas de uso.

Proveer Sugerencias (Providing Suggestions): A medida que el usuario escribe su consulta, el motor puede también sugerirle términos que han sido buscados con frecuencia a partir de la búsqueda que el usuario está realizando, ayudando a decidir cual es el mejor término para su consulta.

Elasticsearch es un motor de búsqueda open source altamente escalable. A pesar de que empezó como un buscador de texto ha evolucionado como una herramienta de análisis, que soporta no solo búsquedas simples sino también agregación compleja de información. Su naturaleza distribuida permite su escalabilidad a medida que la cantidad de información que contiene crece, sin perder rendimiento de forma significativa.

Elasticsearch está basado en Apache Lucene (al igual que Apache Solr) y está implementado en Java. Lucene provee busqueda e indexación, Elasticsearch amplía Lucene añadiendo una cómoda API REST basada en JSON para facilitar la comunicación con Lucene y además provee las características de sistema distribuido que permite escalar la herramienta de forma casi transparente al usuario.

You can’t perform that action at this time.