Lenguaje de consulta de objetos para caché distribuida

Autor: Iqbal Kan

NCache le permite crear una caché distribuida escalable en el nivel medio para que pueda reducir los costosos viajes a la base de datos y mejorar en gran medida el rendimiento de su aplicación. También mejora la escalabilidad de la aplicación porque puede encontrar datos de uso frecuente en esta caché altamente escalable en lugar de un único servidor de base de datos que no puede escalar muy bien.

Por lo general, su aplicación usa un caché como Hashtable donde todo se almacena en función de una clave y debe tener esta clave para obtener un elemento. Esto es como tener una base de datos relacional donde solo puede usar la clave principal para encontrar datos. Esto funciona bien en muchas situaciones, pero en una aplicación compleja de la vida real, su aplicación a menudo necesita encontrar datos basados ​​en atributos distintos a la clave principal. Y, dado que está guardando una gran cantidad de sus datos en el caché, sería muy útil si también pudiera buscar en el caché de esta manera. NCache proporciona exactamente esa facilidad.

En este artículo, discutiré cómo NCache la consulta de objetos funciona.

Búsqueda de caché que devuelve claves

NCache proporciona un lenguaje de consulta de objetos (OQL) para permitirle buscar en la memoria caché. Debe realizar llamadas a la API y especificar una búsqueda basada en este OQL para obtener una colección de objetos del caché. Esto es lo que necesita usar en su aplicación .NET para consultar el caché.

public class Program
{
    public static void Main(string[] args)
    {
        NCache.InitializeCache("myReplicatedCache");
        String query = "SELECT NCacheQuerySample.Business.Product WHERE this.ProductID > 100";
        // Fetch the keys matching this search criteria
        ICollection keys = NCache.Cache.Search(query);
        if (keys.Count > 0)
        {
            IEnumerator ie = keys.GetEnumerator();
            while (ie.MoveNext())
            {
                String key = (String)ie.Current;
                Product prod = (Product)NCache.Cache.Get(key);

                HandleProduct(prod);
                Console.WriteLine("ProductID: {0}", prod.ProductID);
            }
        }
        NCache.Cache.Dispose();
    }
} 

El código anterior busca en el caché y devuelve una colección de claves. Luego, itera sobre todas las claves devueltas y obtiene individualmente los elementos correspondientes almacenados en la memoria caché. El beneficio de este enfoque es que la consulta no devuelve automáticamente una gran cantidad de datos y, en cambio, solo devuelve claves. Luego, la aplicación cliente puede determinar qué claves desea recuperar. El inconveniente de este enfoque es que si va a buscar la mayoría de los elementos del caché de todos modos, terminará haciendo muchos viajes al caché. Y, si se distribuye el caché, esto eventualmente puede volverse costoso. Si ese es el caso, puede realizar una búsqueda que devuelva las claves y los elementos juntos.

Búsqueda en caché de elementos devueltos

Como ya ha visto, un simple Cache.Search(...) devuelve una colección de claves. Sin embargo, si tiene la intención de obtener todos o la mayoría de los elementos almacenados en caché asociados con estas claves, entonces Cache.Search (...) no es una forma muy eficiente de buscar en el caché. La razón es que primero harás una llamada para hacer la búsqueda. Luego, realizará una serie de llamadas para obtener los elementos asociados con cada tecla. Esto puede convertirse en una operación muy costosa. En estas situaciones, es mejor buscar todas las llaves y elementos en una sola llamada. A continuación se muestra el ejemplo haciendo precisamente eso.

public class Program
{
    public static void Main(string[] args)
    {
        NCache.InitializeCache("myReplicatedCache");
        String query = "SELECT NCacheQuerySample.Business.Product WHERE this.ProductID > 100";
        // Fetch the keys matching this search criteria
        IDictionary dict = NCache.Cache.SearchEntries(query);
        if (dict.Count > 0)
        {
            IDictionaryEnumerator ide = dict.GetEnumerator();
            while (ide.MoveNext())
            {
                String key = (String)ide.Key;
                Product prod = (Product)ide.Value;
                HandleProduct(prod);
                Console.WriteLine("Key = {0}, ProductID: {1}",
                key, prod.ProductID);
            }
        }
        NCache.Cache.Dispose();
    }
}

El código anterior busca en la memoria caché y devuelve un diccionario que contiene claves y valores. De esta manera, todos los datos basados ​​en los criterios de búsqueda se recuperan en una llamada a NCache. Esta es una forma mucho más eficiente de obtener todos los datos del caché que hacer Cache.Search().

Indexación de atributos de búsqueda

Ten en cuenta que NCache requiere que todos los atributos de búsqueda estén indexados. Esto se debe a que sin indexación, NCache tendría que recorrer todo el caché para encontrar los elementos que el usuario está buscando. Y esa es una operación muy costosa con el potencial de ralentizar todo el caché y deshacer la razón principal por la que la gente NCache, es decir, para aumentar el rendimiento y la escalabilidad de sus aplicaciones.

NCache proporciona su propio mecanismo de indexación. Puede identificar los objetos en sus ensamblajes .NET que desea indexar. Luego, cuando agrega datos a NCache, verifica si está agregando estos objetos. Y, si es así, utiliza .NET Reflection para extraer datos de los atributos indexados y crea su índice interno. Luego, cuando consulta el caché con Cache.Search() o Cache.SearchEntries(), NCache utiliza estos índices para encontrar rápidamente los objetos deseados y te los devuelve.

Indexación de atributos de objetos antes de iniciar la caché
Figura 1: Indexación de atributos de objetos antes de iniciar la caché

Tenga en cuenta que cada vez que especifica índices en atributos de objetos, aumenta un poco el tiempo de procesamiento para las operaciones Agregar, Insertar y Eliminar. Sin embargo, la operación Get no se ve afectada.

Consulta de diferentes topologías de almacenamiento en caché

Aunque el comportamiento de búsqueda desde la perspectiva de la aplicación cliente es el mismo, independientemente de las topologías de almacenamiento en caché que esté utilizando, las funciones internas de la búsqueda varían de una topología a otra. Por ejemplo, si está realizando una búsqueda en un caché replicado, su búsqueda se realiza completamente en el servidor de caché desde el que inició esta búsqueda. Esto se debe a que todo el caché está disponible allí. Así es como se ejecuta una consulta en una caché replicada.

La consulta se ejecuta localmente en un servidor
Figura 2: Query se ejecuta localmente en un servidor

Sin embargo, si tiene una topología de almacenamiento en caché particionada o de réplica particionada, no todos los datos residen en un solo nodo de caché en el clúster. En esta situación, el servidor de caché donde se inicia la consulta envía la consulta a todos los demás servidores del clúster y también la ejecuta localmente. Luego, la consulta se ejecuta en todos los servidores en paralelo y sus resultados se devuelven desde todos los nodos a este nodo del servidor de origen. Este nodo del servidor luego combina todos los resultados (hace una "unión") y los devuelve al cliente. A continuación se muestra un diagrama que muestra todo esto.

La consulta se ejecuta en paralelo en todos los nodos del servidor
Figura 3: la consulta se ejecuta en paralelo en todos los nodos del servidor

Sus ensamblados .NET

In NCache, la indexación se realiza en los nodos del servidor. Sin embargo, NCache utiliza .NET Reflection en el extremo del cliente para extraer los valores de los atributos de los objetos y los envía al servidor. Por lo tanto, sus ensamblados .NET que contienen la definición del objeto solo se requieren en el extremo del cliente donde reside su aplicación. Ya sea que esté ejecutando en modo InProc o OutProc, sus ensamblajes deben estar en un directorio donde su aplicación cliente pueda acceder a ellos.

Además, NCache también es compatible con el lenguaje de consulta de objetos para clientes Java.

Sintaxis del lenguaje de consulta

NCache admite un lenguaje similar a SQL llamado Object Query Language (OQL). Este lenguaje tiene la siguiente sintaxis en NCache.

SELECT NCacheQuerySample.Business.Product WHERE this.ProductID > 100;

SELECT NCacheQuerySample.Business.Product WHERE (this.ProductID != 100 AND this.ProductID <= 200);

SELECT NCacheQuerySample.Business.Product WHERE (this.ProductID == 150 OR this.ProductID == 160);

SELECT NCacheQuerySample.Business.Product WHERE this.ProductID IN (1, 4, 7, 10);

SELECT NCacheQuerySample.Business.Product WHERE this.ProductID NOT IN (1, 4, 7, 10);

SELECT NCacheQuerySample.Business.Employee WHERE this.HiringDate > DateTime.now;

SELECT NCacheQuerySample.Business.Employee WHERE this.HiringDate > DateTime ('01/01/2007');

SELECT NCacheQuerySample.Business.Employee WHERE this.Hired = true;

Puede combinar varias expresiones junto con AND y OR y mediante paréntesis anidados. La gramática completa de la sintaxis del lenguaje de consulta se especifica a continuación.

<Query>                 ::= SELECT <ObjectType> 
                          | SELECT <ObjectType> WHERE <Expression>

<Expression>            ::= <OrExpr>

<OrExpr>                ::= <OrExpr> 'OR' <AndExpr>
                          | <AndExpr>

<AndExpr>               ::= <AndExpr> 'AND' <UnaryExpr>
                          | <UnaryExpr>

<UnaryExpr>             ::= 'NOT'  <CompareExpr>
                          | <CompareExpr>

<CompareExpr>           ::= <Atrrib> '='  <Value>
                          | <Atrrib> '!=' <Value>
                          | <Atrrib> '==' <Value>
                          | <Atrrib> '<>' <Value>
                          | <Atrrib> '<'  <Value>
                          | <Atrrib> '>'  <Value>
                          | <Atrrib> '<=' <Value>
                          | <Atrrib> '>=' <Value>
                          | <Atrrib> 'IN' <InList>
                          | <Atrrib> 'NOT' 'IN' <InList>
                          | '(' <Expression> ')'

<Atrrib>		::= <ObjectValue>                                                    

<Value>                 ::= '-' <NumLiteral> 
                          | <NumLiteral> 
                          | <StrLiteral>
                          | 'true'
                          | 'false'
                          | <Date>

<Date>                  ::= 'DateTime' '.' 'now'
                          | 'DateTime' '(' StringLiteral ')'

<StrLiteral>		::= StringLiteral
			  | 'null'  

<NumLiteral>            ::= IntegerLiteral 
                          | RealLiteral

<ObjectType>            ::= '*' 
                          | <Property>

<Property>		::= <Property> '.' Identifier
			  | Identifier

<ObjectValue>           ::= Keyword '.' Identifier              

<InList>                ::= '(' <ListType> ')'

<ListType>              ::= <NumLiteralList>
                          | <StrLiteralList>
                          | <DateList>

<NumLiteralList>        ::=  <NumLiteral> ',' <NumLiteralList>
                          | <NumLiteral>

<StrLiteralList>        ::= <StrLiteral> ',' <StrLiteralList>
                          | <StrLiteral>

<DateList>              ::= <Date> ',' <DateList>
                          | <Date> 

Conclusión

Como has visto, NCache hace que sea muy simple consultar el caché distribuido. Esta es una característica poderosa que le permite usar su caché de una manera más significativa y encontrar cosas en él más fácilmente. Échale un vistazo.


Escrito por: Iqbal Khan trabaja para Alachisoft , una empresa de software líder que ofrece soluciones de almacenamiento en caché distribuido .NET y Java, mapeo O/R y optimización de almacenamiento de SharePoint. Puedes localizarlo en iqbal@alachisoft.com.

© Copyright Alachisoft 2002 - Todos los derechos reservados. NCache es una marca registrada de Diyatech Corp.