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)
Download

URL