Integración de servicios de datos distribuidos y su utilización eficiente en computadoras multi-núcleo Alejandro Tamayo Castillo, Darian Serrano Ferrer y Miguel Katrib Grupo WEBOO, Facultad de Matemática y Computación, Universidad de la Habana, Cuba {tamayo, darian.serrano, mkm}@matcom.uh.cu http://www.matcom.uh.cu http://www.weboo.matcom.uh.cu http://www.weboomania.com Abstract. This paper describes an architecture based on web standards (HTTP, SOAP, WS-*) to integrate and directly query heterogeneous distributed data sources. This approach permits the efficient execution of a distributed query which includes join, where, order by and select operations, enabling fail-over, error-handling and high-availability, taking advantage of multi-core capacity in the client computer. 1 Introducción Un sistema heterogéneo y distribuido de base de datos es una entidad que integra y permite consultar diferentes fuentes de datos hospedadas en diferentes servidores pero bajo una misma interfaz. Existen varios productos, como por ejemplo Oracle, que brindan una solución para la integración de diferentes sistemas de bases de datos en una misma base de datos permitiendo la ejecución y optimización de consultas distribuidas [1]. Sin embargo, en lugar de atarse a protocolos propios y específicos de una tecnología es conveniente aprovechar la heterogeneidad y distribución que nos brinda la web utilizando estándares tales como SOAP, WS-* o REST como interfaz común para interactuar con fuentes de datos que pueden ser independientes. Vale señalar que esto no quiere decir que hay que descartar el uso de servidores como Oracle, SQL Server o MySQL como soporte para el almacenamiento de datos, sino que multiplica su utilización al "subirlas a la nube" a través protocolos estándares. Si bien es útil publicar datos en la web a través de protocolos estándares, más útil sería poder consultar estos datos de manera distribuida, sobrepasando las fronteras de cada servicio de datos, tratando a la web como un gran sistema de base de datos. Un problema que se presenta en este escenario es cómo realizar uniones (JOIN) de manera eficiente entre varios servicios de datos, que no tienen por qué guardar una relación explícita entre ellos ya que fueron diseñados para trabajar de manera independiente. El aporte principal del presente trabajo es proponer una arquitectura para consultar diferentes fuentes de datos de manera distribuida utilizando protocolos estándares. En la sección 2 se exponen aquellos aspectos que motivaron el desarrollo de este trabajo. Posteriormente en la sección 3 se analizan algunos resultados de otros 2 Alejandro Tamayo Castillo, Darian Serrano Ferrer y Miguel Katrib trabajos anteriores. En la sección 4 se introduce una arquitectura inicial con un enfoque orientado a servicio y más adelante en la sección 5 se muestra cómo cambiando éste a un enfoque orientado a recursos se logra obtener mejores resultados. Siguiendo el presente enfoque se ha implementado un prototipo del que en la sección 6 se muestran algunos resultados experimentales. 2 Motivación La integración de datos provenientes de fuentes heterogéneas, pero bajo una misma interfaz de consulta, es un deseo común lo mismo en escenarios comerciales, gubernamentales, científicos, por lo que ha sido objeto de estudio por diferentes autores. En un escenario académico por ejemplo, la creación del perfil de un estudiante pudiera ser un proyecto a modelar utilizando una arquitectura distribuida, ya que pudieran existir fuentes de datos heterogéneas como el listado de los estudiantes, el listado de cursos, el registro de actividades extracurriculares, las evaluaciones docentes, los premios, etc, que pudieran estarse gestionando en sistemas independientes. Un ejemplo a gran escala podría ser a nivel de país publicar la información de los ciudadanos: donde cada persona además de un DNI, tiene historial médico, laboral, financiero, entre otros y que evidentemente (y por suerte) son gestionados por estructuras organizacionales independientes. Los objetivos que motivan el presente trabajo son el de minimizar los requerimientos computacionales, lograr una arquitectura simple (basada en estándares), poder integrar fuentes de datos heterogéneas y ejecutar de manera eficiente consultas distribuidas que permitan realizar uniones entre datos provenientes de diferentes fuentes. 3 Trabajos Relacionados Con la Web 2.0 han surgido los sistemas llamados NoSQL, diseñados con el objetivo de ser escalables horizontalmente, a diferencia de los DBMSs tradicionales basados en SQL, donde la escalabilidad se lograba añadiendo más recursos de cómputo al servidor [2]. Estos sistemas no son un reemplazo para los DBMSs tradicionales ya que a pesar de que ganan en escalabilidad horizontal, tienen limitantes en cuanto a complejidad, rendimiento, consistencia y confiabilidad; aunque en algunos escenarios específicos pueden brindar mayores beneficios [3]. Por tanto, una solución intermedia, pudiera ser mantener los DBMSs como soporte para el almacenamiento de datos y así obtener consistencia y confiabilidad y publicar los datos a través de una capa intermedia (Middleware) que utilice un protocolo de comunicación estándar que permita lograr la escalabilidad horizontal requerida. Además, no siempre se pueden homogeneizar las fuentes de datos producto de estar en sistemas legados, por lo que la alternativa a seguir es integrar y no migrar el sistema a una nueva plataforma. Una arquitectura orientada a servicio (SOA) basada en SOAP ha demostrado ser un buen soporte para creación confiable y eficiente de servicios distribuidos [4]. La integración de servicios no es un problema nuevo y de hecho existen soluciones Integración de servicios de datos distribuidos y su utilización eficiente en computadoras multi-núcleo 3 utilizando lenguajes como BPEL sobre un Enterprise Service Bus (ESB) [5]. Sin embargo, son muchos los factores a considerar cuando se desean consultar grandes volúmenes de datos, sobre todo si se encuentran distribuidos en fuentes de datos heterogéneas ya que la optimización y ejecución eficiente de consultas resulta ser un aspecto decisivo. La idea de mantener fuentes de datos autónomas, heterogéneas, distribuidas y en constante cambio que puedan ser descubiertas dinámicamente a través de la web, ha sido objeto de estudio anteriormente [6]. Uno de los retos actuales para lograr el ideal de computación orientada a servicio [7] es el de crear una infraestructura que permita consultar dinámicamente, mediante una interfaz uniforme, fuentes de datos distribuidas y heterogéneas. Un ejemplo interesante de integración de datos es WS-Aggregation [8] que permite la agregación y consulta de servicios de datos XML (por ejemplo fuentes RSS) y que utiliza un lenguaje propio nombrado WAQL basado en XQuery. Presenta una arquitectura distribuida basada en “Concentradores”, que son las entidades que se encargan de conectarse a las diferentes fuentes y extraer de manera autónoma los datos, homogenizando el resultado para que posteriormente se pueda consultar de manera uniforme. La propuesta del presente trabajo, aunque similar a WS-Aggregation desde el punto de vista de arquitectura, difiere de éste en dos aspectos fundamentales. En primer lugar, los datos no se extraen de la fuente de datos para consultarlos posteriormente, sino que se consultan directamente de manera nativa mediante el lenguaje del proveedor de datos. Esto permite el manejo de grandes volúmenes de datos sin duplicar la información y además aprovecha las facilidades de consultas eficientes en el proveedor de datos. El segundo aspecto distintivo de nuestra propuesta es que usa un lenguaje similar a SQL para expresar una consulta global que puede involucrar múltiples fuentes de datos y que pueden representar diferentes entidades. Luego el sistema se encargará de convertir dicha consulta global en múltiples consultas locales en el lenguaje nativo para cada fuente de datos, permitiendo así una ejecución eficiente de las consultas para cada fuente y una composición del resultado que explota las características multi-núcleo de las computadoras modernas (utilizando para ello la técnica hybrid shipping para el procesamiento de la consulta distribuida [9]). Visto de otro modo, define un modelo virtual de datos que se puede consultar como un todo, similar a como se haría en una base de datos local (aplicar selecciones, filtros, uniones), pero en la práctica este modelo virtual estará compuesto por fuentes de datos independientes y distribuidas. La implementación del prototipo de prueba se llevó a cabo con C# .NET y utilizó Parallel LINQ [10] para la ejecución en paralelo de consultas expresadas en el lenguaje LINQ [11]. 4 Arquitectura inicial La base de la arquitectura propuesta está en el servicio de datos. Éste es una aplicación que se conecta directamente a la base de datos que se quiere acceder y 4 Alejandro Tamayo Castillo, Darian Serrano Ferrer y Miguel Katrib publica parcial o totalmente su contenido mediante dos enfoques diferentes; uno más clásico a través de un servicio web SOAP (WS-*) y otro a través de un servicio web RESTFul que implementa el protocolo OData [12]. Cada uno de estos enfoques tiene ventajas y desventajas que serán discutidas en este trabajo. Independientemente de cuál variante se utilice, ambos servicios web se pueden descubrir mediante la utilización del estandar WS-Discovery [13]. Las fuentes de datos podrán ser heterogéneas; lo mismo una base de datos relacional, que ficheros XML almacenados en un gestor de contenidos, pero la gracia es que se accederá de manera homogénea a través del servicio de datos. 4.1 Arquitectura orientada a servicios (SOA) Cada servicio de datos implementará una misma funcionalidad común que ofrece las operaciones disponibles y los tipos de datos involucrados en las mismas. Dicha información se puede acceder mediante WDSL para la creación dinámica de proxies que permiten la comunicación con el servicio de datos desde una determinada plataforma y lenguaje como es el caso de C# y .NET u otra que implemente estos estándares SOAP, WS-*). Varios servicios de datos pudieran implementar la misma funcionalidad (caracterizada con un tipo interface en .NET) lo que permitirá ejecutar de manera distribuida una misma operación entre dichos servicios combinando los resultados. Como ejemplo utilizaremos un modelo de datos que incluye un listado de personas y un listado de logros alcanzados por una persona (premios, títulos académicos, puestos de trabajo ejercidos). Lo interesante a destacar en este ejemplo es que existe una relación semántica de uno a varios entre las entidades persona y logro, además de que las listas de personas y logros pueden ser de gran tamaño por lo que podría haber que distribuirlos entre varios servicios de datos. Una posible definición de estos tipos (en este caso utilizando Windows Communication Foundation y C#) pudiera ser: [ServiceContract] public interface IPersonService { [OperationContract] Person[] GetPeople(int skip, int count); } [ServiceContract] public interface IBadgeService { [OperationContract] Badge[] GetBadgeByPersonId(int PersonID); } Como se puede apreciar la operación GetPeople recibe dos parámetros enteros que tienen como objetivo "paginar" la cantidad de personas que se van mostrando ya que sería impracticable devolver millones de elementos a través de la red. El tipo Person contendría el nombre, el apellido, la edad y otros datos personales. En el caso de la operación GetBadgeByPersonId se le pasa un identificador de la persona y se recibe el listado de logros de esta persona (el tipo Badge contendría de Integración de servicios de datos distribuidos y su utilización eficiente en computadoras multi-núcleo 5 manera básica un campo Description con la información del logro y un campo Date con la fecha en que ocurrió dicho logro). Entonces se tendría un grupo de servicios de datos implementando el tipo IPersonService y otro grupo implementando IBadgeService de manera tal que la información quede distribuida en ambos grupos. Fig. 1. Topología en un entorno LAN (izquierda) donde el descubrimiento se realiza vía UDP (líneas intermitentes) y sobre Internet (Derecha) donde el descubrimiento se realiza con la ayuda de un repositorio HTTP que registra los servicios de datos. Para acceder a estos servicios de datos primero habría que realizar un descubrimiento dinámico, ya que no se tiene a priori información de disponibilidad ni ubicación. La topología de red juega un papel importante en este paso. En una red de área local (LAN), al iniciar un cliente se pueden realizar anuncios (Broadcast) que llegarían a los servicios de datos y estos a su vez responderían con anuncios que contengan la dirección URL en que se alcanza el servicio web propiciando un descubrimiento Ad-Hoc. En el caso de Internet, no es posible realizar este tipo de anuncios por problemas de seguridad por lo que la alternativa sería utilizar un servicio web adicional que actúe de “Repositorio”, recibiendo los pedidos de ambas partes (clientes y servicios) para mantener un mapa con la dirección URL de cada servicio y su disponibilidad (Fig. 1). De esta manera es que funciona WS-Discovery que se puede configurar en modo Ad-Hoc para su uso en una LAN y en modo Administrado para su uso en Internet. El cliente a priori solo tiene que conocer el tipo que expresa los servicios que quiere consultar. La dirección URL donde se encuentra el servicio, así como la versión del protocolo SOAP que se está aplicando, se descubren dinámicamente. 6 Alejandro Tamayo Castillo, Darian Serrano Ferrer y Miguel Katrib 4.2 Ejecutando operaciones distribuidas Dado un conjunto de servicios que implementan una misma interfaz o contrato, se puede ejecutar una misma operación (IPersonService.GetPeople por ejemplo) de varias formas1 atendiendo a objetivos diferentes: 1. Ejecutar la operación en algún servicio y devolver el resultado 2. Ejecutar la operación en cada uno de los servicios y devolver el conjunto de resultados 3. Ejecutar la operación en cada uno de los servicios y devolver los resultados mezclados El primer caso se resuelve relativamente simple. Para ello primero se realiza una ordenación aleatoria de los servicios disponibles (previamente descubiertos) y se ejecuta en orden el pedido hasta que se obtenga un resultado (Round-Robin). En el mejor de los casos el primer servicio nos daría una respuesta, pero en caso de que éste falle, o se encuentre fuera de servicio, se continúa con el siguiente hasta haber intentado con todos los servicios disponibles. El objetivo de la ordenación aleatoria es lograr un balance de carga. Los restantes casos son un poco más complejos de tratar. La diferencia entre los dos últimos está en que uno devuelve un objeto iterable con los resultados de cada llamada (por ejemplo un array Person[] para el método GetPeople) por lo que para su fácil consumo sería conveniente contar con un método que mezcle cada uno de los resultados iterables en un único iterable. Está claro que el programador pudiera obtener manualmente el listado de servicios y ejecutar las operaciones una a una por cada servicio, pero ésta no es la mejor manera de hacerlo porque en este caso el tiempo de ejecución total sería igual a la sumatoria del tiempo que demore cada servicio. Pero como el tiempo de respuesta no es el mismo para cada servicio, si en el momento en que se espera por la respuesta de un servicio “lento” se realizasen otras llamadas a servicios más rápidos se estaría mejorando el tiempo de ejecución total. Es muy difícil encontrar una secuencia óptima de llamadas ya que en principio no se conoce a priori el tiempo de respuesta de cada operación para cada servicio. Por otra parte, si se realizan todas las llamadas a la misma vez puede saturarse el enlace o bloquearse el cliente debido a la cantidad de información resultante que tendría que procesar. Por último habría que manejar las distintas excepciones que se podrían lanzar (timeout, conexión rechazada, error de transmisión, etc). La solución adoptada fue la de implementar el patrón productor-consumidor en el que cada llamada a servicio se convierte en un productor que almacena el resultado en una colección con soporte concurrente y de la cual luego el consumidor obtiene el resultado al ser ésta una colección iterable (Fig. 2). A lo sumo se lanza una cantidad p productores en paralelo (más abajo se explica cómo se determina este valor p) sólo cuando existe un consumidor que le ha solicitado un elemento a la colección intermedia. Cuando un productor finaliza, si existe un consumidor esperando (debido 1 Se sale del alcance de este trabajo la composición de operaciones y la ejecución de flujos de trabajos. Integración de servicios de datos distribuidos y su utilización eficiente en computadoras multi-núcleo 7 a que la colección intermedia está vacía) se lanza entonces el siguiente productor manteniendo de este modo, siempre que sea posible, un número p de productores en ejecución. El proceso de añadir el resultado devuelto por el productor (por ejemplo Person[] para el método GetPeople) y el contenido de la colección intermedia, se realiza inmediatamente, por lo que puede suceder que varios productores finalicen al mismo tiempo y resultado combinado quedaría entremezclado. El valor de p puede impactar en la eficiencia de la ejecución. Este número debe calcularse en dependencia de la cantidad de hebras disponibles en el sistema donde se ejecuta el cliente (lo cual se aprovecha cuando se tiene más de un núcleo de procesamiento). Si p es pequeño se estarían sub-utilizando las capacidades de cómputo, pero si es muy grande habría más hebras que capacidad real, por lo que se perdería tiempo en hacer cambio de contexto y el consumidor tendría menos tiempo para procesar la colección de elementos. En el prototipo implementado en .NET se utilizó la heurística de paralelismo incluida en el Task Parallel Library (TPL) la cual calcula dinámicamente dicho valor [14]. Fig. 2. Ejecución en paralelo para p=3 de seis productores (llamada a servicio) que una vez que finalizan la ejecución publican el resultado en una colección intermedia para su posterior consumo. Note que solo hay a lo sumo tres productores ejecutando a la vez, A, B, C, al terminar B siguen A, C, D, al terminar A siguen C, D, E y por último E y F. Otro detalle interesante es que si el consumidor solicita solamente k elementos, y estos se obtienen con la ejecución de un subconjunto de productores, entonces no se realiza la ejecución de los restantes productores. 4.3 Limitantes del enfoque orientado a servicios para consulta de datos distribuidos Suponga que quiere obtener las k primeras personas ordenadas por el nombre cuyo apellido sea “Rodriguez”. En un entorno cliente-servidor donde se consulta un único servicio, una solución sería definir una operación con signatura Person[] GetPeopleByLastName(string lastname, int count) que devuelva el resultado de ejecutar una consulta SQL parametrizada como la siguiente: 8 Alejandro Tamayo Castillo, Darian Serrano Ferrer y Miguel Katrib SELECT TOP @count * FROM People WHERE Lastname = @lastname ORDER BY Name El primer problema con esta solución es que hay que ser algo clarividente para definir a priori todas las operaciones que se vayan a utilizar. Si se quiere filtrar u ordenar por otro criterio no previsto, habría que modificar el servicio para añadirles dichas operaciones. Por otra parte, es un inconveniente la cantidad de operaciones que habría que definir para que un modelo de datos real sea útilmente consultable. Por supuesto, siempre se podrían devolver a la aplicación cliente los datos de todas las personas y que ésta sea la que realice las operaciones de filtrado y ordenación, pero esto es impracticable debido al gasto de ancho de banda y al tiempo de transmisión requerido si la base de datos es de gran volumen. En un entorno distribuido con m servicios disponibles, si lo que finalmente decide el cliente es quedarse con las k primeras personas que cumplan las condiciones de la consulta, habría que transmitir al cliente como mínimo m*k personas (las k primeras de cada uno los m servicios disponibles) para luego reordenarlas y quedarse sólo con las k primeras globalmente. Note que en este caso se estarían transmitiendo innecesariamente los datos de k*(m-1) personas lo que impacta en el rendimiento y en el consumo de ancho de banda. Debido a estas dificultades se decidió investigar una alternativa mucho más eficiente basada en un enfoque orientado a recursos. 5 Mejorando la arquitectura con un enfoque orientado a recursos (ROA) En lugar de tener que determinar a priori las operaciones para la publicación de datos a través de una arquitectura SOA, se podría usar un enfoque que permita a posteriori diseñar y ejecutar una consulta basándose solamente en las entidades expuestas y no en la ejecución de operaciones predefinidas. Para ello tendría que ser posible serializar la consulta y transmitirla del cliente hacia el servidor, ejecutarla en el servidor y luego consumir los resultados. En principio si la fuente de datos fuese SQL podría utilizarse dicho lenguaje para expresar la consulta, pero no en un contexto en que las fuentes de datos son heterogéneas y no necesariamente bases de datos relacionales. En un trabajo previo [15] analizamos cómo modelar, serializar y ejecutar dinámicamente una consulta de datos sobre repositorios de datos heterogéneos. OData es un protocolo abierto que permite consultar y actualizar datos utilizando la Web como transporte basándose en estándares como AtomPub, JSON y el propio HTTP. Este protocolo cuenta con implementaciones en Java, PHP, Ruby y en la propia familia .NET (.NET Framework, Silverlight y Windows Phone 7) y es utilizado en aplicaciones por grandes compañías como SAP y EastBank. En OData la consulta se codifica en la URL del pedido y el resultado se devuelve como un Feed de AtomPub [16]. Es posible establecer filtros, definir una ordenación para los resultados, paginar y proyectar pero no se pueden realizar uniones (JOIN). En los lenguajes que incorporan LINQ existe un proveedor que convierte una consulta LINQ en un pedido OData para simplificar la modelación de la consulta. Integración de servicios de datos distribuidos y su utilización eficiente en computadoras multi-núcleo 9 La presente propuesta no impone restricciones sobre las fuentes de datos (salvo la trivial de que se publiquen a través de OData) sino que las impone sobre el cliente que es en donde se lleva a cabo el trabajo de serialización y ejecución en paralelo de las múltiples consultas requeridas para componer y satisfacer una única consulta distribuida. No obstante, las ideas que se muestran a continuación para el procesamiento de la consulta distribuida pueden implementarse, con más o menos trabajo, en otras plataformas en dependencia de las bondades de cada una. 5.1 Procesamiento de una consulta distribuida El procesamiento y ejecución de una consulta distribuida es un problema complejo a pesar de los beneficios en cuanto a escalabilidad horizontal que se obtienen al tener los datos distribuidos en varias máquinas en una red. Esto se debe a varios factores tales como el tamaño del sistema distribuido, la heterogeneidad de las fuentes de datos, el dinamismo del sistema y la limitante de la velocidad de la red que, por muy rápida que sea, hasta el momento no compite con la que se obtiene localmente en un mismo servidor. Es por ello que resulta de interés el estudio de diferentes técnicas que permitan el procesamiento eficiente de consultas distribuidas que incluyen filtros, uniones, proyección y ordenamiento. Estas técnicas básicamente se mueven entre dos extremos: ejecutar la consulta en el servidor y recibir el resultado procesado o recibir todos los datos y ejecutar la consulta en el cliente. Es evidente que ejecutar siempre la consulta en el cliente es impracticable producto del costo de transmisión. Lo ideal sería ejecutar las consultas en el servidor y procesar los resultados, pero existen consultas que simplemente no se pueden "distribuir", a menos que se conozca información adicional sobre los datos almacenados. Cuando se habla de procesamiento eficiente en un entorno distribuido, se refiere a minimizar el costo de transmisión que a fin de cuentas es el que más impacta el rendimiento. Lograr esta eficiencia es usualmente complejo, ya que la mayoría de los problemas de optimización de consultas distribuidas son NP-Hard [17]. Por tanto, en este trabajo el enfoque que se ha seguido es el de analizar los casos comunes de consulta y brindar una heurística con ciertas restricciones que aunque no necesariamente logra el óptimo, se ha comprobado que funciona aceptablemente para el problema que se quiere resolver. 5.2 Análisis semántico del árbol de sintaxis abstracta que representa a la consulta y ejecución distribuida Para aplicar las heurísticas de optimización, el enfoque adoptado parte de que la consulta distribuida se exprese en el cliente mediante LINQ para luego ejecutarla utilizando un proveedor LINQ personalizado que es quien se encarga de transformar la consulta en pedidos OData y luego componer los resultados. La idea es que en dependencia de la forma de la consulta, su ejecución se realizará completamente en el servidor, en el cliente o de forma híbrida. Considere el ejemplo siguiente: 10 Alejandro Tamayo Castillo, Darian Serrano Ferrer y Miguel Katrib query = (from person in bus.GetDataServices<Person>() where person.Name.StartsWith("A") orderby person.Name join badge in bus.GetDataServices<Badge>() on person.Id equals badge.PersonId select new {person.Name, badge.Description}) .Take(25); En este caso, se está solicitado el descubrimiento automático de todos los servicios de datos disponibles que publiquen las entidades Person y Badge. De todas las entidades de tipo Person que se encuentran distribuidas en diferentes servicios de datos, solo interesan aquellas que cumplan cierta condición (en este ejemplo que el nombre empiece con “A”) y que se devuelvan ordenadas por el nombre. Luego se realiza un inner join con las entidades de tipo Badge relacionadas y finalmente se proyectan los datos en un nuevo tipo que contenga las propiedades Name y Description. Pero la consulta también indica que de todos los resultados nos basta con los 25 primeros. Esta consulta ejecutará de manera eficiente si la cantidad de elementos que se transmiten por la red para lograr el resultado es linealmente proporcional a la cantidad solicitada (en el ejemplo 25). Fig. 3. Relación entre los ejecutores y cada parte de la consulta. El primer paso en el procesamiento de la consulta distribuida consiste en analizar el árbol de sintaxis abstracta (AST) que representa a la consulta y determinar las partes que pueden ser ejecutadas del lado del servidor. LINQ permite acceder al AST de una consulta y transformarlo dinámicamente según convenga. Además permite ejecutar una consulta en el cliente utilizando el proveedor LINQ to Objects si las fuentes de datos implementan la interfaz IEnumerable<T>. La estrategia que se siguió para lograr la eficiencia en la ejecución de la consulta distribuida fue la de analizar el árbol de expresión y sustituir aquellas ramas que puedan ejecutarse del lado del servidor por objetos especiales que se les llamó Ejecutores que implementan la interfaz IEnumerable<T> y son capaces de ejecutar dicha rama de forma remota del lado del servidor. Por ahora se han creado solo dos tipos de ejecutores: el SingleQueryExecutor y el JoinExecutor. El primero se encarga de ejecutar de forma remota consultas que involucren un solo tipo de entidad. En la Fig. 3 se muestra la relación de los ejecutores y la consulta que se está tomando como ejemplo. El SingleQueryExecutor (Fig. 4) transforma la consulta en pedidos OData y los ejecuta en paralelo en los servicios de datos disponibles siguiendo el proceso de ejecución en paralelo que se mostró en 4.2. Integración de servicios de datos distribuidos y su utilización eficiente en computadoras multi-núcleo 11 Fig. 4. Funcionamiento del ejecutor SingleQueryExecutor Los datos no se devuelven inmediatamente sino que se paginan y se van devolviendo página a página según se vayan solicitando para minimizar el costo de transmisión. La idea es tratar de transmitir la misma cantidad de elementos; es similar tener 10 servicios con tamaño de página 10 que 2 servicios con tamaño de página 50 ya que se transmitirían en el pedido inicial 100 elementos. Cuando se establece una solicitud de ordenación (order by) cada servicio de datos ordena su subconjunto, pero el orden global hay que calcularlo. Lo que se hace es una mezcla ordenada; se ordena por el primer elemento de cada página devuelta y se selecciona el menor (o mayor según sea especificado) y se devuelve dicho elemento. Luego se reubica dicha página por el sucesor y se vuelve a realizar el proceso. Cuando la página queda vacía se solicita al servidor una nueva página y se continúa el proceso hasta que no queden páginas disponibles o se hayan devuelto la cantidad de elementos requeridos. Como no se conoce información sobre los datos almacenados, puede ser que los primeros k elementos ordenados globalmente estén en un único servicio de datos o distribuidos por todos los servicios de datos. Este algoritmo ejecuta en O(k) ya que se transmiten k elementos más un valor constante que es menor o igual que la cantidad de servicios de datos por el tamaño de página. La entrada de JoinExecutor estaría compuesta por dos fuentes de datos que son de tipo ejecutor, dos funciones con forma Entidad Llave que permitan realizar ) la unión y una función con forma ( que realice la reducción de la relación. Se siguió un enfoque híbrido para ejecutar el inner join. De la primera fuente de datos se solicita una página (que según el ejecutor que sea, este pedido puede realizarse de manera remota o local) y con estos elementos se le añade dinámicamente un filtro adicional a la consulta de la segunda fuente de datos para que solo devuelva las entidades que satisfagan los valores de las llaves de los elementos seleccionados. De esta forma se devuelven los valores que satisfacen el inner join para dicha página y se procede a la siguiente página con el mismo algoritmo. El tamaño de página aquí se calcula teniendo en cuenta el tamaño máximo 12 Alejandro Tamayo Castillo, Darian Serrano Ferrer y Miguel Katrib de la URL, ya que añadir un filtro con muchos valores puede sobrepasar este tamaño máximo y no se podría ejecutar el pedido OData. Toda parte de la consulta que utilice como fuente de datos a un ejecutor se ejecuta en el cliente utilizando LINQ to Object. En el ejemplo anterior, la proyección se realiza a nivel de cliente. El método Take también se ejecuta en el cliente, pero como los ejecutores se han diseñado con un comportamiento perezoso (Lazy) los pedidos remotos se hacen por páginas según la demanda de la consulta minimizándose así el costo de transmisión. 6 Resultados experimentales Se confeccionaron dos grupos de consultas que incluyen filtros, ordenación y uniones. Las consultas en el primer grupo solo devuelven la cantidad de elementos consultados y aquellas en el segundo, devuelven una cantidad específica de resultados (diez mil elementos de manera predeterminada). Cada servicio de datos se configuró para acceder a una base de datos de 1 millón de elementos, almacenadas en MySQL 5.5.15 con motor MyISAM (sin ajustes personalizados) ejecutando en computadoras de escritorio con un procesador Core 2 Duo a 2Ghz y 1GB de memoria RAM conectadas a través de una red Fast Ethernet (100Mbps). En cada computadora física se ejecutaron de 5 a 10 servicios de datos por lo que cada una servía de 5 a 10 millones de datos en cada momento. La aplicación cliente ejecutó en una PC Quad 2 Core (4 núcleos) para así explotar el paralelismo. Tabla 1. Resultados experimentales Se realizaron diez repeticiones para cada consulta y se diseñaron seis ciclos de pruebas en donde en cada uno se iban incrementando la cantidad de servidores y servicios de datos paulatinamente. El resultado del experimento se puede visualizar en la Tabla 1. A lo sumo se levantaron 4 computadoras con 10 servicios llegando a 40 millones de elementos a consultar. Como caso promedio se logró un rendimiento de 1040 elementos por segundo y el rendimiento se sostuvo a medida que se iban añadiendo mayor cantidad de datos y servidores con un comportamiento lineal, lo que Integración de servicios de datos distribuidos y su utilización eficiente en computadoras multi-núcleo 13 es consecuente, por lo que se puede concluir que se logró la escalabilidad horizontal que se quería. Como dato interesante, con 10 millones de elementos el proceso de MySQL utilizaba el 100% del procesador para realizar la mayoría de las consultas (donde la más costosa resultó ser aquella que incluye ordenación). A partir de 15 millones de elementos, el proceso MySQL comenzó a pasarse de los 60 segundos sin dar respuesta. Los 10 millones de elementos resultaron ser el límite aceptable para el hardware utilizado. De manera general cada iteración consumía 10000 elementos, salvo las dos variantes que incluyen ordenación, donde se devuelven 25 y 300 elementos respectivamente. En este caso se comprobó que el proceso MySQL requería unos cuantos segundos para realizar la ordenación antes de devolver respuesta (compare la consulta countwhere-orderby con query-where-orderby) por lo que el rendimiento de estas consultas no va a subir mucho aunque se soliciten los diez mil elementos ya que como se realizan los pedidos por páginas hay que reordenar nuevamente. Éste es otro indicador a tener en cuenta; la ordenación tiene que optimizarse utilizando índices o técnicas de cache del lado del servidor o reducir los datos locales y aumentar la cantidad de servidores. 7 Conclusiones Se ha presentado una arquitectura que permite consultar diferentes fuentes de datos de manera distribuida utilizando protocolos estándares. Al ser OData el protocolo utilizado para el intercambio de datos, se pueden utilizar fuentes de datos existentes que implementen dicho protocolo o crear servicios de datos utilizando diferentes lenguajes de programación que se conecten a sistemas legados, no necesariamente SQL, y publiquen en la web dicha información. El prototipo que se ha utilizado no requiere conocimiento adicional de la forma o relación de los datos, así como la disposición de los servidores. El descubrimiento de los servicios de datos se realiza de manera dinámica y el esquema de datos se infiere de igual manera. Para el programador, el trabajo con la red, el descubrimiento de servicios y la consulta de datos son transparentes ya que utiliza el prototipo como si los tipos de datos fuesen del propio lenguaje y los datos estuviesen almacenados en memoria. Empíricamente los resultados obtenidos muestran un buen rendimiento y alcanzan el objetivo de escalabilidad horizontal cuando el monto de datos es grande y no se puede lograr escalabilidad vertical. El aporte fundamental consiste en mostrar un prototipo que permite consultar, utilizando un enfoque orientado a recursos, una base de datos en la nube, compuesta por servicios de datos independientes. 14 Alejandro Tamayo Castillo, Darian Serrano Ferrer y Miguel Katrib Referencias 1. Oracle: Distributed Database Administrator's Guide. Concepts. In : Oracle® Database 2. Cattell, R.: Scalable SQL and NoSQL data stores. ACM SIGMOD Record (2010) 3. Leavitt, N.: Will NoSQL Databases Live Up to Their Promise? IEEE Computer Society (2010) 4. Kart, F., Moser, L., Melliar-Smith, P.: Building a Distributed E-Healthcare System Using SOA. IT Pro IEEE (2008) 5. Keen, M., Acharya, A., Bishop, S., Hopkins, A., Milinski, S., Nott, C., Robinson, R., Adams, J., Verschueren, P.: Patterns: Implementing an SOA Using an Enterprise Service Bus. IBM RedBooks (2004) 6. Zhub, F., Turnera, M., Kotsiopoulosc, I., Bennettb, K., Russelld, M., Budgena, D., Breretona, P., Keanec, J., Layzellc, P., Rigbyd, M., Xue, J.: Dynamic Data Integration Using Web Services. ICWS'04 IEEE (2004) 7. Papazoglou, M., Traverso, P., Dustdar, S., Leymann, F.: Service-Oriented Computing: State of the Art and Research Challenges. (2007) 8. Hummer, W., Leitner, P., Dustdar, S.: WS-Aggregation: Distributed Aggregation of Web Services Data. SAC'11 ACM (2011) 9. Kossmann, D.: The State of the Art in Distributed Query Processing. ACM Computing Surveys 32(4) (2000) 10. Leijen, D., Schulte, W., Burckhardt, S.: The design of a task parallel library. (2009) 11. Pialorsi, P., Russo, M.: Introducing microsoft® linq. (2007) 12. Open Data Protocol (OData). Available at: http://www.odata.org/ 13. Web Services Dynamic Discovery (WS-Discovery). In: OASIS. Available at: http://docs.oasis-open.org/ws-dd/ns/discovery/2009/01 14. Campbell, C., Johnson, R., Miller, A., Toub, S.: Parallel Programming with Microsoft.NET. Design Patterns for Decomposition and Coordination on Multicore Architectures. (2010) 15. Tamayo, A., Sánchez, L., Katrib, M.: Un Servicio de Datos Dinámico. CibSE 2010 (2010) 16. Chappell, D.: Introducing OData: Data Access for the Web, Cloud, Devices, and More. (2011) 17. Wang, , Ming-Syan, C.: On the complexity of distributed query optimization. IEEE Transactions on Knowledge and Data Engineering 8(4) (1996)