Limitaciones que deben violarse o cómo aceleramos las pruebas funcionales tres veces

imagen

Las pruebas funcionales son algo útil. Al principio, no toman mucho tiempo, pero el proyecto está creciendo y se necesitan más y más pruebas. No teníamos la intención de tolerar una desaceleración en la velocidad de entrega y, reuniendo nuestra fuerza, aceleramos las pruebas funcionales tres veces. En el artículo encontrará consejos universales, sin embargo, notará un efecto especial en proyectos grandes.

Brevemente sobre la aplicación


Mi equipo está desarrollando una API pública que proporciona datos a los usuarios de 2GIS. Cuando va a 2gis.ru y busca "Supermercados", obtiene una lista de organizaciones: estos son los datos de nuestra API. En nuestro 2000+ RPS, casi todos los problemas se vuelven críticos si se rompe algún tipo de funcionalidad.

La aplicación está escrita en Scala, las pruebas están escritas en PHP, la base de datos es PostgreSQL-9.4. Tenemos alrededor de 25,000 pruebas funcionales, demoran 30 minutos en completarse en una máquina virtual dedicada para la regresión general. La duración de las pruebas no nos molestó mucho: estábamos acostumbrados al hecho de que las pruebas podían demorar 60 minutos en el marco anterior.

Cómo aceleramos las llamadas pruebas "rápidas"


Todo comenzó por accidente. Como suele pasar. Apoyamos una característica tras otra, pasando las pruebas al mismo tiempo. Su número creció y el tiempo necesario para completarlo también. Una vez que las pruebas comenzaron a arrastrarse fuera de los límites de tiempo asignados a ellos, y por lo tanto, el proceso de su ejecución fue terminado por la fuerza. Las pruebas incompletas están plagadas de un problema perdido en el código.

Analizamos la velocidad de las pruebas y la tarea de acelerarlas rápidamente se hizo relevante. Entonces comenzó un estudio llamado "Las pruebas funcionan lentamente, corríjalo".

A continuación se presentan tres de los grandes problemas que encontramos en las pruebas.

Problema 1: jsQuery mal utilizado



Todos los datos que tenemos se almacenan en la base de datos PostgreSQL. Principalmente en forma de json, por lo que utilizamos activamente jsQuery.

Aquí hay un ejemplo de una consulta que realizamos en la base de datos para obtener los datos necesarios:

SELECT * FROM firm WHERE json_data @@ 'rubrics.@# > 0' AND json_data @@ 'address_name = *' AND json_data @@ 'contact_groups.#.contacts.#.type = “website”' ORDER BY RANDOM() LIMIT 1 

Es fácil notar que el ejemplo usa json_data varias veces seguidas, aunque sería correcto escribir esto:

 SELECT * FROM firm WHERE json_data @@ 'rubrics.@# > 0 AND address_name = * AND contact_groups.#.contacts.#.type = “website”' ORDER BY RANDOM() LIMIT 1 

Tales deficiencias no eran demasiado visibles, porque en las pruebas no escribimos todas las consultas con nuestras manos, sino que usamos QueryBuilders, que ellos mismos componen después de especificar las funciones necesarias. No pensamos que esto podría afectar la velocidad de ejecución de la consulta. En consecuencia, en el código se ve así:

 $qb = $this>createQueryBulder() ->selectAllBranchFields() ->fromBranchPartition() ->hasRubric() ->hasAddressName() ->hasWebsite() ->orderByRandom() ->setMaxResults(1); 

No repita nuestros errores : si hay varias condiciones en un campo JSONB, descríbalas en el marco del operador único '@@'. Después de rehacer, aceleramos el tiempo de ejecución de cada solicitud dos veces. Anteriormente, la solicitud descrita tardaba 7500 ms, pero ahora tarda 3500 ms.

Problema 2: datos de prueba adicionales



El acceso a nuestra API es proporcionado por clave, cada usuario tiene su propia API. Anteriormente, en las pruebas, a menudo era necesario modificar la configuración clave. Debido a esto, las pruebas cayeron.

Decidimos crear varias claves con la configuración necesaria para cada ejecución de regresión para evitar problemas de intersección. Y dado que la creación de una nueva clave no afecta la funcionalidad de toda la aplicación, este enfoque en las pruebas no afectará nada. Vivieron en tales condiciones durante aproximadamente un año, hasta que comenzaron a lidiar con la productividad.

No hay muchas llaves: 1000 piezas. Para acelerar la aplicación, los almacenamos en la memoria y la actualizamos cada pocos minutos o bajo demanda. Por lo tanto, después de guardar la siguiente clave, las pruebas comenzaron el proceso de sincronización, cuyo final no esperábamos, recibimos la respuesta "504", que estaba escrita en los registros. Al mismo tiempo, la aplicación no indicaba ningún problema de ninguna manera, y pensamos que todo funcionó muy bien para nosotros. El proceso de prueba de regresión en sí continuó. Y al final resultó que siempre tuvimos suerte y nuestras llaves se guardaron.

Vivimos en la ignorancia hasta que revisamos los registros. Resultó que creamos las claves, pero no las eliminamos después de ejecutar las pruebas. Por lo tanto, hemos acumulado 500,000 de ellos.

No repita nuestros errores: si de alguna manera modifica la base de datos en las pruebas, asegúrese de que la base de datos vuelva a su estado original. Después de limpiar la base de datos, el proceso de actualización de claves se aceleró 500 veces.

Problema 3: muestreo aleatorio



Nos encanta probar la aplicación en diferentes datos. Tenemos muchos datos y periódicamente se encuentran problemas. Por ejemplo, hubo un caso en el que no se cargaron datos en publicidad, pero las pruebas detectaron este problema a tiempo. Es por eso que en cada solicitud de nuestras pruebas puede ver ORDER BY RANDOM ()

Cuando observamos los resultados de las consultas, con y sin aleatoriedad, con EXPLAIN, vimos un aumento de rendimiento de 20 veces. Si hablamos del ejemplo anterior, entonces, sin aleatorización, funciona durante 160 ms. Pensamos seriamente en qué hacer, porque realmente no queríamos abandonar por completo la casa al azar.

Por ejemplo, en Novosibirsk hay alrededor de 150 mil compañías, y cuando tratamos de encontrar una compañía que tenga una dirección, un sitio web y un encabezado, recibimos un registro aleatorio de casi toda la base de datos. Decidimos reducir la selección a las primeras 100 empresas que se ajustan a nuestras condiciones. El resultado de los pensamientos fue un compromiso entre una selección constante de diferentes datos y velocidad:

 SELECT * FROM (SELECT * FROM firm_1 WHERE json_data @@ 'rubrics.@# > 0 AND address_name = * AND contact_groups.#.contacts.#.type = "website"' LIMIT 100) random_hack ORDER BY RANDOM() LIMIT 1; 

De esta manera simple, no perdimos casi nada con una aceleración de 20x. El tiempo de ejecución de dicha solicitud es de 180 ms.

No repita nuestros errores: este momento, por supuesto, difícilmente se puede llamar un error. Si realmente tiene muchas pruebas, siempre piense en cuánto necesita aleatoriedad en los datos. La compensación entre la velocidad de ejecución de consultas en la base de datos y la singularidad de la selección nos ayudó a acelerar las consultas SQL en 20 veces.

Una vez más, una breve lista de acciones:


  1. Si especificamos varias condiciones para seleccionar datos en el campo JSONB, entonces deben aparecer en un solo operador '@@'.
  2. Si creamos datos de prueba, asegúrese de eliminarlos. Incluso si parece que su presencia no afecta la funcionalidad de la aplicación.
  3. Si necesita datos aleatorios para cada ejecución, encontramos un compromiso entre la unicidad de la muestra y la velocidad de ejecución.

Aceleramos la regresión tres veces gracias a modificaciones simples (y para algunas, probablemente incluso obvias). Ahora nuestras pruebas de 25K pasan en 10 minutos. Y este no es el límite: estamos optimizando el código a continuación. No se sabe cuántos descubrimientos inesperados nos esperan allí.

Source: https://habr.com/ru/post/es419671/


All Articles