Ted Levitt
La gente no quiere un índice de cuatro teclas. Necesitan una respuesta de cuatro ms.
El desarrollo de aplicaciones es exigente. Cada aplicación intenta avanzar en nombre del cliente: buscar el producto o el formulario adecuados, hacer pedidos, cancelarlos, enviarlos, comprobar su estado, etc.
Las bases de datos y la infraestructura típicas basadas en consultas y B-Tree son adecuadas para desarrollar muchos de los módulos. Aun así, hay casos en los que la búsqueda basada en SQL y B-Tree resulta ineficaz para cumplir su SLA.
Aquí están los siete problemas que encontrarías en el desarrollo de SQL y las soluciones para ellos usando la tecnología de Búsqueda. He usado Couchbase N1QL como ejemplo de implementación SQL, Couchbase GSI como ejemplo de índices basados en B-Tree (lógicamente hablando) y FTS (Couchbase Full-Text Search) como ejemplo de tecnología de búsqueda.
También utilizaré el viaje-muestra y los datos del conjunto de datos de muestra de Couchbase en los ejemplos.
Retos para los desarrolladores
1. El problema de búsqueda de NOMBRE (también conocido como el problema LIKE)
A los medios sociales les puede gustar como a menudo, pero SQL no. Los predicados LIKE son una de las primeras cosas que hay que tener en cuenta cuando se utilizan en una consulta. Sin un uso cuidadoso, el predicado LIKE aumentará la latencia, reducirá el rendimiento y bloqueará la operación. He aquí un ejemplo de predicados LIKE que he visto en aplicaciones reales de clientes.
- WHERE nombre LIKE "%joe%";
- WHERE UPPER(nombre) LIKE "%JOE%"
- WHERE REGEX_CONTAINS(nombre, ".*joe.*")
- WHERE nombre LIKE "% joe %"
Problemas:
- Incluso cuando tenga un índice sobre el nombre, tendrá que escanear todo el índice para determinar todos los documentos con joe.
- Tendrías que crear un índice funcional (por ejemplo, UPPER(nombre)) y tener la clave adicional con el nombre para guardar el caso exacto del nombre. Por ejemplo, McDonald
- Con los tres primeros predicados, no sólo obtendrá joe, sino también las cadenas joel, sojoel, bejoel, etc.
2. El problema de los partidos múltiples
Cuando los nombres se dividen en nombre, segundo nombre y apellido, hay que buscar en todas partes. A veces, querrás buscar una cadena en cualquiera de los nombres. Combina eso con la búsqueda de su número de llamada. Su consulta será algo parecido a esto.
SELECT c.customerid, c.fname, c.mname, c.lname, c.address
DESDE cliente c
WHERE (c.fname in ['John', 'Doe']
OR c.mname in ['John', 'Doe']
OR c.lname in ['John', 'Doe'])
OR (ANY p in c.contacts satisfies p = '6509264813')
OR (ANY q in c.services satisfies q = '6509264813')
Ahora, intenta crear un índice B-TREE que te proporcione no sólo la baja latencia que deseas, sino también un alto rendimiento.
3. Más allá de la coincidencia exacta
Los partidos del mundo real son inexactos. William puede abreviarse como Will, Willy, Bill y Billy. Los irlandeses y los escoceses tienen abreviaturas adicionales. ¿Cómo se puede buscar Billy y acabar con William?
4. Búsqueda basada en los requisitos de la empresa
Cada empresa tiene normas sobre la resolución de la información del cliente (nombre, dirección y fecha de nacimiento) a un cliente en particular. Algunas dan más prioridad a la dirección que al nombre o la dirección. Las reglas pueden decir, los dos de cuatro tendrá que ser una coincidencia exacta y otros tienen que ser una coincidencia parcial. Los predicados N1QL se vuelven aún más complejos que en el segundo caso. Por lo tanto, el índice GSI adecuado para la consulta también se complica.
5. Ordenación por relevancia
El orden de N1QL se basa en la cláusula ORDER BY y la expresión dentro de ella. Cuando se hace una coincidencia inexacta, es posible que desee el orden basado en la puntuación del texto y la distancia desde el patrón de búsqueda dado.
6. El problema de la matriz.
Las matrices JSON te ofrecen la posibilidad de almacenar las relaciones 1:N, N:M como una matriz de objetos o matrices con enlaces a otros objetos. Este artículo le ofrece detalles sobre la manipulación de matrices. La página segundo artículo describe operaciones adicionales orientadas a conjuntos sobre matrices. Para obtener el mejor rendimiento al buscar dentro de matrices, es necesario crear índices con claves de matriz. El índice de matriz tiene una limitación: cada índice de matriz sólo puede tener una clave de matriz por índice. Por lo tanto, si tienes un objeto cliente con varios campos de matriz, no puedes buscar en todos ellos utilizando un único índice. Cuando tienes clientes con un array de números de contacto y un array de contactos de correo electrónico, no puedes buscar por ambos utilizando un único índice, lo que provoca consultas costosas. Se trata de una limitación común de los índices de matrices basados en árboles b. Dado que el índice del árbol B almacena los elementos individuales de la matriz como claves distintas, al tener varias claves de matriz será necesario que el índice almacene el producto del número de claves (en el ejemplo anterior, (número de números de contacto) * (número de correos electrónicos). El tamaño del índice aumentará exponencialmente. Para evitar esto, los productos deshabilitan la creación de múltiples claves de array para un único índice.
Pero, el trabajo del desarrollador para buscar eficientemente en múltiples matrices JSON permanece.
7. Consultas múltiples, índice único.
El modelo JSON se ha utilizado con éxito para agregar información de múltiples fuentes. El objetivo de agregar toda esta información no es simplemente almacenarla, sino consultarla y obtener valor empresarial. Cuando se tiene una consulta ad-hoc sobre un conjunto de datos, es difícil planificar previamente y volver a crear los índices necesarios para ello. El propio N1QL proporciona el índice adaptativo que ayuda en determinados casos de uso. Si tiene varios predicados, cada uno de los cuales puede devolver un conjunto de resultados grande, el índice adaptativo tendrá problemas de rendimiento debido a la cantidad de transferencia de datos del índice al nodo de consulta y al gran conjunto de resultados intermedios.
En este caso, los desarrolladores deben supervisar y ajustar continuamente los índices. Los usuarios con problemas de rendimiento seguirán quejándose.
Soluciones:
1.El problema de búsqueda de NOMBRE (también conocido como problema LIKE)
Tanto si busca joe, Joel o jolly, todas suelen ser palabras sueltas. Normalmente no se busca lajoella. El índice de búsqueda toma cada palabra de su campo (nombre, título o comentario), la analiza en función de las características lingüísticas, clasifica las palabras en stop words (palabras vacías y que no suelen buscarse - por ejemplo: and, or, not, the en inglés) y términos. Indexar el término en lugar de la palabra propiamente dicha tiene dos ventajas.
- Agrupa las palabras relacionadas: francisco, francisco, frank, francoisetc. Al buscar francisco, también encontrará palabras estrechamente relacionadas francisco.
- Esta agrupación también reducirá el tamaño del índice, ya que sólo tendremos que indexar el término raíz en lugar de todos los modificadores de esa palabra.
Volvamos a la búsqueda del nombre: Busquemos ahora el nombre del hotel.
- Crear un índice FTS en documentos de tipo hotel y el campo nombre en el documento.
- Empieza a buscar el nombre:
- Aquí está la consulta a ejecutar:
curl -XPOST -H "Content-Type: application/json" \ http://172.23.120.38:8094/api/index/trhotelname/query \ -d '{ "explain": true, "fields": [ "*" ], "highlight": {}, "query": { "query": "francisco" } }'
- Búsqueda de francisco te da los nombres de los hoteles:
-
- "name": "The Opal San Francisco",
- "name": "Francisco Bay Inn",
- y mucho más (11 resultados en total).
-
2. El problema de los partidos múltiples
Antes vimos la consulta compleja.
SELECT c.customerid, c.fname, c.mname, c.lname, c.address
DESDE cliente c
WHERE (c.fname in ['John', 'Doe']
OR c.mname in ['John', 'Doe']
OR c.lname in ['John', 'Doe'])
OR (ANY p in c.contacts satisfies p = '6509264813')
OR (ANY q in c.services satisfies q = '6509264813')
En FTS, podemos definir un campo especial _all que combina varios campos. La búsqueda en este índice busca automáticamente en TODOS los campos. (Véase también este artículo) Se trata de un índice sobre cinco campos del documento del hotel:
- ciudad
- país
- nombre
- public_likes (un campo de matriz)
- descripción
Ejemplos de valores para los cinco campos:
[
{
"ciudad": "Medway",
"país": "Reino Unido",
"Descripción": "Albergue de verano de 40 camas a unos 5 km de Gillingham, ubicado en una característica Oast House reconvertida en un entorno semirural.",
"name": "Albergue Juvenil Medway",
"public_likes": [
"Julius Tromp I",
"Corrine Hilll",
"Jaeden McKenzie",
"Vallie Ryan",
"Brian Kilback",
"Lilian McLaughlin",
"Sra. Moses Feeney",
"Elnora Trantow"
]
}
]
Una vez que defina el índice de búsqueda en todos estos campos, simplemente empiece a consultar.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
rizo -XPOST -H "Content-Type: application/json" \ http://172.23.120.38:8094/api/index/trinfoall/query \ -d '{ "explicar": true, "fields": [ "*" ], "highlight": {}, "consulta": { "consulta": "verano" } }' |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
rizo -XPOST -H "Content-Type: application/json" \ http://172.23.120.38:8094/api/index/trinfoall/query \ -d '{ "explicar": true, "fields": [ "*" ], "highlight": {}, "consulta": { "query": "medway \"Vallie Ryan\" reino unido" } }' |
Esta es la definición de índice para el índice de campo _all utilizado aquí.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 |
{ "tipo": "fulltext-index", "nombre": "trinfoall", "uuid": "430401ce6b5c1879", "sourceType": "couchbase", "sourceName": "viaje-muestra", "sourceUUID": "ddd78a53e740e6a8274e13c990b47abc", "planParams": { "maxPartitionsPerPIndex": 171 }, "params": { "doc_config": { "docid_prefix_delim": "", "docid_regexp": "", "mode": "tipo_campo", "tipo_campo": "tipo" }, "mapeo": { "análisis": {}, "default_analyzer": "estándar", "default_datetime_parser": "dateTimeOptional", "default_field": "_todos", "default_mapping": { "dinámico": verdadero, "activado": falso }, "tipo_por_defecto": "_por defecto", "docvalues_dynamic": verdadero, "index_dynamic": verdadero, "store_dynamic": falso, "tipo_campo": "_tipo", "tipos": { "hotel": { "dinámico": verdadero, "activado": verdadero, "propiedades": { "ciudad": { "dinámico": falso, "activado": verdadero, "campos": [ { "docvalues": verdadero, "incluir_en_todos": verdadero, "include_term_vectors": verdadero, "índice": verdadero, "nombre": "ciudad", "tipo": "texto" } ] }, "país": { "dinámico": falso, "activado": verdadero, "campos": [ { "docvalues": verdadero, "incluir_en_todos": verdadero, "include_term_vectors": verdadero, "índice": verdadero, "nombre": "país", "tipo": "texto" } ] }, "descripción": { "dinámico": falso, "activado": verdadero, "campos": [ { "docvalues": verdadero, "incluir_en_todos": verdadero, "include_term_vectors": verdadero, "índice": verdadero, "nombre": "descripción", "tipo": "texto" } ] }, "nombre": { "dinámico": falso, "activado": verdadero, "campos": [ { "docvalues": verdadero, "incluir_en_todos": verdadero, "include_term_vectors": verdadero, "índice": verdadero, "nombre": "nombre", "tipo": "texto" } ] }, "public_likes": { "dinámico": falso, "activado": verdadero, "campos": [ { "docvalues": verdadero, "incluir_en_todos": verdadero, "include_term_vectors": verdadero, "índice": verdadero, "nombre": "public_likes", "tipo": "texto" } ] } } } } }, "tienda": { "tipo de índice": "chamuscar", "kvStoreName": "" } }, "sourceParams": {} } |
3. Más allá de la coincidencia exacta
Search (FTS) hace más que una búsqueda exacta. Realiza la búsqueda de términos, la búsqueda de rangos, la búsqueda difusa, conjuntos, disjuntos, geo-búsqueda (vecino más cercano), búsqueda regex, búsqueda potenciada, búsqueda de frases y mucho más. Vea ejemplos y detalles en Documentación de Couchbase FTS.
4.Búsqueda basada en los requisitos de su empresa
Cuando busque hoteles en Nueva York, es posible que prefiera los hoteles de Manhattan, pero que quiera ver también otros hoteles. Para ello, basta con potenciar el término de búsqueda Manhattan añadiendo ^5. Esta potenciación mejora la puntuación de los documentos que contienen Manhattan. Los resultados se ordenan por defecto en orden descendente de puntuación.
5. Ordenación por relevancia
En SQL, la ordenación se basa en el valor de la expresión o del propio campo. En la búsqueda, la relevancia de un documento se calcula por la distancia entre lo que se busca y lo que contiene el documento. Esta es la puntuación que manipulamos al aumentar la importancia de un término en la sección anterior. Puede ordenar los resultados por esta puntuación y cualquier otro campo utilizando la cláusula de ordenación de la petición de búsqueda.
6. El problema de las matrices
Ahora, vamos a crear un índice único en las siguientes cuatro matrices.
- me gusta_público
- reseñas.autor
- reviews.ratings.Location
- reseñas.valoraciones.Servicio
Aquí está la definición del índice. Se trata de un índice único sobre cuatro claves de matriz. Esto es algo que nunca se podría hacer en un índice basado en un árbol B.
Ahora puede realizar consultas utilizando predicados en uno o varios de los campos anteriores.
EJEMPLO 1: hoteles que le gustan a "Vallie Ryan" o valoración del servicio superior a 4
‘{
"explicar": true,
"fields": [ "*" ],
"highlight": {},
"query": {"query": "+public_likes:\"Vallie Ryan\" reviews.ratings.Service:>4″ }
}’
Índice completo Definición.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 |
{ "tipo": "fulltext-index", "nombre": "trmultiarrayidx", "uuid": "7b3a85992989e196", "sourceType": "couchbase", "sourceName": "viaje-muestra", "sourceUUID": "ddd78a53e740e6a8274e13c990b47abc", "planParams": { "maxPartitionsPerPIndex": 171 }, "params": { "doc_config": { "docid_prefix_delim": "", "docid_regexp": "", "mode": "tipo_campo", "tipo_campo": "tipo" }, "mapeo": { "análisis": {}, "default_analyzer": "estándar", "default_datetime_parser": "dateTimeOptional", "default_field": "_todos", "default_mapping": { "dinámico": verdadero, "activado": falso }, "tipo_por_defecto": "_por defecto", "docvalues_dynamic": verdadero, "index_dynamic": verdadero, "store_dynamic": falso, "tipo_campo": "_tipo", "tipos": { "hotel": { "dinámico": falso, "activado": verdadero, "propiedades": { "public_likes": { "dinámico": falso, "activado": verdadero, "campos": [ { "docvalues": verdadero, "incluir_en_todos": verdadero, "include_term_vectors": verdadero, "índice": verdadero, "nombre": "public_likes", "tipo": "texto" } ] }, "reviews.author": { "dinámico": falso, "activado": verdadero, "campos": [ { "docvalues": verdadero, "incluir_en_todos": verdadero, "include_term_vectors": verdadero, "índice": verdadero, "nombre": "reviews.author", "tipo": "texto" } ] }, "reviews.ratings.Location": { "dinámico": falso, "activado": verdadero, "campos": [ { "docvalues": verdadero, "incluir_en_todos": verdadero, "include_term_vectors": verdadero, "índice": verdadero, "nombre": "reviews.ratings.Location", "tipo": "texto" } ] }, "reviews.ratings.Service": { "dinámico": falso, "activado": verdadero, "campos": [ { "docvalues": verdadero, "incluir_en_todos": verdadero, "include_term_vectors": verdadero, "índice": verdadero, "nombre": "reviews.ratings.Service", "tipo": "texto" } ] } } } } }, "tienda": { "tipo de índice": "chamuscar", "kvStoreName": "" } }, "sourceParams": {} } |
7.Consultas múltiples, índice único.
"A veces, las consultas son como una caja de bombones. No sabes qué consulta vas a recibir". Cuando se desea exponer los datos a usuarios empresariales que pueden realizar consultas ad hoc, no se puede crear cualquier tipo de índice para acelerar cualquier tipo de consulta. El ajuste del rendimiento requiere un enfoque diferente.
Considere el índice creado en la sección (2) anterior sobre los 5 campos:
- ciudad
- país
- nombre
- public_likes (un campo de matriz)
- descripción
Si creara un árbol B (índice GSI en el caso de Couchbsae), tendría el siguiente aspecto:
CREAR ÍNDICE itravel EN viaje-muestra
(ciudad, país, nombre, DISTINCT public_likes, descripción) WHERE type = 'hotel';
Las consultas que se beneficiarían de ello son las siguientes normas explicadas aquí. El principal problema es que cada bloque de consulta (en concreto, la exploración del índice) tendrá que utilizar predicados en la(s) clave(s) principal(es) del índice. Esto limita la eficacia del índice.
Con el índice search(FTS) creado anteriormente, puedes realizar consultas basadas en cualquier conjunción, disyunción, must have, etc con cualquier combinación. Con consultas complejas, la búsqueda puede tardar más de unos pocos milisegundos, pero estás haciendo todo esto en un solo índice. La flexibilidad con un uso razonable de los recursos hace que el índice de búsqueda sea muy valioso.
Nota: En la próxima versión 6.5 de Couchbase, hemos facilitado la consulta utilizando el índice de búsqueda. Binh Le lo ha explicado en este artículo:https://www.couchbase.com/blog/n1ql-and-search-how-to-leverage-fts-index-in-n1ql-query/
Referencias
- Documentación de Couchbase: https://docs.couchbase.com/server/6.0/fts/full-text-intro.html
- Recursos de búsqueda de Couchbase: https://www.couchbase.com/products/full-text-search
- Formación online de Couchbase FTS: https://learn.couchbase.com/store/509465-cb121-intro-to-couchbase-full-text-search-fts
- Blogs de Couchbase: https://www.couchbase.com/blog/category/full-text-search/
- Comparación de la búsqueda de texto en Couchbase y MongoDB: https://www.couchbase.com/blog/searching-json-comparing-text-search-in-couchbase-and-mongodb/