El presente post describirá las diferentes caches existentes en Solr y Lucene, sus parámetros más comunes y algunos puntos a tener en cuenta a la hora de utilizarlas, o no.
Funcionamiento
Las caches que nos facilitan Solr y Lucene tienen en esencia el mismo objetivo que cualquier cache: devolver lo que se está buscando de manera más rápida.
Las caches en Solr están asociadas a un “Index Searcher”, es decir, se crean al iniciarse este y finalizan cuando este termina. El motivo de este funcionamiento es porque el índice cambia cuando se realiza un “commit”, mientras esto no ocurra, los documentos que contiene el índice son siempre los mismos y por eso las caches contienen datos validos. Cuando esto ocurre, se va preparando o “auto-calentando” (autowarm) un nuevo “Index Search” que estará vinculado al nuevo índice. Durante este periodo de “auto-calentamiento” se importa información del “Index Searcher” actual al que se está preparando; con ella, las caches. El “Index Searcher” actual sigue activo hasta que termina de atender todos los pedidos que tenía. Al finalizar comienza el nuevo “Index Searcher” a funcionar; este es un proceso se repite con cada “commit” y/o “optimize”.
Las caches se van llenando a medida que se ejecutan las distintas “queries”; hasta llegar a la cantidad máxima de entradas. A medida que se van ejecutando diferentes “queries” mayor cantidad de entradas tendremos en nuestras caches, por ende mayor probabilidad de aciertos.
Para evitar perder toda la información recolectada por una cache durante “su vida” se utiliza el parámetro “autowarmCount”. Este indica la cantidad de datos que regenerara de la cache que está dejando de utilizarse en la cache que va a empezar a utilizarse.
Los diferentes parámetros para configurar cada tipo de cache se encuentran en archivo solrconfig.xml.
Parámetros para la mayoría de las caches
“Class”
En este atributo se especifica la implementación de caches que que quiere utilizar. Existen dos implementaciones incluidas en Solr:
- “solr.search.LRUCache”, se usan las entradas mas recientemente utilizadas de la cache y se descartan las menos usadas.
- “solr.search.FastLRUCache”, tiene la misma funcionalidad que “LRUCache”, pero si al eliminar las entradas menos usadas no se llega al limite estipulado (“acceptableWaterMark”), fuerza la eliminación de mas entradas sin importar el orden de acceso a las mismas.
- Para reducir los tiempos sincronización, y de esta manera utilizar mas eficientemente multiples núcleos en una CPU, “FastLRUCache” se basa en la implementación “ConcurrentLRUCache” y otras técnicas.
Al mismo tiempo, podrían desarrollarse implementaciones personalizadas y especificar en este atributo su nombre completo. El único requisito es que las mismas implementen la interfaz org.apache.solr.search.SolrCache<K, V>.
Se implementan sobre filterCache, queryResultCache, documentCache, fieldValueCache. Se explayaran mas adelante.
“size”, La máxima cantidad de entradas en la cache.
“initialSize”, Es el tamaño inicial del mapa (“key-value”).
“autowarmCount”
La cantidad de entradas que se importaran de la cache vieja.En Solr 4.0 el “autowarmCount” puede ser especificado como porcentaje (ej. "90%"). Esto es evaluado con respecto a la cantidad de ítems que existen en la cache.
“minSize” (opcional)
Sólo se aplica en FastLRUCache. Cuando la cache alcanza su tamaño, la cache trata de bajarlo hasta su “minSize”. Por defecto es 0.9 * size.
“acceptableSize” (opcional)
Sólo funciona en FastLRUCache. Si la cache no puede bajar su tamaño hasta “minSize” lo hace hasta “acceptableSize”. El valor por defecto es 0.95 * size.
“cleanupThread” (opcional)
Sólo aplicable en FastLRUCache, por defecto viene apagado (“false”).
Si se activa, la limpieza correrá en un hilo separado dedicado. Se considera utilizarlo para tamaño (“size”) de cache muy grande. Cuando el tamaño de la cache alcanza el máximo esto activa la limpieza, la cual puede llevar bastante tiempo.
Diferentes tipos de cache
“queryResultCache”
Guarda resultados de búsquedas, es decir, conjuntos de “IDs” de documentos ordenados por algún criterio de la “query” que devuelve el resultado; los resultados de las “queries” normales.
Ejemplos basados en el índice generado con los “exampledocs” del trunk de Solr:
“filterCache”
Almacena todos los conjuntos de IDs de documentos sin ordenar que den respuesta a una “query”.
Dependiendo de la cantidad de documentos que contenga el índice, la cache se organiza de dos maneras diferentes.
En el caso de que existan pocos documentos dentro del índice, la cache guarda todos los Ids correspondientes a cada valor de campo de cada filtro.
Cuando los documentos en el índice son muchos, esta cache almacena vectores de bit por cada valor de campo de cada filtro. Este bit es un valor booleano que indica si el documento en cuestión tiene, o no, coincidencia con el filtro realizado. Cada posición de bit dentro del vector tiene su correspondencia con el ID de documento que genera Lucene internamente, como una secuencia de números enteros; no es el ID definido en el “schema.xml” como clave única.
Tiene tres diferentes propósitos:
- Guarda los resultados de cualquier “Filter Query” (parámetros de fq) que Solr explícitamente pidió que se ejecute. Cada filtro es ejecutado y “cacheado” separadamente, cuando es tiempo de usarlos para limitar el número de resultados devueltos por una query lo hace usando un conjunto de intersecciones. Este es el principal objetivo de esta cache.
- Es usada en algunos casos para “Faceting”; cuando está habilitado el método “TermEnum”, se agrega una entrada en la cache por cada “Term” (termino) evaluado.
Si la opción de configuración “<useFilterForSortedQuery/>” de solrconfig.xml está habilitada (“true”), se utilizará para ordenamiento. Ejemplo basado en el indice generado con los “exampledocs” del trunk de solr:
q=manu:Apple&fq=name:iPod&fq=cat:electronics
q=manu:Belkin&fq=name:iPod&fq=cat:electronics
Los filtros name y cat (“fq”), son almacenadas en este tipo de cache; no varían entre ambas “queries”.
El campo manu de la “query” es variable entre las distintas consultas y por eso no se incluye como una “Filter Query” y será guardado en la “queryResultCache”.
“fieldValueCache”
Guarda los valores de los términos que son de rápido acceso. Se utiliza para el “faceting” sobre campos “multi-valuados” o “tokenizados”.
Es similar a la “FieldCache de Lucene”, se produce una entrada en la cache por cada campo. Utiliza “un-inverted Field”.
“documentCache”
Guarda Documentos Lucene que fueron buscados y traídos del disco rígido.
Esta cache no se puede usar como origen para usar el “autowarm”. En su lugar, puede utilizarse “static warming” que puede forzarse a ser ejecutado cuando un “newSearcher” o “firstSearcher” se va a comenzar.
El tamaño para esta cache debería ser siempre mayor que “<max_results>” * “<max_concurrent_queries>” para asegurar que Solr no tendrá que ir a buscar al disco rígido un documento mientras se está haciendo un pedido. Cuanto más documentos guardemos en esta cache mayor será el consumo de memoria.
Cada Documento en la cache contiene una lista de referencias a los campos. Cuando está habilitado “enableLazyFieldLoading” (enableLazyFieldLoading = true) y hay “documentCache”, los documentos que son traídos desde el “IndexReader” sólo contendrán los campos especificados en “fl”. El resto de los campos serán marcados como “LOAD_LAZY”.
Cuando posteriormente haya un “hit” en la cache sobre esa “uniqueKey”, los campos cargados serán usados directamente, de ser requeridos. En cambio, los campos marcados como “LOAD_LAZY”, serán obtenidos desde el “IndexReader” y los documentos actualizarán sus referencias a estos campos; los cuales no estarán más marcados como “LOAD_LAZY”.
Por lo tanto, el mismo objeto podrá ser reusado para distintos parámetros de “fl”, pero los campos para ese documento crecerá según los parámetros de “fl”.
“User/Generic Caches”
Los usuarios que hayan realizado “plugins” personalizados de Solr para sus aplicaciones podrán configurar caches genéricas que serán mantenidas por Solr; pueden tener opcionalmente “autowarm” si un regenerador personalizado se especifica.
Por ejemplo una cache de usuario podría ser implementada para realizar sugerencias de palabras mal escritas a través de “spellcheckers”; en el siguiente blog se desarrolla el tema “spellchecker”.
“The Lucene FieldCache”
Esta cache no es manejada por Solr, por lo cual no tiene opciones de configuración y se utiliza “static warming”; no puede ser “autowarmed”.
Se usa sobre campos “monovaluados” y que no están “tokenizados”; es decir, campos donde si uno dice el número de documento se puede saber con certeza el valor del termino en ese campo.
Es una cache de bajo nivel que tiene Lucene. La utiliza para acceder eficientemente a todos los valores de un campo en memoria en lugar de ir a buscarlo al disco. Es necesaria para el ordenamiento y puede ser utiliza para realizar “faceting” con Solr, entre otras cosas.
Esta cache almacena por cada campo (“field”) el valor de cada documento en memoria; un vector de tamaño relacionado a la cantidad de documentos indexados.
Si los documentos son del tipo ordinal (“integer”, “long”, etc.) se comparan rápidamente entre ellos para ordenarlos.
Si los documentos son del tipo “string” y se le da el lugar necesario a la cache para que realice las comparaciones entre ellos, la cache será un vector de strings (String[]). Se guardara dentro del vector los términos (“term”) de cada documento por cada campo del índice. Se guardará en un vector la representacion de String[] para buscar en él. El ordenamiento en este caso es mas lento que en los enteros (“integer”) porque hay que comparar strings.
Si los documentos son del tipo “string”, pero no se especifica el lugar necesario para realizar las comparaciones, se generaran dos vectores. El primero contendra todos los terminos únicos (“unique terms”) del indice y el segundo guardará enteros (“integers”) que representan los documento en el indice. En este método el acceso a los datos es más lento, por tener dos vectores desreferencias, pero el ordenamiento se hace sobre enteros en vez de “strings”.
Ejemplo:
[Chevrolet, Dodge, Fiat, Peugeot] [0, 2, 1, 3, 2, 1, 1, 1, 4, 3, 2, 2, 1, 4]
4 terminos únicos 14 documentos
Analizando el segundo vector del ejemplo podemos observar que el documento 0 no coincide con ningún termino del primer vector, el documento 1 contiene el elemento 1 del primer vector (Dodge) y asi continuamos hasta terminar el vector.
Si reemplazamos en el segundo vector los valores del primer vector, a modo de ejemplo y para ser mas representativo el ejemplo, quedaria:
[0, Dodge, Chevrolet, Fiat, Dodge, Chevrolet, Chevrolet, Chevrolet, Peugeot, Fiat, Dodge, Dodge, Chevrolet, Peugeot]
Consideraciones a tener presente
- Caches muy grandes consumen mucha memoria. El tener la mayor cantidad posible de documentos dentro de la cache no significa que sea lo mejor o presente el mejor desempeño.
- En caso de tener “queries” concurrentes (al mismo tiempo) se sugiere poner el tamaño de cache a 10 * “cantidad de documentos que se devuelven”. Esto es para garantizar que cada “query” tenga el espacio necesario en cache al momento de utilizarla.
- Verificar siempre de la pagina de administración de Solr la “cache statistics” los valores mostrados, estos nos guiaran a obtener la mejor performance.
- “Hit rate” muy bajos significan que la cache está perjudicando el desempeño en vez de beneficiarlo.
- “Eviction rate” muy altos pueden significar que la cache es muy chica y quizás también este perjudicando el desempeño de Solr.
Conclusión
Se debe realizar un gran análisis para determinar si es necesario o no utilizar las caches; observar los resultados en “statistics” para ello.
Si bien las caches tienen como objetivo mejorar el desempeño de Solr, una mala utilización de las mismas puede ser más perjudicial que beneficioso; consumo innecesario de recursos, realizar “autowarming” sin sentido, etc.
Agradecimientos:
A Tom, Emma y Diego por ayudarme a realizar este post.
Referencias:
Buena info para tener en cuenta.
ResponderEliminarGracias!