Escrito por: Keith Harper
Revisado por: Robert Randolph, Jarrod Taylor


Como escritor de una query de Datomic, tu tienes control sobre el orden de las cláusulas de query, lo que puede ser muy poderoso si tienes suficiente información para ordenar las cláusulas de query de mayor a menor selectiva. Algunas medidas de selectividad de cláusulas son la cantidad de filas que entran y salen de cada cláusula, así como la delimitación de las variables lógicas utilizadas por cada cláusula. La información devuelta por query-stats puede informarte sobre esas medidas y usarse para tomar una decisión informada sobre el orden de las cláusulas.

Usaremos el repositorio de código de base de datos de muestra de MusicBrainz para todos los ejemplos de este artículo. Para obtener un tutorial rápido sobre cómo hacer que Datomic se ejecute localmente con la base de datos de muestra de MusicBrainz, consulta el archivo LÉAME.

Usaremos query-stats para lograr el estado «después» que se muestra en la imagen de arriba manteniendo el resultado intermedio lo más pequeño posible entre cláusulas.

Cómo solicitar query-stats

A partir de la v1.0.6610 de Datomic On-Prem,que pronto se lanzará en Datomic Cloud,los usuarios ahora pueden solicitar query-stats cuando ejecutan queries.

Para ejecutar una query mientras se acumulan query-stats, necesitaremos aprovechar datomic.api/query y proporcionar «:query-stats true» en el argumento del mapa de query.

Algunas partes de las estadísticas de consulta se explicarán con más detalle en las siguientes secciones. Para obtener información adicional, consulta la documentación del usuario de query-stats.

Check our job opportunies

Escenario: utiliza query-stats mientras optimiza el orden de las cláusulas

En este escenario, utilizaremos query-stats para guiar nuestro proceso de pensamiento mientras buscamos el orden óptimo de las cláusulas.Realizaremos una consulta que encontrará todos los álbumes lanzados antes de 1970 en los que al menos uno de los artistas fuera John Lennon, luego devolveremos el nombre del álbum y el año de lanzamiento.

{:query       '{:find  [?album-name ?year]
                :in    [$ ?artist-name]
                :where [[?artist :artist/name ?artist-name]
                        [?release :release/artists ?artist]
                        [?release :release/name ?album-name]
                        [?release :release/year ?year]
                        [(< ?year 1970)]]}
 :args        [db "John Lennon"]
 :query-stats true}

Los resultados de la solicitud de query-stats para este query se pueden encontrar aquí.

:phases

Cada elemento de «:phases» representa una colección de cláusulas que se procesarán como una única unidad de trabajo. Para la mayoría de las consultas, es posible que solo haya un elemento debajo, pero ciertas consultas tendrán varios elementos en «:phases» (por ejemplo, aquellas que usan reglas de query)

:sched

«:sched» es la abreviatura de «schedule» y representa el plan aproximado que utilizará el motor de consultas al procesar esta consulta:

(([(ground $__in__2) ?artist-name]
  [?artist :artist/name ?artist-name]
  [?release :release/artists ?artist]
  [?release :release/name ?album-name]
  [?release :release/year ?year]
  [(< ?year 1970)]))

Puedes notar que hay una cláusula de query adicional en el cronograma que no estaba en la colección de cláusulas que proporcionamos a «:where». “[(ground $__in__2) ?artist-name]” es la forma en que el motor de query vincula el valor (John Lennon) y la variable lógica (“?artist-name”) pasada a “:in”.

:clauses

“:clauses” contiene las cláusulas de query que se ejecutaron para esta fase:

clauserows-inrows-outbinds-inbinds-outexpansionpreds
[(ground $__in__2) ?artist-name]01()[?artist-name]1
[?artist :artist/name ?artist-name]11[?artist-name][?artist]
[?release :release/artists ?artist]121[?artist][?release]20
[?release :release/name ?album-name]2121[?release][?album-name ?release]
[?release :release/year ?year]213[?album-name ?release][?year ?album-name]([(< ?year 1970)])

La tabla anterior podría facilitar un poco la interpretación de las query-stats de las cláusulas. Observe cómo “:rows-in” y “:binds-in” de una cláusula se corresponden directamente con “:rows-out” y “:binds-out” de la cláusula anterior.

Cláusula 1

{:clause    [(ground $__in__2) ?artist-name],
 :rows-in   0,
 :rows-out  1,
 :binds-in  (),
 :binds-out [?artist-name],
 :expansion 1}

Nuevamente, esta cláusula simplemente representa la vinculación del valor y la variable lógica que se pasaron a «:in».

“:rows-in 0” indica que no había filas en el conjunto de resultados antes de procesar esta cláusula. 

“:rows-out 1” indica que el conjunto de resultados ahora tiene 1 cláusula, que contiene el enlace indicado por “:binds-out [?artist-name]”.
“:expansion 1” simplemente significa que “:rows-out” se expandió en tamaño en 1 fila, después de procesar esta cláusula.

Cláusula 2

{:clause    [?artist :artist/name ?artist-name],
 :rows-in   1,
 :rows-out  1,
 :binds-in  [?artist-name],
 :binds-out [?artist]}

Observa cómo “:binds-out” ya no tiene “?artist-name”, pero sí tiene un nuevo enlace de “?artist”. Esto se debe a que el motor de consulta puede ver las cláusulas que se procesarán después de esta, y ninguna de ellas usa «?artist-name», por lo que se puede eliminar de forma segura del conjunto de resultados.

Cláusula 3

{:clause    [?release :release/artists ?artist],
 :rows-in   1,
 :rows-out  21,
 :binds-in  [?artist],
 :binds-out [?release],
 :expansion 20}

Ahora llegamos a la primera cláusula que introduce una expansión del conjunto de resultados real. Las query-stats para esta cláusula indican que se encontraron 21 lanzamientos, en los que John Lennon fue uno de los artistas involucrados.

Cláusula 4

{:clause    [?release :release/name ?album-name],
 :rows-in   21,
 :rows-out  21,
 :binds-in  [?release],
 :binds-out [?album-name ?release]}

Esta cláusula introduce nuevos valores en el conjunto de resultados, como lo indica la diferencia entre enlaces de entrada y salida. A pesar de eso, el número de filas en el conjunto de resultados no aumenta. ¿Qué crees que pasaría si hiciéramos de esta la última cláusula de nuestras cláusulas “:where”?

Cláusula 5

{:clause    [?release :release/year ?year],
 :rows-in   21,
 :rows-out  3,
 :binds-in  [?album-name ?release],
 :binds-out [?album-name ?year],
 :preds     ([(< ?year 1970)])}

La cláusula final realmente reduce el conjunto de resultados y muestra una información que antes no era evidente: “:preds”. La presencia de una clave «:preds» aquí indica que las cláusulas correspondientes se utilizaron como predicados de filtro al encontrar respuestas a “[?release :release/year ?year]”.

Ahora echemos un vistazo a lo que sucedería si moviéramos la cláusula 4 al final de nuestras cláusulas «:where».

Escenario: Realiza Todas las Uniones Necesarias y Luego Proyecta los Valores

Vamos a tomar la misma consulta que ejecutamos antes, excepto que esta vez observaremos lo que sucede cuando movemos “[?release :release/name ?album-name]” al final de nuestras cláusulas “:where”.

{:query '{:find  [?album-name ?year]
          :in    [$ ?artist-name]
          :where [[?artist :artist/name ?artist-name]
                  [?release :release/artists ?artist]
                  [?release :release/year ?year]
                  [(< ?year 1970)]
                  [?release :release/name ?album-name]]}
 :args  [db "John Lennon"]}

Cuando ejecutamos este query, sucede algo muy interesante, aunque no inmediatamente obvio. Los resultados de la solicitud de query-stats para este query se pueden encontrar aquí.

clauserows-inrows-outbinds-inbinds-outexpansionpreds
[(ground $__in__2) ?artist-name]01()[?artist-name]1
[?artist :artist/name ?artist-name]11[?artist-name][?artist]
[?release :release/artists ?artist]121[?artist][?release]20
[?release :release/year ?year]213[?release][?year ?release]([(< ?year 1970)])
[?release :release/name ?album-name]33[?year ?release][?album-name ?year]

Echemos otro vistazo a la cláusula 4 de nuestra primera query:

{:clause    [?release :release/name ?album-name],
 :rows-in   21,
 :rows-out  21,
 :binds-in  [?release],
 :binds-out [?album-name ?release]}

Podemos observar el hecho de que el conjunto de resultados ahora contiene 21 elementos, cada uno de los cuales contiene 2 valores. Como resultado, tenemos un total de 42 valores en el conjunto de resultados.

Ahora comparemos eso con la segunda query:

;; was clause #5, now clause #4
{:clause    [?release :release/year ?year],
 :rows-in   21,
 :rows-out  3,
 :binds-in  [?release],
 :binds-out [?year ?release],
 :preds     ([(< ?year 1970)])}
 
;; was clause #4, now clause #5
{:clause    [?release :release/name ?album-name],
 :rows-in   3,
 :rows-out  3,
 :binds-in  [?year ?release],
 :binds-out [?album-name ?year]}

La cláusula 4 de la segunda query reduce nuestro conjunto de resultados de 21 a 3, pero introduce otro valor en el conjunto de resultados. El conjunto de resultados ahora contiene 3 filas, cada una con 2 valores, lo que da como resultado un total de 6 valores.

Ahora la cláusula que encuentra el nombre del álbum solo tiene que hacerlo para 3 álbumes, en lugar de buscarlos para 21 álbumes y descartar todos menos 3, después de reducir el año de lanzamiento. Como puedes imaginar, esto ahorra bastante trabajo a escalas mayores.

Conclusión

Esta es solo una de las formas en que puedes utilizar la información devuelta por query-stats para comprender y mejorar tus queries. También puedes encontrar que las query-stats son útiles al razonar por qué su consulta funciona de manera diferente dada una distribución diferente de los datos subyacentes, razonar a través de reglas de query recursiva, etc.

Check our job opportunies