Exécution parallèle de requêtes relationnelles et - smis

Transcription

Exécution parallèle de requêtes relationnelles et - smis
Exécution parallèle de requêtes
relationnelles et équilibrage de charge
Luc Bouganim
Projet Rodin - INRIA Rocquencourt
78153 Le Chesnay Cedex
* Travail effectué dans le GIE Dyade (Bull Inria)
RÉSUMÉ : L’utilisation de machines parallèles pour les systèmes de gestion de bases de données
constitue une solution aux besoins de performances de ces applications. Afin d’exploiter le
parallélisme, les requêtes sont découpées en plusieurs tâches, exécutées sur plusieurs processeurs. A
cet effet, le parallélisme inter-opérateurs (plusieurs opérateurs sont exécutés en parallèle sur plusieurs
processeurs) et intra-opérateur (un opérateur est cloné sur plusieurs processeurs) sont utilisés. Un des
obstacles majeur à l’obtention de bonnes performances réside dans l’équilibrage de la charge entre
les différents processeurs. En effet, le temps de réponse d’une exécution parallèle est égal au temps de
réponse du processeur le plus chargé. Une mauvaise distribution de la charge de travail peut donc
entraîner une chute de performance importante. Après une présentation des différentes architectures
multiprocesseurs pour les SGBD, nous exposons, les techniques mises en oeuvres pour l’exécution
parallèle de requêtes ainsi que les solutions existantes au problème de l’équilibrage de charge.
ABSTRACT : The use of parallel machines for database management system is the solution for
high performance aplications. There are two dimensions for parallelizing queries : horizontally (i.e.
intra-operator parallelism) by distributing each operator among several processors, and vertically
(i.e. inter-operator parallelism) by distributing all operators of the query among several processors.
However, query response time can be hurt by several barriers, the major one being poor load
balancing, i.e. some processors are overloaded while some others remain idle. As the response time of
a set of parallel activities is that of the longest one, this can severely degrade performance. After
presenting parallel architectures for database systems, we describe technics for parallel query
execution. Afterwards, we present existing solutions for load balancing.
MOTS-CLÉS : bases de données, parallélisme, équilibrage de charge
KEYWORD : databases, parallelism, load balancing
L’objectif spécifique d’un système parallèle de gestion de bases de données est de
profiter au mieux des caractéristiques de la machine multiprocesseur sur laquelle il
s’exécute. Son efficacité est directement fonction de sa rapidité à traiter l’ensemble des
transactions s’appliquant sur les données de la base. On distingue deux types de transactions
qui conduisent à exploiter le parallélisme de manière différente. Le parallélisme interrequête vise à améliorer le débit transactionnel de transactions courtes de type OLTP (On
Line Transaction Processing). Nous ne considérons pas ici cette forme de parallélisme
potentiellement présent dans les systèmes de bases de données multi-utilisateurs. Dans le cas
de transactions longues de type aide à la décision, le parallélisme intra-requête, consistant à
exécuter en parallèle différentes parties d’une même requête, est utilisé pour réduire le
temps de réponse de chaque transaction.
La Figure 1 présente l’architecture standard d’un SGBD exploitant ce type de
parallélisme. Les requêtes de l’utilisateur sont envoyées au préprocesseur qui vérifie sa
validité syntaxique et sémantique à l’aide d’informations stockées dans le schéma de la base.
La requête est alors transformée en algèbre relationnelle puis optimisée. L’optimiseur évalue
un certain nombre de stratégies d’exécution potentiellement efficaces, et choisi celle qui
optimise l’utilisation des ressources de la machine, tout en produisant le meilleur temps de
réponse. Le plan d’exécution parallèle ainsi produit, décrit la décomposition de la requête en
fragments qu’il est ensuite possible d’exécuter en parallèle. L’exécution est finalement prise
en charge par le moteur d’exécution dont le rôle est d’obtenir les meilleures performances
sur l’architecture multiprocesseur cible.
Compilation
Requête SQL
Préprocesseur
Informations schéma
Schéma
de la Base
Algèbre Relationnelle
Optimiseur parallèle
Informations physiques
Plan d’exécution parallèle
Exécution
Moteur d’exécution
....
Machine parallèle
Figure 1. Architecture Standard d’un SGBD Parallèle
Cet article présente l’ensemble des techniques ainsi que les principaux travaux relatifs au
problème de l’exécution parallèle de requêtes. Dans la section 1, nous décrivons en détail les
architectures matérielles, leurs avantages et inconvénients, ainsi que les principaux
prototypes ou produits existants pour chaque architecture. La section 2 présente les
différentes approches possibles pour la mise en oeuvre du parallélisme. La section 3
présente les plans d’exécution parallèle et en particulier, les différentes formes d’arbres
d’exécution. La section 4 décrit les métriques permettant de quantifier le gain d’une
exécution parallèle. Les problèmes liés à l’exécution parallèle sont décrit dans la section 5.
Le problème de la répartition de charge est plus largement abordé, et les solutions existantes
sont décrites. La section 6 conclut cette étude.
1. Architectures matérielles
Dans cette section, nous présentons les architectures parallèles les plus courament
utilisées pour les systèmes de gestion de bases de données.
1.1. Architectures à mémoire partagée ou shared-memory
Dans les architectures mémoire partagée, la mémoire est accessible et partagée par
l’ensemble des processeurs de la machine. Chaque processeur a un coût uniforme d’accès à
la mémoire, on parle alors de machine UMA (Uniform Memory Access). Une architecture
UMA est constituée par un bus reliant entre eux tous les éléments matériels de la machine :
processeurs, modules mémoire et disques. Appelée aussi SMP (Symetric MultiProcessor),
cette architecture a été adoptée sur des plate-formes diverses, depuis les stations de travail à
quatre processeurs jusqu’aux plus grosses configurations comme les machines MULTIMAX
série 500 fabriquées par ENCORE ou la série SYMMETRY fabriquée par SEQUENT
[Lov88].
P1
cache
Mémoire1
Pn
Mémoirek
Disque1
Disquep
cache
Bus
Figure 2. Architecture UMA
L’architecture UMA (c.f. Figure 2) permet un accès rapide aux données mais limite le
nombre de processeurs du système. En effet, le bus, élément matériel partagé par tous les
processeurs constitue le goulot d’étranglement d’une telle architecture. Sa bande passante
limitée interdit de très grosses configurations.
Ce type de système représente cependant un excellent rapport coût/performance pour des
applications de bases de données de taille moyenne. De plus, la répartition de la charge de
travail sur les différents processeurs peut être efficacement réalisée grâce à l’utilisation de la
mémoire partagée. Enfin, le modèle de programmation des architectures à mémoire partagée
facilite grandement le portage d’une application développée pour un système monoprocesseur. Pour ces raisons, l’architecture UMA a été largement adoptée par l’ensemble des
constructeurs du marché.
Les prototypes de systèmes parallèles de gestion de bases de données s’exécutant sur des
machines à mémoire partagée sont principalement XPRS [Sto88], Volcano [Gra94], DBS3
[Ber93] et Monet [Bon96]. Pour les systèmes commerciaux, Oracle [Dav92] et Informix
[Dan92] proposent des solutions sur ce type d’architecture.
1.2. Architecture distribuées ou shared-nothing (SN)
Dans l’architecture SN, excepté le réseau d’interconnexion, rien n’est partagé entre les
processeurs. Ainsi, chaque noeud de la machine consiste en un processeur avec ses disques
et sa mémoire locale. Les noeuds communiquent entre eux par envois de messages via un
réseau d’interconnexion permettant des échanges à haut débit (Figure 3).
Réseau d’interconnexion
P1
P2
Pn
Mémoire
locale
Mémoire
locale
Mémoire
locale
Disque(s)
Disque(s)
Disque(s)
Figure 3. Architecture shared-nothing
Les partisans de l’architecture shared-nothing [Bor88] [DeW92b] considèrent que
l’extensibilité de cette architecture est un facteur décisif justifiant l’adoption des machines
SN pour les systèmes parallèles de gestion de bases de données. En effet, sur ce type de
machines, chacun des processeurs gère directement une portion de la base. Le partage des
ressources matérielles et logicielles entre les différents processeurs est donc limité au
minimum. Ainsi, une machine parallèle adoptant une architecture SN peut théoriquement
monter jusqu’à quelques centaines de processeurs. Cependant, la répartition de la charge de
travail entre les processeurs de la machine SN est complexe et coûteuse.
De nombreux systèmes parallèles de gestion de bases de données exploitent ce type
d’architecture comme TANDEM [Che93], Bubba [Bor88], Gamma [DeW90], Teradata
[Ter83], EDBS [Lop89] et PRISMA/DB [Ape92].
1.3. Architectures à disques partagés ou shared disks
L’architecture à disques partagés est illustrée Figure 4. Cette architecture a été
notamment adoptée dans Oracle à partir de la version 6.0 (Oracle Parallel Server [Dav92]).
L’architecture à disques partagés tente de faire un compromis entre l’architecture à mémoire
partagée et shared-nothing. Les disques sont partagés pour faciliter la répartition de la
charge de travail tandis que la mémoire reste distribuée pour ne pas trop pénaliser
l’extensibilité. De plus, contrairement à l’architecture shared-nothing, le partage des disques
permet une grande disponibilité des données puisque la totalité de la base de données
continue à être accessible même si l’un des nœuds devient indisponible.
P1
P2
Pn
Mémoire
locale
Mémoire
locale
Mémoire
locale
Réseau d’interconnexion
Disque
Disque
Disque
Figure 4. Architecture shared-disks
Comme la mémoire n’est pas partagée, une page disque peut se retrouver dupliquée dans
plusieurs noeuds de la machine. Il est nécessaire de garantir la cohérence entre toutes les
copies d’une même page. Le surcoût de maintenance de cette cohérence est bien sûr le point
faible de ce type d’architecture, et cela d’autant plus que le nombre de mises à jour est
important. L’architecture à disques partagés est décrite de manière plus détaillée dans .
2. Formes de parallélisme.
Le modèle relationnel se prête bien à la parallélisation des requêtes. Une requête
relationnelle, sous sa forme algébrique, est formée par un graphe d’opérateurs s’appliquant
sur de grands ensembles de données ou relations. En exécutant en parallèle plusieurs
opérateurs du graphe de la requête, il est possible de générer du parallélisme de type interopérateur. En fragmentant les opérandes d’un seul opérateur, il est possible de la
décomposer en sous-opérateurs identiques conduisant à générer du parallélisme de type
intra-opérateur. Ces deux types de parallélisme peuvent être combinés ou non pour
améliorer le temps de réponse de la requête.
Dans la section 2.1, nous présentons la mise en oeuvre du parallélisme intra-et interopérateur pour des modèles d’exécutions basés sur la fragmentation, i.e., où chaque
processeur n’accède qu’à un sous ensemble des relations accédées. L’approche non
fragmentée, présentée dans la section 2.2, est uniquement applicable sur des architectures à
mémoire partagée. Elle est une simple parallélisation d’une exécution centralisée. Dans ce
cas, on ne peut parler réellement de parallélisme intra ou inter-opérateur.
2.1. Modèle d’exécution fragmenté
Dans un modèle d’exécution fragmenté, l’ensemble des relations (de base ou
temporaires) sont fragmentées grâce à une fonction de fragmentation. Chaque processeur
n’accède alors qu’à un sous ensemble des données. Cette approche est la seule possible sur
une architecture shared-nothing, sans quoi les accès aux données, souvent distant
entraînerait des surcoûts de communication prohibitifs.
2.1.1 Parallélisme intra-opérateur
Le parallélisme intra-opérateur repose sur la décomposition d’un opérateur relationnel en
un ensemble de sous-opérateurs indépendants, généralement appelées instances de
l’opérateur initial. Cette décomposition est rendue possible grâce à la fragmentation statique
et/ou dynamique des relations de la base, consistant à fragmenter horizontalement chaque
relation en paquets. Il existe trois méthodes basiques de fragmentation des données. Ces
méthodes, illustrées à la Figure 5, sont round-robin, par hachage et par intervalle (range).
...
...
...
...
...
Round-Robin
a-g
h-m
Hachage
...
u-z
Intervalle
Figure 5. Méthodes de Fragmentation des Données
La méthode round-robin place le iième tuple de la relation sur le disque numéro i mod n
où n est le nombre disque. La méthode par hachage associe les tuples aux disques selon une
fonction de hachage appliquée à l’un des attributs de la relation. Enfin, la méthode par
intervalle place les tuples sur les disques par intervalles de valeurs. Chacune de ces
méthodes a ses avantages et inconvénients. La méthode round-robin permet de répartir
uniformément les tuples contrairement à la méthode par hachage ou par intervalle. Ainsi,
elle garanti une bonne parallélisation des entrées/sorties lors de la lecture des relations. Elle
n’a cependant pas d’intérêt pour les relations intermédiaires (sauf si elles sont stockées sur
disque). Les deux dernières méthodes permettent, elles, de restreindre, dans certains cas, un
calcul à un sous-ensemble de paquets, mais peuvent générer des fragments de taille très
différente. Dans [Ber92], cette propriété est utilisée afin de réduire la complexité d’une
requête. Une technique appelée task elimination permet de supprimer, dans une requête
multi-jointure l’ensemble des sous-jointure ne produisant aucun résultat. Notons que, pour
cet algorithme, une mauvaise distribution est un avantage, dans la mesure où elle permet
plus d’élimination.
Basé sur la fragmentation des données, le mécanisme général de parallélisation intraopérateur consiste alors à décomposer l’opérateur initial en sous-opérateurs utilisant chacun,
un paquet de chaque relation en entrée. Souvent, la décomposition de l’opérateur peut
bénéficier de la répartition initiale des données, sinon, une redistribution préalable des
données est requise avant l’exécution de l’opérateur. Pour illustrer le parallélisme intraopérateur, prenons successivement le cas d’un opérateur de sélection puis d’un opérateur de
jointure.
Un opérateur de sélection sur une relation R peut être directement décomposé en n
opérateurs de sélection identiques, chacun d’eux étant appliqué sur l’un des paquets de la
relation initiale. Dans ce cas, aucune redistribution n’est requise et l’on a directement
SEL (R) = ∪ SELi(Ri) quelque soit la méthode de fragmentation (c.f Figure 6). Notons que
cela est indépendant de l’algorithme de sélection (avec ou sans index). Dans le cas d’une
sélection avec index, l’index est fragmenté de la même manière.
S
S1
S2
S3
Sn
Sel.
Sel.1
Sel.2
Sel.3
Sel.n
R1
R
R2
R3
n = degré de parallélisme
intra-opérateur
Rn
Sel.
opérateur
Sel.i
L’instance i
de l’opérateur
Figure 6. Parallélisme Intra-opérateur
Le cas d’un opérateur de jointure, opérateur binaire, est plus complexe que celui d’un
opérateur de sélection. Pour ramener l’exécution de la jointure à l’exécution de n sousjointures utilisant un paquet de chaque relation en entrée, il faut que chaque paquet de la
deuxième relation contienne tous les tuples de cette relation susceptibles de joindre avec le
paquet correspondant de la première relation. Ceci n’est possible qu’avec la fragmentation
par hachage ou par intervalle si l’attribut utilisé pour la fragmentation est l’attribut de
jointure. Dans le cas contraire, une redistribution de l’une, ou des deux relations (suivant
l’attribut de jointure) est nécessaire.
Enfin, il faut noter que la méthode de fragmentation (hachage, round-robin, range) n’a
aucun rapport avec l’algorithme local utilisé pour effectuer la jointure (boucle imbriquée,
tri-fusion, hachage, etc..). Par exemple, dans le cas d’une jointure par hachage utilisant une
fragmentation par hachage, deux fonctions de hachage distinctes seront utilisées. La
première h1, permettra de fragmenter les relations de bases en paquet, la seconde h2 (qui
peut être différente pour chaque instance) permettra d’effectuer la jointure par hachage.
2.1.2 Parallélisme inter-opérateur
Le parallélisme inter-opérateur consiste à exécuter en parallèle des opérateurs d’une
même requête. Il existe deux formes de parallélisme inter-opérateur.
Le parallélisme inter-opérateur indépendant consiste à exécuter en parallèle deux
opérateurs indépendants du graphe de la requête. Les plans d’exécution de type bushy-tree
offrent de nombreuses opportunités pour mettre en œuvre cette forme de parallélisme. Sur
l’exemple de la Figure 7a, OP1 et OP2 peuvent profiter de ce type de parallélisme.
OP3
OP1
OP2
OP2
R
T
S
S
(a)
OP1
(b)
R
Figure 7. Parallélisme inter-opérateur indépendant (a) et pipeline (b)
Le parallélisme inter-opérateur pipeline consiste à exécuter deux opérateurs du graphe
ayant un lien producteur-consommateur. Le résultat du producteur (OP1 sur la Figure 7b)
n’est pas matérialisé mais envoyé en pipeline à l’opérateur suivant (OP2) sur le même
principe qu’un pipe unix. Cela évite notamment la matérialisation complète de la relation
intermédiaire (T). On appelle une chaîne pipeline l’ensemble des opérateurs exécutés en
pipeline (OP1 et OP2 dans l’exemple).
Peu de systèmes exploitent le parallélisme inter-opérateur. Par exemple, certains
systèmes à mémoire distribuée comme Teradata [Ter83] ou Gamma [DeW86] forcent
chaque relation à être fragmentée sur l’ensemble des noeuds de la machine parallèle (fulldeclustering). Le parallélisme intra-opérateur met alors à contribution l’ensemble des
processeurs, ne laissant plus aucun intérêt au parallélisme inter-opérateur. De plus, la
possibilité d’exploiter le parallélisme inter-opérateur complexifie à la fois le système
d’exécution et l’optimiseur de requêtes qui doit considérer un nombre beaucoup plus
important de plans d’exécution.
2.2. Modèle d’exécution non fragmenté
On parle d’une approche non fragmentée lorsque la mise en oeuvre du parallélisme
revient à la simple parallélisation d’une exécution mono-processeur.
Sur un mono-processeur, l’exécution de plusieurs opérateurs successifs peut aussi être
faite en utilisant le pipeline. Cette approche a l’intérêt de supprimer la matérialisation des
relations intermédiaires. Dans ce cas, le pipeline est implémenté grâce à des appels de
procédure. Par exemple, une sélection d’une relation R suivie de deux jointures avec S puis
T se déroulera comme suit : Pour chaque tuple de R, s’il satisfait le prédicat de sélection, un
appel sera fait à la procédure join1 avec comme argument le tuple (ou sa projection). Ce
tuple sera alors testé avec la relation S. S’il joint, un appel sera fait à la procédure join2 avec
comme argument le tuple résultat. Si de nouveau, ce tuple joint, il sera inséré dans la relation
résultat, et ainsi de suite. Conceptuellement, nous avons donc les appels de procédures
suivant : join2(T, join1(S, select(R))).
Sur un multiprocesseur à mémoire partagée, il est possible de paralléliser cette stratégie
en utilisant une forme de parallélisme intra-opérateur. Plusieurs threads1 sont crées au début
de la l’exécution. Chaque thread traite alors un sous ensemble de la relation racine (R dans
l’exemple) et exécute l’ensemble des opérateurs grâce au pipeline synchrone. Bien qu’il y
ait une fragmentation des données pour les relations de bases (stockées sur disque), ce n’est
pas le cas pour les autres relations. Chaque thread accède potentiellement à l’ensemble des
relations S et T. Cette stratégie appelée pipeline parallèle synchrone est décrite dans
[Hon92] [She93].
Ainsi, dans le cas du pipeline synchrone, on ne peut plus vraiment parler de parallélisme
inter-opérateur puisque l’exécution pipeline est justement séquentielle. Par contre, le
parallélisme indépendant peut encore être mis en oeuvre en exécutant plusieurs chaînes
pipelines indépendantes concurremment.
3. Arbre d’opérateurs
L’arbre d’opérateur provient de la macro-expansion de l’arbre de jointure [Has94]. Les
1. Un thread est un processus léger. Plusieurs threads peuvent être créés dans un même processus. Leur ordonnancement est alors moins coûteux que pour des processus.
noeuds représentent les opérateurs atomiques de l'algèbre relationnelle et les arcs
représentent les flots de données. Ces arcs peuvent être de deux types : bloquants ou
pipelines. Un arc bloquant indique que les données doivent être entièrement produites avant
de pouvoir commencer l’opérateur consommateur. Ainsi, un opérateur ayant une entrée
bloquante doit attendre la matérialisation complète de l'opérande avant de commencer. Un
arc pipeline indique que les données peuvent être consommées au fur et à mesure. Ainsi, le
consommateur peut débuter dés que le premier tuple à consommer a été produit.
Plusieurs formes d’arbres peuvent être considérées : linéaires (gauches ou droits)
[Sch90], zigzag [Zia93] ou bushy [She93]. La Figure 8 présente les arbres droits, gauches et
bushy pour une requête effectuant la jointure de 4 relations R, S, T et U.
Dans un arbre droit, toutes les jointures sont effectuées en pipeline. On peut ainsi dire
que tous les opérateurs s’exécutent en parallèle. En fait, l’exécution s’effectue souvent en au
moins en deux phases. Dans une première phase, les relations à gauche (S, T et U sur la
figure) sont chargées en mémoires (et éventuellement indexées, hachées ou triées). Dans la
deuxième phase, les tuples de R sont testées successivement avec les relations S, T et U en
pipeline. L’avantage d’un arbre droit est qu’il permet de ne pas matérialiser les relations
intermédiaires et produit rapidement le premier résultat (pipeline). Toutefois, pour obtenir
de bonnes performances, il faut que toutes les relations à gauche puissent tenir en mémoire.
Dans le cas contraire, l’exécution pipeline générera du swapping.
Dans un arbre gauche, les jointures sont toutes exécutées en séquence. Ainsi, l’exécution
se fait en autant de phases qu’il y a de relations à joindre. Dans chaque phase une relation
intermédiaire (ou le résultat final) est matérialisée. Cette stratégie d’exécution est utilisée
lorsque la mémoire est limité car elle ne matérialise jamais plus de deux relations
simultanément : une relation intermédiaire à gauche et le résultat de sa jointure, la relation à
droite étant lue page à page.
Les arbres gauches et droits sont deux stratégies extrêmes d’exécution d’un arbre
linéaire. les arbres zigzag, introduits dans [Zia93], donnent une stratégie intermédiaire entre
les arbres gauches et droits. Dans un arbre zigzag, une relation intermédiaire peut être
matérialisée ou pipelinée selon la quantité de mémoire disponible. L’arbre linéaire droit est
ainsi découpé en plusieurs sous-arbres droits exécutés séquentiellements. L’intérêt de cette
stratégie est qu’elle permet d’effectuer plus de pipeline que les arbres gauches mais
consomme moins de mémoire que les arbres droits.
U
U
T
R
S
T
R
S
T
U
S
R
(c)
(a)
(b)
Figure 8. Arbres linéaire gauche (a), bushy (b) et linéaire droit (c)
Les arbres bushy sont les plus généraux. Ils n’imposent aucune contrainte sur les liens
entre les opérateurs (bloquant, non-bloquant). Ils permettent d’obtenir du parallélisme
indépendant [Lan93]. De plus, ils offrent un bon compromis entre le degré de parallélisme
inter-opérateur et la consommation mémoire [She93]. Enfin, la considération des arbres
bushy lors de l’optimisation augmente considérablement l’espace de recherche, permettant,
de ce fait, l’obtention potentielle de meilleurs plans d’exécution.
4. Mesure de performances
Le but de l’exécution parallèle de requêtes est d’obtenir un gain de performance. En plus
du temps de réponse, il existe plusieurs métriques permettant de quantifier le gain obtenu par
une exécution parallèle. Les plus connues sont le speed-up et le scale-up (voir par ex. ). Un
système parallèle idéal est celui qui permet d’obtenir à la fois un speed-up linéaire et un
scale-up constant. De manière informelle, un speed-up idéal indique qu’une tâche peut être
exécutée deux fois plus vite si l’on dispose de deux fois plus de ressources matérielles
(processeurs, disques, mémoire,...). Un scale-up idéal signifie qu’une tâche deux fois plus
grosse peut être exécutée dans le même temps si l’on dispose de deux fois plus de ressources
matérielles.
La définition formelle du speed-up est la suivante : soit une tâche de taille fixe exécutée
de manière séquentielle en un temps Ts puis exécutée en parallèle sur p processeurs en un
temps Tp, le speed-up obtenu par l’exécution parallèle est alors défini par :
Ts
speed – up ( p ) = -----Tp
et le speed-up est idéal si speed-up (p) = p.
On peut déduire du speed-up l’efficacité Ep de l’algorithme parallèle, c’est à dire le
rapport entre le speed-up effectif et le speed-up idéal :
Ts
Ep = ---------------Tp × p
Par exemple, si l’on considère un problème résolu en 120 secondes sur une machine
séquentielle alors qu’il faut 10 secondes sur une machine parallèle avec 16 processeurs, le
speed-up est égal à 12 et l’efficacité du traitement parallèle est de 75%.
Il est important de remarquer qu’il est plus facile d’obtenir de bon speed-up avec des
algorithmes de jointures peu efficaces (p. ex. boucles imbriquées). En effet, la part des
surcoûts d’exécution est d’autant plus grande que le temps de réponse est petit. La mesure de
speed-up ne peut donc être prise pour comparer deux systèmes ou deux stratégies
d’exécution. Il est dans ce cas, important de mesurer aussi les performances relatives des
deux systèmes ou stratégies.
La définition formelle du scale-up est la suivante : soient deux problèmes P1 et P2 avec
P2 n fois plus gros que P1 et T(Pi, p) le temps d’exécution du problème Pi sur un système
avec p processeurs, le scale-up est défini par la formule suivante :
T ( P1, p )
scale – up = -----------------------------T ( P2, p × n )
Le scale-up est idéal si il reste toujours égal à 1.
Notons qu’un problème n fois plus gros est obtenu par augmentation du volume de
données à traiter. Par exemple, supposons que l’on désire mesurer le scale-up d’un opérateur
de sélection. Pour obtenir une sélection n fois plus grosse, il suffit simplement de multiplier
le volume des données à sélectionner par n. Ceci n’est plus vrai pour un opérateur de
jointure dont la complexité n’est pas linéairement proportionnelle à la taille de ses
opérandes. C’est pour cette raison que la mesure de speed-up, plus facile à mettre en oeuvre,
est souvent préférée à celle du scale-up.
5. Problèmes liés à l’exécution parallèle
Un ensemble de facteurs limite les gains obtenus lors d’une exécution parallèle. Dans
cette section, nous montrons quels sont ces facteurs. Nous insistons particulièrement sur les
problèmes de répartition de charge et présentons les solutions existantes.
5.1. Initialisation et mise en place du parallélisme
Avant que l’exécution parallèle ne débute réellement, toute une phase d’initialisation et
de mise en place du parallélisme est nécessaire. La phase d’initialisation consiste par
exemple à ouvrir les relations impliquées dans la requête, à créer la relation résultat, à
initialiser le contexte d’exécution propre à chaque opérateur. La phase de mise en place du
parallélisme consiste, par exemple, à créer les processus ou les threads impliqués dans
l’exécution parallèle et à établir les connexions entre ceux-ci pour mettre en place les
communications inter-opérateur.
Ce processus est, en général séquentiel et précède l’exécution parallèle proprement dite.
Le temps d’initialisation et de mise en place du parallélisme explique en grande partie les
mauvais speed-up obtenus pour les requêtes de faible complexité comme un opérateur de
sélection sur une relation. Par exemple, les mesures de performance effectuées dans
PRISMA/DB [Wil92] montrent un effondrement du speed-up sur les petites sélections, et
cela dès que le nombre de processeurs utilisés devient supérieur à 8. Le degré de
parallélisme doit être choisi en fonction de la complexité de la requête.
[Wil92] présente une formule permettant de déterminer le speed-up maximal atteignable
lors de l’exécution d’un seul opérateur et d’en déduire le nombre de processeur optimal pour
son exécution. Considérons l’exécution d’un opérateur traitant N tuples avec n processeurs,
le temps de traitement de chaque tuple étant c et le temps d’initialisation de l’exécution par
processeur a. Le temps de réponse peut s’écrire avec la formule suivante :
cN
R = an + ------n
Le temps de réponse minimum (correspondant au speed-up maximum) s’obtient alors
facilement en dérivant cette formule par rapport à n et en l’égalant à 0. On obtient alors :
1
cN
S 0 = -le speed-up maximal étant égal à
n 0 = ------2 n0
a
Dans le cas des systèmes à grande mémoire, le temps d’initialisation (an) n’est pas
négligeable devant le temps de réponse. Ce qui limite le nombre de processeurs optimal.
Pour des systèmes lisant les relations sur disque, le temps d’initialisation limite aussi le
nombre maximum de processeurs mais dans d’autres proportions. Ces équations montrent,
entre autres, qu’il ne semble pas judicieux d’exécuter chaque opérateur sur l’ensemble des
processeurs (utilisant alors uniquement le parallélisme intra-opérateur). Il faut plutôt
combiner parallélisme inter et intra-opérateur afin de diminuer le degré de parallélisme de
chaque opérateur et d’en augmenter le speed-up.
5.2. Contrôle de l’exécution parallèle
Lors de l’exécution parallèle d’une requête, des messages de contrôle sont échangés
entre les différentes entités d’exécution (ex. processus) mises en jeu pour l’exécution
parallèle. Ces messages de contrôle permettent par exemple de démarrer les opérateurs de la
requête ou de signaler leur fin. Ainsi, plus le degré de parallélisme est élevé et plus le
nombre de messages de contrôle est important.
Pour les premiers systèmes parallèles de bases de données, le surcoût lié au contrôle de
l’exécution parallèle était l’un des obstacles majeurs à l’obtention de bons speed-up. Un cas
typique est le prototype DIRECT [Bor82], ancêtre de Gamma. Dans DIRECT, chaque page
de données traitée donnait lieu à l’échange d’au minimum 1 message de contrôle vers un
scheduler centralisé. L’échange de ces messages constituait le goulot d’étranglement du
système et contre-balançait tous les gains de l’exécution parallèle.
Depuis l’expérience de DIRECT, de nombreuses solutions ont été proposées visant à
limiter le nombre de messages échangés pour la synchronisation et le déclenchement tout en
éliminant la nécessité d’un contrôleur centralisé.
Le travail le plus complet dans ce domaine est certainement celui effectué dans le cadre
du projet EDS [EDS90] au travers du langage d’expression des plans d’exécution parallèle
LERA-par [Cha92]. L’idée de base est d’introduire directement dans le code de la requête
l’ensemble des opérateurs de contrôle nécessaires pour assurer une exécution parallèle
correcte. De plus, les stratégies de contrôle sont construites à partir d’une bibliothèque
d’opérateurs où chaque opérateur représente une fonctionnalité de contrôle élémentaire.
Cette approche apporte une grande souplesse car la combinaison des fonctions élémentaires
de contrôle disponibles dans la bibliothèque [Dag91][BoS91] permet l’élaboration de
stratégies optimisées et adaptées à chaque requête, qui conduisent à une réduction du
nombre de messages échangés.
5.3. Interférences et effets de convoi
Une exécution hautement parallèle peut être freinée par l’apparition de phénomènes
d’interférences. Ces phénomènes apparaissent lorsque plusieurs processeurs accèdent
simultanément à une même ressource non partageable (ou peu partageable). Certains
processeurs doivent alors être mis en attente, freinant ainsi l’exécution parallèle. Les
phénomènes d’interférence peuvent survenir tant au niveau matériel (i.e. la ressource est
matérielle) que logiciel (i.e. la ressource est une donnée partagée).
Un exemple typique d’interférence matérielle est la contention créée au niveau du bus
d’une machine à mémoire partagée lors des accès mémoire effectués par les processeurs.
Plus le nombre de processeurs augmente et plus les conflits au niveau de ce bus augmentent.
C’est la raison pour laquelle le nombre de processeurs est limité à quelques dizaines sur ce
type d’architecture. Il en est de même pour les disques. Lorsque deux processus effectuent
une entrée/sortie sur le même disque, ils sont séquentialisés au niveau du système. Notons
que la multiplication des ressources matérielles permet de limiter les conflits d’accès. Par
exemple, au niveau d’un disque, l’adoption de disques RAID (Redondant Array of
Inexpensive Disks [Pat88]), permet de limiter ces interférences.
Quant aux interférences logicielles, elles apparaissent lorsque plusieurs threads ou
processus accèdent en parallèle à une même donnée [Fon89]. Pour prévenir tout risque
d’incohérence, une variable verrou [Lam86] est généralement utilisée afin de séquentialiser
les accès à la donnée (c.f. Figure 9).
Lorsqu’un processus tente de prendre un verrou sur une donnée et que celui-ci est détenu
par un autre processus, il est mis en attente. Ces interférences logicielles peuvent alors
devenir le goulot d’étranglement du système et cela d’autant plus rapidement que des effets
de convoi peuvent survenir.
Un effet de convoi [Bla79] survient lorsqu’un processus est de-schedulé par le système
alors qu’il détient un verrou. Cela peut survenir suite à l’épuisement de son quantum de
temps sur le processeur ou à cause d’une entrée-sortie. Ce temps allonge la période de prise
du verrou, augmentant du même coup la probabilité de conflits. Plus cette probabilité
augmente et plus les processus en attente du verrou vont s’accumuler conduisant à un effet
boule de neige.
Un exemple typique d’interférence logicielle survient lors des accès aux tampons de la
base de données par les différentes entités d’exécutions (threads ou processus). Pour
prévenir tout risque d’incohérence, une variable verrou séquentialise l’accès à ces tampons.
Comme le montre l’étude sur le réglage d’une requête de tri effectuée dans Volcano
[Gra92], l’accès aux tampons peut alors devenir rapidement le goulot d’étranglement du
système et cela même lorsque le nombre de processeurs est peu élevé. Par exemple, cette
étude a montré que pour 16 processeurs, le surcoût occasionné par les interférences
logicielles représentaient 45% du temps total d’exécution. Ce problème de contention lors
de l’accès aux tampons de la base a aussi été reporté dans [Hon91] comme un des facteurs
limitant le degré de parallélisme.
Variables
partagées
Variables
partagées
Verrou
Threads
Accés incontrolé
Accés séquentiel
áccés parallèle
Figure 9. Accés à des structures partagées
La solution générale pour remédier aux problèmes d’interférences consiste à paralléliser
la ressource partagée, c’est à dire à la décomposer en sous-ressources indépendantes. Deux
sous-ressources distinctes peuvent alors être accédées en parallèle, limitant ainsi la
probabilité d’interférence (c.f. Figure 9).
Cette solution a été adoptée dans DBS3 [Cas94] à la structure d’accès aux tampons de la
base, ce qui permet leur accès simultané par un grand nombre de threads.
5.4. Le problème de la répartition de la charge de travail
Ce problème est certainement celui qui affecte le plus la mesure de speed-up d’une
exécution parallèle. Nous avons présenté dans la section précédente les différentes
techniques pour générer du parallélisme intra-requête. Ces techniques sont le parallélisme
inter-opérateur et intra-opérateur. Utilisées conjointement ou non, elles permettent de
fragmenter le code séquentiel d’une requête pour constituer plusieurs fragments ou tâches
qu’il est ensuite possible d’exécuter en parallèle. Pour obtenir de bonnes performances, il est
nécessaire que les tâches ainsi formées soient réparties équitablement entre les différents
processeurs alloués pour l’exécution. Dans le cas contraire, certains processeurs vont
recevoir plus de travail que d’autres et le temps de réponse de la requête sera alors égal au
temps de réponse du processeur le plus chargé.
L’algorithme de répartition des tâches sur l’ensemble des processeurs impliqués dans
l’exécution est rendu difficile car le temps d’exécution peut grandement varier d’une tâche à
l’autre. Cela est principalement dû à deux raisons. Premièrement, si le parallélisme interopérateur est exploité, ces tâches sont issues d’opérateurs différents du graphe de la requête
conduisant à des coûts d’exécution très divers. Deuxièmement, même en ne considérant que
le parallélisme intra-opérateur, une répartition biaisée des données (ou data skew) peut
conduire à des différences de temps d’exécution entre les instances d’un même opérateur.
La section 5.4.1 et 5.4.2 détaillent le problème de la répartition de charge intra et interopérateur et donnent un aperçu des solutions existantes pour chaque forme de parallélisme.
5.4.1 Répartition de charge intra-opérateur
Walton et al. [Wal91] a classifié les effets d’une mauvaise répartition des données lors
d’une exécution parallèle avec un modèle d’exécution fragmenté. Ceux-ci sont résumés sur
un exemple, présenté à la Figure 10.
JPS
AVS/TPS
JPS
Res1
Res2
Join1
Join2
AVS/TPS
S2
AVS/TPS
S1
RS/SS
Scan1
R2
RS/SS
AVS/TPS
Scan2
R1
Figure 10. Exemple de problèmes de mauvaises distributions de données
Cet exemple décrit l’exécution d’une requête de sélection-jointure appliquée à deux
relations R et S mal distribuées fragmentées en deux paquets. La mauvaise fragmentation de
R et S provient soit des données elles-mêmes (skew de valeur des attributs ou AVS), soit de
la fonction de fragmentation (skew de placement des tuples ou TPS). Les temps de
traitement du paquet R1 ne sera alors pas égal à celui du paquet R2. Sur cet exemple, pour
obtenir une répartition équitable de la charge de travail, il est nécessaire de mettre plus de
puissance de calcul sur l’instance scan2 que sur l’instance scan1.
Pour l’opérateur de jointure, c’est encore pire. D’une part, la taille inégale des paquets de
la relation S (due à l’AVS et au TPS) entraîne des temps de traitement différents pour chaque
tuple transmis depuis l’opérateur de scan. De plus, le nombre de tuples transmis varie d’une
instance à l’autre suite à la mauvaise redistribution des paquets de R (skew de redistribution
ou RS) ou à une sélectivité variable suivant le paquet de R traité (skew de sélectivité ou SS).
Finalement, la sélectivité de la jointure peut aussi varier d’une instance à l’autre (skew de
sélectivité de la jointure ou JPS).
Une solution de répartition statique de la charge, i.e. basée sur une estimation de la
complexité de chaque instance d’opérateur, semble donc complexe à mettre en oeuvre. En
effet, pour que l’estimation soit correcte, il faut maintenir des statistiques du type
histogramme sur les attributs de jointure, les attributs de fragmentation, les attributs
participant à un prédicat (sélection ou jointure), donc potentiellement sur tous les attributs de
la relation. Il semble plus raisonnable de tenter de redistribuer la charge de travail
dynamiquement.
Wolf et al. [Wol93] proposent de modifier les algorithmes classiques de jointure
parallèle par tri-fusion et par hachage pour y ajouter une phase dite de scheduling. Cette
phase, placée après la phase de tri ou de hachage, vise à balancer le travail entre les
différents processeurs impliqués dans la phase ultérieure de jointure. Pour ce la jointure est
décomposée en un nombre de sous-jointures plus important que le nombre de processeurs
effectifs. Le coût d’exécution de chacune des sous-jointures est alors estimé et est utilisé
pour les répartir sur les processeurs de manière à ce que l’écart à la moyenne de la charge de
travail affectée à un processeur ne dépasse pas un seuil de tolérance fixé par avance. Si
aucune répartition ne satisfait ce prérequis, la méthode est itérée en modifiant la phase de tri
ou de hachage de manière à générer un plus grand nombre de sous-jointures, offrant ainsi
plus d’opportunités à la phase de scheduling. Notons que la phase de scheduling utilise
l’heuristique dite de LPT (Longest Processing Time first [Gra69]) pour répartir au mieux les
sous-jointures entre les processeurs.
Kitsuregawa et al. [Kit90] proposent un algorithme supportant les mauvaises
distributions de données et conçu spécifiquement pour la machine SDC (Super Database
Computer) basé sur une architecture distribuée. L’idée est de refragmenter chaque paquet de
relation en un grand nombre de sous-paquets. Ceux-ci sont alors temporairement distribués
sur les processeurs. Un mécanisme matériel, intégré au réseau d’interconnexion (appelé
réseau Oméga) est alors utilisé pour redistribuer dynamiquement les sous paquets ainsi
formés, de manière à obtenir une bonne répartition de charge.
Omiecinski [Omi91] propose une approche similaire pour une architecture à mémoire
partagée et utilise l’heuristique first fit decreasing pour redistribuer les sous-paquets aux
processeurs. Cependant, cette redistribution ne nécessite pas un mécanisme matériel
particulier.
Dewitt et al. [DeW92a] suggèrent l’utilisation de plusieurs algorithmes, chacun d’eux
étant spécialisés pour un certain degré de skew. Pour cela, une première phase
d’échantillonnage des relations est réalisé afin de déterminer le degré de skew et permet de
choisir un algorithme plus ou moins insensible au mauvaises distributions. Quatre
algorithmes spécifiques sont proposés et implémentés, en plus de l’algorithme de jointure
par hachage Hybrid hash join [Sch89]. L’évaluation de performances, menée sur le
prototype GAMMA, montrent qu’une telle approche permet d’obtenir un bon support des
mauvaises distributions sans surcoût lorsque les relations sont bien distribuées.
Shatdal et al. [Sha93] proposent d’utiliser la mémoire virtuellement partagée (SVM),
implémentée par logiciel, sur un environnement shared-nothing, afin de réaliser
dynamiquement une redistribution de la charge de travail. L’algorithme proposé combine
l’utilisation de la mémoire virtuellement partagée et l’envoi de messages afin d’éviter des
réplications de données intensives qui saturerait la mémoire de chaque processeur.
Lorsqu’un processeur est inactif, il ‘vole’ une partie de la charge de travail à un processeur
actif grâce à la mémoire virtuellement partagée. Une simulation est réalisée afin de
comparer cette stratégie avec celle développé dans [DeW92a]. L’ajout d’une phase
d’échantillonnage préalable des relations permet d’améliorer encore les performances.
Plus récemment, Brunie et al. [Bru95] [Bru96] ont proposé de déterminer statiquement
l’allocation des processeurs aux opérateurs et de permettre une réallocation dynamique des
processeurs en cours d’exécution en cas de mauvaises distributions des données. Pour cela,
des opérateurs spécifiques, appelés control operators sont insérés dans le plan d’exécution
parallèle afin de déterminer si les estimations statiques ne diffèrent pas trop des valeurs
réelles. Si la différence est trop grande, le control operator déclenche une phase de
redistribution des données afin de prévenir le skew de redistribution et le skew de produit de
jointure. Notons que cet opérateur peut aussi modifier le degré de parallélisme intraopérateur de l’opérateur suivant si besoin est. Il réalise ainsi dynamiquement la répartition
de charge intra et inter-opérateur.
5.4.2 Répartition de charge inter-opérateur
Afin d’obtenir une bonne répartition de charge au niveau inter-opérateur, il est nécessaire
de déterminer, pour chaque opérateur, son degré de parallélisme intra-opérateur et de choisir
judicieusement les processeurs alloués pour son exécution.
Supposons qu’un modèle de coût nous permette de déterminer, sans erreur, le temps
d’exécution séquentiel de chaque opérateur. Il nous faut alors trouver une méthode
d’assignation des processeurs aux opérateurs afin d’obtenir la meilleure répartition de
charge.
Chen et al. [Che93] proposent d’effectuer l’allocation des processeurs en utilisant
uniquement le parallélisme intra-opérateur et inter-opérateur de type indépendant. Il n’y a
donc pas d’exécution pipeline. Les processeurs sont alors alloués récursivement de la racine
aux feuilles de l’arbre d’opérateur de manière à deux sous arbre d’un noeud terminent leur
exécution au même moment. Ainsi tous les processeurs sont alloués à la dernière jointure
(racine de l’arbre) puis récursivement alloués à chaque sous-arbre en fonction de leur
complexité.
Hsiao et al. [Hsi94] utilisent la même méthode que précédemment mais l’applique sur ce
qu’ils appellent l’arbre d’allocation au lieu de l’appliquer sur l’arbre d’opérateur. L’arbre
d’allocation est déduit de l’arbre d’opérateur. Il faut d’abord identifier les chaînes pipelines
existantes dans l’arbre d’opérateur. Chaque chaîne pipeline devient alors un noeud de l’arbre
d’allocation. Les arcs entre ces noeuds représentent les contraintes de précédence entre les
chaînes pipelines. Ainsi, les processeurs sont alloués pour une chaîne pipeline et non plus
pour un opérateur. La distribution des processeurs alloués pour une chaîne pipeline sur
chaque opérateur est décrite dans [Lo93]. Cette stratégie d’allocation privilégie l’exécution
pipeline en essayant de produire toutes les données nécessaires à l’exécution d’une chaîne
pipeline de manière synchrone.
Wilshut et al. [Wil95] utilisent l’algorithme de jointure par hachage non bloquant pour
autoriser l’exécution de tous les opérateurs d’un arbre bushy simultanément (puisque cet
algorithme n’entraîne pas de blocages). Ils répartissent les processeurs sur les opérateurs en
fonction de leur complexité estimée. Cette technique appelé Full Parallel permet un degré
de parallélisme inter-opérateur maximum. Dans cette même étude, les auteurs étudient les
performances des stratégies présentées précédemment. Ils montrent que ces approches (y
compris la jointure non bloquante) souffrent des problèmes suivant :
• Problèmes de partie entière (discretization error) : Comme les processeurs et les
opérateurs sont des entités discrètes, les arrondis inévitables entraînent des problèmes
de répartition de charge.
• Délai dans les pipelines (pipeline delays) : Lors d’une exécution pipeline, les
processeurs associés à l’opérateur du haut de la chaîne sont inactifs tant que le
premier tuple n’est pas produit. Les délais dans les pipelines entraînent donc aussi
une sous utilisation des processeurs.
• Erreurs du modèle de coût : Enfin, et c’est sans doute le plus grave, il est clair qu’un
modèle de coût ne peut fournir que des résultats très approximatifs puisqu’ils ne
prennent pas en compte les aspects dynamiques de l’exécution (interférences,
contexte de l’exécution) et peut difficilement évaluer le coût d’un opérateur affecté
par le skew. Toutes ces stratégies sont donc dépendantes de la qualité du modèle de
coût
Metha et al. [Meh95] propose de déterminer dynamiquement (juste avant l’exécution) le
degré de parallélisme ainsi que l’ensemble des processeurs utilisés pour chaque opérateur.
L’algorithme Rate Match est décrit, il utilise un modèle de coût pour choisir le degré de
parallélisme de chaque opérateur. Toutefois, au lieu de se baser sur une estimation de la
taille des opérandes, l’algorithme Rate Match tente de faire correspondre le taux de
production des tuples résultat d’un opérateur avec le taux de consommation des tuples de
l’opérateur suivant. L’aspect multi-utilisateur est pris en compte en réduisant dans le modèle
de coût, la vitesse des processeurs d’un facteur dépendant de son utilisation. Enfin, six
algorithmes sont proposés pour le choix des processeurs qui exécuteront un opérateur (basés
sur la capacité mémoire, l’utilisation des processeurs, des disques, etc..).
Rahm et al. [Rah95] proposent d’autres algorithmes pour le choix du degré de
parallélisme et des processeurs. Les algorithmes proposés cherchent à répartir efficacement
les opérateurs de manière à maximiser l’utilisation des ressources (disques, CPU, mémoire),
grâce à des statistiques sur l’utilisation des processeurs et de la mémoire, maintenus par un
noeud de contrôle. Les algorithmes proposés sont évalués dans un contexte multi-utilisateurs
sur une simulation.
Sur des architectures à mémoire partagée, l’utilisation de modèles non fragmentés
change la nature du problème de répartition de charge inter-opérateur. En effet, chaque
processeur participant à l’ensemble des opérateurs d’une chaine pipeline, il n’y a pas
réellement de parallélisme inter-opérateur sauf en considérant deux chaînes pipelines
indépendantes.
Hong et al. [Hon92] proposent, lors de l’exécution concurrente de plusieurs chaînes
pipeline, d’ajuster dynamiquement le degré de parallélisme intra-opérateur de chacune afin
d’optimiser l’utilisation des ressources disques et CPU. Pour cela, ils proposent d’exécuter
concurremment deux chaînes pipeline, les performances de chacune étant limitées par des
facteurs différents (i.e., CPU bound et I/O bound). Le processus de contrôle de l’exécution
parallèle détermine alors le point d’utilisation maximum des ressources, (i.e. le degré de
parallélisme optimal de chaque chaîne pipeline pour maximiser l’utilisation des ressources)
et modifie le degré de parallélisme de chaque chaîne pipeline dynamiquement (en cours
d’exécution).
6. Conclusion
Nous avons donc, dans cet article présenté un ensemble de notions et techniques relatives
à l’exécution parallèle de requêtes : architectures matérielles, formes de parallélisme, plans
d’exécution parallèle, ainsi que les métriques permettant de quantifier le gain d’une
exécution parallèle.
Nous avons alors montré les problèmes liés à l’exécution parallèle et plus
particulièrement insisté sur le problème de la répartition de charge lors d’une exécution
parallèle. Les différentes solutions existante ont été décrites. Elles se divisent en deux
catégories. D’une part, les solutions au problème de la répartition de charge au niveau intraopérateur, généralement dynamiques, cherchent à redistribuer la charge durant l’exécution.
Les solutions au niveau inter-opérateur, sont elles, statiques et utilisent des techniques
basées sur un modèle de coût.
Nous avons alors décris l’approche que nous avons suivi pour réaliser une répartition
dynamique de la charge au niveau intra-opérateur et inter-opérateur lors de l’exécution sur
des architectures hybrides car elles nous semblent être des architectures clés pour les besoins
en performances des nouvelles applications.
7. Bibliographie
[Ape92]
P. M. G. Apers, C. A. van den Berg, J. Flokstra, P. W. P. J. Grefen, M. L. Kersten, A. N.
Wilschut, “PRISMA/DB : A Parallel Main Memory Relational DBMS”. IEEE Transaction
on Knowledge and Data Engineering, 4(6), 1992.
[Ber92]
C. A. van den Berg, M, L, Kersten, “Analysis of a Dynamic Query Optimization Technique
for Multi-join Queries”. Int. Conf. on Information and Knowledge Engineering, 1992.
[Ber93]
B. Bergsten, M. Couprie, P. Valduriez, “Overview of Parallel Architectures for
Databases”. Computer Journal, 36(8), 1993.
[Bla79]
M. Blasgen, J. Gray, M. Mitoma, T. Price, “The Convoy Phenomenon”. Operating Systems
Review 13(2), 1979.
[Bon96]
Peter A. Boncz, Fred Kwakkel, Martin L. Kersten : “High Performance Support for OO
Traversals in Monet”. BNCOD, 1996.
[Bor82]
H. Boral, D. DeWitt, D. Friedland, N. Jarrell, W. Wilkinso, “Implementation of the
Database Machine DIRECT”. IEEE Transaction on Software Engineering, 8(6), 1982.
[Bor88]
H. Boral, “Parallelism and data management”. Int. Conf. on Data and Knowledge
Engineering, 1988.
[BoS91]
P. Borla-Salamet, C. Chachaty, B. Dageville, “Compiling Control into Database Queries
for Parallel Execution Management”. Int. Conf. on Parallel and Distributed Information
Systems, 1991.
[Bou96a] L. Bouganim, D. Florescu, P. Valduriez, “Dynamic Load Balancing in Hierarchical
Parallel Database Systems”. Int. Conf. on Very Large Data Bases, 1996.
[Bou96b] L. Bouganim, D. Florescu, P. Valduriez : “Parallel Query Execution in NUMA
Multiprocessors”. Soumis à la publication. 1996.
[Bru95]
L. Brunie, H. Kosch, A. Flory, “New static scheduling and elastic load balancing methods
for parallel query processing”. BIWIT 95, IEEE Computer Society Press, 1995
[Bru96]
L. Brunie, H. Kosch, “Control strategies for complex relational query processing in shared
nothing systems”. ACM Sigmod Records, 25(3), 1996.
[Cas94]
P. Casadessus, B. Dageville, P. Borla-Salamet, “Evaluation du SGBD parallèle DBS3 sur
la machine KSR1”. Ingénierie des Systèmes d’Information (ISI), 2(4), 1994.
[Cha92]
C. Chachaty, P. Borla-Salamet, M. Ward, “A Compositional Approach for the Design of a
Parallel Query Processing Language”. Int. Conf. on Parallel Architectures and Language
Europe, 1992.
[Che93]
A. Chen, Y. F. Kao, M. Pong, D. Sak, S. Sharma, J. Vaishnav, H. Zeller, “Query
Processing in NonStop SQL”. IEEE Data Engenering Bulletin, 16(4), 1993.
[Dag91]
B. Dageville, P. Borla-Salamet, C. Chachaty, “Compiling Control Into Database Queries
For Parallel Execution Management”. Journées Bases de Données Avancées, BDA’91,
1991.
[Dav92]
D. D. Davis, “Oracle’s Parallel Punch for OLTP”. Datamation, 1992.
[Dan92]
W. Davison, “Parallel Index Building in Informix OnLine 6.0”. ACM-SIGMOD Int. Conf.,
1992.
[DeW86] D. J. DeWitt, R. H. Gerber, G. Graefe, M. L. Heytens, K. B. Kumar, M. Muralikrishna,
“GAMMA - A High Performance Dataflow Database Machine”. Int. Conf. on Very Large
Data Bases, 1986.
[DeW90] D. J. DeWitt, S. Ghandeharizadeh, D. Schneider, A. Bricker, H. Hsiao, R. Rasmussen, “The
Gamma Database Machine Project”. IEEE Trans. on Knowledge and Data Engineering,
2(1), 1990.
[DeW92a] D.J. DeWitt, J.F. Naughton, D.A. Schneider, S. Seshadri, “Practical Skew Handling in
Parallel Joins”. Int. Conf. on Very Large Data Bases, 1992.
[DeW92b] D.J. DeWitt, J. Gray, “Parallel Database Systems : The Future of High Performance
Database Processing”. Communications of the ACM, 35(6), 1992.
[EDS90]
EDS Database Group, “EDS - Collaborating for High Performance Parallel Relationnal
Database”. Esprit Conf., 1990.
[Fon89]
M. Fontenot, “Software Congestion, Mobile Servers, and the Hyperbolic Model”. IEEE
Transactions on Software Engineering 15, 1989.
[Gra92]
G. Graefe, S. Thakkar, “Tunning a Parallel Database Algorithm on a Shared-memory
Multiprocessor”. Software - Practice and Experience, 22(7), 1992.
[Gra94]
G. Graefe, “Volcano, An Extensible and Parallel Dataflow Query Evaluation System”.
IEEE Trans. on Knowledge and Data Engineering, 6(1), 1994.
[Gra69]
R. L. Graham, “Bounds on Multiprocessing Timing Anomalies”. SIAM Journal of Applied
Mathematics, V17, 1969.
[Has94]
W. Hasan, R. Motwani, “Optimization Algorithms for Exploiting the Parallel
Communication Tradeoff in Pipelined Parallelism”. Int. Conf. on Very Large Data Bases,
1994.
[Hon91]
W. Hong, M. Stonebraker, “Optimization of Parallel Query Execution Plans in XPRS”. Int.
Conf. on Parallel and Distributed Information Systems, 1991.
[Hon92]
W. Hong, “Exploiting Inter-Operation Parallelism in XPRS”. ACM-SIGMOD Int. Conf.,
1992.
[Hsi94]
H. Hsiao, M. S. Chen, P. S. Yu, “On Parallel Execution of Multiple Pipelined Hash Joins”.
ACM-SIGMOD Int. Conf., 1994.
[Kit90]
M. Kitsuregawa, Y. Ogawa, “Bucket Spreading Parallel Hash : A New, Robust, Parallel
Hash Join Method for Data Skew in the Super Database Computer”. Int. Conf. on Very
Large Data Bases, 1990.
[Lam86]
L. Lamport, “The Mutual Exclusion Problem : Part II - Statement and Solutions”. Journal
of ACM, 33(2), 1986.
[Lan93]
R. Lanzelotte, P. Valduriez, M. Zait, “On the Effectiveness of Optimization Search
Strategies for Parallel Execution Spaces”. Int. Conf. on Very Large Data Bases, 1993.
[Lo93]
M-L. Lo, M-S. Chen, C. V. Ravishankar, P. S. Yu, “On Optimal Processor Allocation to
Support Pipelined Hash Joins”. ACM-SIGMOD Int. Conf., 1993.
[Lop89]
M. Lopez, “The EDS Database Server”. EDS Working Report EDS.D901.B, 1989.
[Lov88]
T. Lovett, S. S. Thakkar, “The Symmetry Multiprocessor System”. Int. Conf. on Parallel
Processing, 1988.
[Meh95]
M. Metha, D. DeWitt, “Managing Intra-operator Parallelism in Parallel Database
Systems”. Int. Conf. on Very Large Data Bases, 1995.
[Omi91]
E. Omiecinski, “Performance Analysis of a Load Balancing Hash-Join Algorithm for a
Shared-Memory Multiprocessor”. Int. Conf. on Very Large Data Bases, 1991.
[Pat88]
D. A. Patterson, G. Gibson and R. H. Katz, “A Case for Redundant Arrays of Inexpensive
Disks (RAID)”. ACM-SIGMOD Int. Conf., 1988.
[Rah95]
E. Rahm, R. Marek, “Dynamic Multi-Resource Load Balancing in Parallel Database
Systems”. Int. Conf. on Very Large Data Bases, 1995.
[Sch89]
D. Schneider, D. DeWitt, “A Performance Evaluation of Four Parallel Join Algorithms in
a Shared-Nothing Multiprocessor Environment”. ACM-SIGMOD Int. Conf., 1989.
[Sch90]
D. Schneider, D. DeWitt, “Tradeoffs in processing complex join queries via hashing in
multiprocessor database machines”. Int. Conf. on Very Large Data Bases, 1990.
[Sha93]
A. Shatdal, J. F. Naughton, “Using Shared Virtual Memory for Parallel Join Processing”.
ACM-SIGMOD Int. Conf., 1993.
[She93]
E. J. Shekita, H. C. Young, “Multi-Join Optimization for Symmetric Multiprocessor”. Int.
Conf. on Very Large Data Bases, 1993.
[Sto88]
M. Stonebraker, R. Katz, D. Patterson & J. Ousterhout, “The design of XPRS”. Int. Conf. on
Very Large Data Bases, 1988.
[Ter83]
Teradata, “DBC/1012 Data Base Computer, Concepts and Facilities”. 1983.
[Wal91]
C.B. Walton, A.G. Dale, R.M. Jenevin, “A taxonomy and Performance Model of Data Skew
Effects in Parallel Joins”. Int. Conf. on Very Large Data Bases, 1991.
[Wil92]
A. N. Wilschut, J. Flokstra & P. M. G. Apers, “Parallelism in a main-memory system : The
performance of PRISMA/DB”. Int. Conf. on Very Large Data Bases, 23-27, 1992.
[Wil95]
A. N. Wilshut, J. Flokstra, P.G Apers, “Parallel Evaluation of multi-join queries”. ACMSIGMOD Int. Conf., 1995.
[Wol93]
J. L. Wolf, D. M. Dias, P. S. Yu, J. Turek, “Algorithms for Parallelizing Relational
Database Joins in the Presence of Data Skew”. Research Report RC19236. IBM. 1993.
[Zia93]
M. Ziane, M. Zait, P. Borla-Salamet, “Parallel Query Processing With Zig-Zag Trees”.
VLDB Journal, 2(3), 1993.