Peter Rieger Implementierung einer Client-Server-Optimierung zum Austausch von Multimedia-Dokumenten Diplomarbeit im Studiengang Diplom-Mathematik an der Universitat Augsburg Aufgabensteller: Prof. Dr. Werner Kieling Zweitgutachter: Prof. Dr. Bernhard Moller Betreuer: Dipl.-Inf. Matthias Wagner Augsburg, November 1997 Inhaltsverzeichnis Inhaltsverzeichnis i Abbildungsverzeichnis iii 1 Einleitung 1 2 Grundlagen 5 3 Architektur 9 2.1 Internet und WWW . . . . . . . . . . . . . . . . . . . . . . . . 2.2 Das Hypertext Transfer Protocol (HTTP) . . . . . . . . . . . . 3.1 Der Client . . . . . . . . . . 3.2 Der Proxy-Server . . . . . . 3.3 Der Storage-Server . . . . . 3.3.1 Der Web-Server . . . 3.3.2 Das CGI-Programm 3.3.3 Die Datenbank . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5 6 10 10 17 17 19 23 4 Optimierung 30 5 Messungen 41 6 Zusammenfassung und Ausblick 48 Literaturverzeichnis 50 4.1 Theoretische Grundlagen . . . . . . . . . . . . . . . . . . . . . . 4.2 Realisierung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . i 30 34 INHALTSVERZEICHNIS ii A Abbildungen A-1 B Tabellen B-1 C Programme C-1 C.1 C.2 C.3 C.4 C.5 C.6 C.7 converter.c . . . . . . . . . . . . dynform.sqc . . . . . . . . . . . db interface.sqc . . . . . . . . . optimierer.sqc . . . . . . . . . . insert copy.sqc . . . . . . . . . . mklter.c . . . . . . . . . . . . Eingabeformular (HTML-Code) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . C-1 C-4 C-7 C-17 C-35 C-43 C-45 Abbildungsverzeichnis 1.1 Anzahl der Hostadressen im Internet . . . . . . . . . . . . . . . 1 3.1 3.2 3.3 3.4 3.5 3.6 3.7 Systemarchitektur . . . . . . . . CACHEMISS . . . . . . . . . . CACHEHIT . . . . . . . . . . . Ablauf der Formatumwandlung Datenbankschema . . . . . . . . Das relationale Schema . . . . . Die Log-Tabelle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9 11 12 21 26 27 27 4.1 4.2 4.3 4.4 Menge der Attribute . . . . . Zusammenhangskomponenten Auswahl der Attributmengen Auswahl der Basiselemente . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36 37 38 39 A.1 A.2 A.3 A.4 A.5 Proxy-Konguration 1 Proxy-Konguration 2 Proxy-Konguration 3 Benutzerschnittstelle . Anfrageergebnis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . A-1 A-2 A-2 A-3 A-4 . . . . . . . . . . . . . . . . . . . . iii Kapitel 1 Einleitung Die zunehmende Popularitat des World Wide Web (WWW) bestimmt heute das Wachstum des Internet. Es ist aber kaum moglich, die Groe des Internet oder die Anzahl der Benutzer exakt zu bestimmen. Bezuglich der Anzahl der Hostadressen kann man jedoch einen Trend erkennen. In Abbildung 1.1 1 spiegelt sich die Entwicklung der Anzahl wieder, wobei erwahnt werden sollte, da die Zahl der Rechner im Internet nicht mit der Anzahl der Hostadressen ubereinstimmt. Abbildung 1.1: Anzahl der Hostadressen im Internet Mit der wachsenden Benutzergemeinde steigt auch die Zahl der Anbieter im WWW, welche heute den groten Anteil an den Internetdiensten haben. Immer 1 Quelle: http://nw.com/zone/WWW/report.html 1 KAPITEL 1: EINLEITUNG 2 mehr Firmen, Organisationen oder Privatpersonen sind im WWW vertreten und bieten dort ihre Informationen an. Diese Daten konnen in den unterschiedlichsten Formen vorliegen, z.B. als: Texte Bilder Videosequenzen Audiodaten Diese Daten werden als Multimedia-Dokumente bezeichnet. Die Speicherung dieser Dokumente kann in unterschiedlichen Formaten und Qualitaten erfolgen. Beispielsweise gibt es eine Reihe von Grakformaten, um Bilder zu speichern. jpeg ti gif png Ebenso konnen unterschiedliche Auosungen oder Farbtiefen verwendet werden. Bei Videos gibt es die Formate avi und mpeg die Qualitat wird hier zusatzlich durch die Anzahl der Bilder pro Sekunde bestimmt. Obwohl sich Formate zum Teil entsprechen mussen solche Daten in verschiedenen bereitgestellt werden, sei es, weil der Anwender nur ein bestimmtes Format verarbeiten kann (Er besitzt nur einen Audioplayer fur das SUN-Audio-Format (au), jedoch nicht fur wav-Dateien.2), oder weil er fur eine Sichtung3 der Daten mit geringerer Qualitat (dadurch kurzere Ladezeit, weniger Speicherbedarf) zufrieden ist. Um die Vielzahl der Dokumente verwalten zu konnen, bietet sich die Speicherung in einer Datenbank an. Es ist allerdings nicht sinnvoll, alle Formate und Qualitaten zu speichern, da zwischen den einzelnen Formaten funktionale Abhangigkeiten bestehen. So kann z.B. mit dem Tool convert aus dem ImageMagick-Paket eine png-Datei in das gif-Format uberfuhrt werden.4 (Das ImageMagick-Paket von John Cristy ist zum Anzeigen oder Bearbeiten von Bildern geeignet und ist im WWW unter http://www.wizards.dupont.com/cristy/ImageMagick.html zu nden.) Um die Rechenzeit fur die Konvertierung moglichst gering zu halten, siehe z.B.: http://www.swf3.de/studio/comixarchiv.htm siehe z.B.: http://www.ulistein.de/CARTOONS/uebers.html 4 Die genaue Syntax lautet: convert datei.png datei.gif 2 3 KAPITEL 1: EINLEITUNG 3 ist es sinnvoll, Formate die oft angefragt werden, direkt in der Datenbank zu speichern. Ein anderes Ziel jedoch ist es, den Speicherbedarf gering zu halten. Durch ein sich anderndes Anfrageverhalten (Queryprol) konnen sich allerdings auch die in der DB zu speichernden Formate andern. Die groe Popularitat des WWW ist wohl auf die einfache Benutzerschnittstelle (Web-Browser) und das stark zunehmende Informationsangebot zuruckzufuhren. Der Anwender kann mit einfachen Maus-Klicks seine Daten erhalten und mu keine komplizierten Kommandos beherrschen. Dies aber hat zur Folge, da die Netze immer starker ausgelastet werden. Fruher, als FTP (File Transfer Protocol) noch den groten Anteil an den Internetdiensten hatte, reichte es aus, den Inhalt von wichtigen Servern zu spiegeln (d.h. Kopien des Inhalts auf Servern in anderen Landern anzulegen), um den Benutzern lange U bertragungszeiten zu ersparen und die Netze zu entlasten. Um im World Wide Web die Ladezeiten zu verkurzen, verwendet man Caches. Die Web-Browser besitzen meist Speicher- und Platten-Caches, die es ihnen ermoglichen aufgerufene Seiten und deren Bestandteile (z.B. Grakelemente) zwischenzuspeichern, um sie bei einem erneuten Aufruf der Web-Seite aus dem lokalen Speicher holen zu konnen. Dadurch entfallt der Verbindungsaufbau zum entfernten Server, die Ladezeit wird verkurzt und das Netz entlastet. Diese lokalen Caches haben den Nachteil, da sie nur von einem Anwender verwendet werden konnen. Diese Einschrankung wird durch die Einfuhrung von Proxy-Servern aufgehoben. Wird ein Proxy-Server { kurz Proxy { verwendet (der Browser mu entsprechend konguriert werden), so wird die Anforderung (Request) eines Dokuments vom Proxy und nicht vom Web-Server abgewickelt. Der Proxy-Server kann somit fur viele Benutzer Daten uber seinen Cache bereitstellen, die er nur beim ersten Request direkt vom Web-Server laden mu. Fur den Anwender ergibt sich somit eine kurzere Ladezeit, und sowohl Netz als auch WebServer werden entlastet. Es gibt allerdings ein Problem bezuglich der Aktualitat der Daten. Hierauf wird in Abschnitt 3.2 naher eingegangen. Wunschenswert ware auch fur den Proxy-Server eine Formatumwandlung und eine zusatzliche Optimierung aufgrund des Queryprols und der Netzlast. W. Kieling, W. Kowarschick und G. Kostler haben in KKK96] ein Konzept zur Optimierung eines solchen Systems vorgestellt. Diese Diplomarbeit befat sich mit dessen Implementierung, wobei nach Rucksprache die Optimierung nur fur den Storage-Servers realisiert wurde beim Proxy-Server wurde lediglich eine Formatumwandlung implementiert. Es werden fur die Realisierung hauptsachlich (frei verfugbare) Standardsoftwarekomponenten verwendet: Squid Version 1.1.5 | Proxy-Server Squid ist unter http://squid.nlanr.net/Squid zu beziehen. KAPITEL 1: EINLEITUNG 4 DB2 for Common Servers Version 2 | relationale Datenbank DB2 ist ein kommerzielles Datenbanksystem von IBM. Apache Version 1.2b4 | Web-Server http://dev.apache.org/ ImageMagick Version 3.9.0 | Konvertierungs-Tool fur Bildformate http://www.wizards.dupont.com/cristy/ImageMagick.html Sox Version 12.12 | Konvertierungs-Tool fur Audiodateien http://pardis.sas.upenn.edu/softlist.de/sox.html Netscape Gold Version 3.0.1 | Web-Browser http://home.netscape.com/ Allerdings wurden am Quellcode des Proxy-Servers einige A nderungen und Erganzungen vorgenommen, um die zusatzlichen Aufgaben zu erfullen. Die Anbindung der Datenbank an den Web-Server sowie die Optimierung der Speicherformate, der Datenbank erfolgt uber C-Programme mit Embedded SQL. Dies wird ausfuhrlich in Kapitel 3 besprochen. Kapitel 2 Grundlagen In diesem Kapitel erfolgt zunachst ein kurzer geschichtlicher U berblick uber die Entstehung des Internet und des World Wide Web, sowie eine kleine Einfuhrung in das im WWW verwendete HTTP-Protokoll. 2.1 Internet und WWW Der Name Internet ist eine Abkurzung fur Interconnected Networks. Es handelt sich um einen Zusammenschlu von lokalen, nationalen und globalen Computernetzen, die durch das TCP/IP-Protokoll (Transmission Control Protocol/Internet Protocol) untereinander verbunden sind. Das Internet verdankt seine Entstehung dem US-Verteidigungsministerium, das Ende der sechziger Jahre ein dezentral funktionierendes Netzwerk fur militarische Zwecke forderte, das selbst bei groeren Ausfallen noch eine Datenubermittlung gewahrleisten sollte. 1969 entstand ein Computernetz mit dem Namen ARPANET (Advanced Research Projects Agency-NET). Als dieses Netz 1972 der O entlichkeit vorgestellt wurde, schlossen sich auch Universitaten diesem Netz an. 1983 trennte sich der militarische Teil ab es entstand das MILNET, und aus dem ARPANET wurde das INTERNET. Anfang der neunziger Jahre begann die Kommerzialisierung des Internet. Im Internet gibt es zahlreiche Dienste: e-Mail Newsgroups IRC (Internet Relay Chat) 5 2.2 Das Hypertext Transfer Protocol (HTTP) 6 FTP (File Transfer Protocol) Telnet (Teletype Network) Gopher WAIS (Wide Area Information Servers) WWW (World Wide Web) Das World Wide Web ist der jungste, aber spektakularste aller Internet-Dienste. Das WWW wurde 1993 am CERN-Institut in der Schweiz entwickelt und war ursprunglich nur fur interne Zwecke bestimmt. Aufgrund seiner einfachen und exiblen Nutzbarkeit wurde es weltweit verfugbar gemacht und hat sich seither zum erfolgreichsten Internet-Dienst entwickelt. 2.2 Das Hypertext Transfer Protocol Das Hypertext Transfer Protocol (vgl. FGM+97]) ist das Kommunikationsprotokoll, das im WWW zwischen Clients (Browsern) und Servern benutzt wird. Es nutzt das TCP (Transmission Control Protocol) als Transport-Protokoll. Bei der Verwendung eines Web-Browsers bleibt dem Endanwender verborgen, was sich auf HTTP-Ebene abspielt. Deshalb wird hier anhand eines kleinen Beispiels der Ablauf eines Request aufgezeigt. Dies ist fur das Verstandnis des Proxy-Servers und der CGI-Programme (Common Gateway Interface) von Vorteil. Beispiel (HTTP-Request) Ruft man von einem Web-Browser aus die Homepage des Instituts fur Informatik an der Universitat Augsburg auf, so genugt es, den URL (Uniform Resource Locator) http://www.informatik.uni-augsburg.de/ einzugeben, und man erhalt das Ergebnis angezeigt. Baut man aber eine Telnet-Verbindung zum Institutsserver auf, so erhalt man den HTML-Code (Hypertext Markup Language) der Web-Seite mit zusatzlichen Protokolldaten (HTTP). Dazu mu zunachst einmal eine Verbindung zum HTTPPort (standardmaig Port 80) des Web-Servers aufgebaut werden. Dies geschieht mit dem Befehl: telnet www.informatik.uni-augsburg.de 80 2.2 Das Hypertext Transfer Protocol (HTTP) 7 Um die Seite anzufordern, genugt die Eingabe von GET http://www.informatik.uni-augsburg.de/ HTTP/1.0 gefolgt von einer Leerzeile1, um den Befehl abzuschlieen. Das Ergebnis sieht dann wie folgt aus: HTTP/1.1 200 OK Date: Wed, 24 Sep 1997 07:11:42 GMT Server: Apache/1.2b4 Connection: close Content-Type: text/html Last-Modified: Fri, 29 Aug 1997 11:42:19 GMT ETag: "c85-1391-3406b59b" Content-Length: 5009 Accept-Ranges: bytes <HTML> <HEAD> <META HTTP-EQUIV="Content-Type" CONTENT="text/html charset=iso-8859-1"> <META NAME="GENERATOR" CONTENT="Mozilla/4.01 de] (Win95 I) Netscape]"> <TITLE> Universit&aumlt Augsburg - Institut f&uumlr Informatik </TITLE> </HEAD> ... Hier ist deutlich zu erkennen, da zunachst der HTTP-Header und anschlieend, durch eine Leerzeile getrennt, die HTML-Seite ubertragen wird. Die wichtigsten Header-Zeilen haben dabei folgende Bedeutung: HTTP/1.1 200 OK Typ und Version des Protokolls gefolgt vom Ruckgabecode. Date: Wed, 24 Sep 1997 07:11:42 GMT Systemzeit des Servers wird zusammen mit Last-Modied oder Expires fur die Bestimmung der Gultigkeitsdauer einer Web-Seite verwendet. Content-Type: text/html MIME-Type (vgl. FB96]) des Dokuments bestehend aus einem media-type (z.B.: text, image, sound oder application) und einem subtype. Anhand des Content-Type entscheidet der Web-Browser, wie er die Daten zu behandeln hat. Eine Auswahl gultiger Typen ist in Tabelle B.1 zu nden. Last-Modied: Fri, 29 Aug 1997 11:42:19 GMT Zeitpunkt der letzten A nderung wird zusammen mit Date fur die Bestimmung der Gultigkeitsdauer der Web-Seite verwendet. 1 Im HTTP wird der Header durch eine Leerzeile von den Daten getrennt 2.2 Das Hypertext Transfer Protocol (HTTP) 8 Content-Length: 5009 Die Lange des anschlieenden Datenbereichs in Bytes. Aus historischen Grunden ist die Verwendung von Headern optional (in HTTP 0.9 gab es sie noch nicht), sie dienen aber dazu, den Funktionsumfang der beteiligten Anwendungen zu erhohen. Darauf wird in Abschnitt 3.2, bei Erklarung des Squid, naher eingegangen. Um eine einfache Erweiterbarkeit von HTTP zu gewahrleisten, mussen alle Programme ihnen unbekannte Header ignorieren. Kapitel 3 Architektur In diesem Kapitel werden die Grundlagen und die Einzelkomponenten des Systems beschrieben, und es wird auf die Probleme eingegangen, die sich bei der Realisierung ergeben haben. Client Storage-Server Proxy-Server Web-Server CGI-Programm B D e ch Ca Abbildung 3.1: Systemarchitektur Wie aus Abbildung 3.1 hervorgeht, besteht das System aus: Client Proxy-Server mit Cache Storage-Server Der Storage-Server umfat dabei folgende Komponenten: 9 3.1 Der Client 10 Web-Server CGI-Programm Datenbank 3.1 Der Client Der Client stellt die Schnittstelle zum Anwender dar. Er hat nur die Aufgabe, dem Benutzer die Auswahl der gespeicherten Dokumente und der zur Verfugung stehenden Formate zu ermoglichen. Hierzu kann jeder gangige Web-Browser (z.B.: Netscape Navigator, Internet Explorer, . . . ) verwendet werden. Die Anbindung der Datenbank wird dabei durch ein CGI-Programm realisiert, das dynamisch eine HTML-Seite erzeugt. Diese Seite enthalt die in der Datenbank gespeicherten Daten innerhalb von Listenfeldern und bietet eine Auswahlmoglichkeit, um die Dokumente anzufordern (siehe Abb. A.4). Desweiteren ist der Browser auch in der Lage, einige der verwendeten Formate anzuzeigen (Abb. A.5), externe Programme zu starten, die dies ermoglichen, oder das Dokument auf die Festplatte zu speichern. Fur die Beispiele wurde Netscape Gold V3.01 als Client-Software verwendet. 3.2 Der Proxy-Server Hier werden zunachst die Vorteile des Einsatzes von Proxy-Servern erlautert. Anschlieend wird anhand der verwendeten Software auf die Probleme eingegangen, die durch zusatzliche Anforderungen entstehen, und die Losungsmethoden werden vorgestellt. Wird die Anfrage eines Clients an den Storage-Server uber einen Proxy-Server abgewickelt, so sind folgende Ablaufe moglich: 1. Der Client stellt seine Anfrage an den Proxy der Proxy stellt fest, da das Dokument nicht im Cache liegt (CACHEMISS) und leitet die Anfrage an den Storage-Server weiter. Dieser liefert das gewunschte Dokument an den Proxy-Server. Der Proxy-Server speichert das Dokument im Cache ab und leitet es an den Client weiter (siehe Abbildung 3.2). 2. Der Client stellt seine Anfrage an den Proxy der Proxy hat das geforderte Dokument im Cache (CACHEHIT) und liefert es direkt an den Client (siehe Abbildung 3.3). 3.2 Der Proxy-Server CLIENT 11 PROXY/CACHE STORAGE-SERVER Zeit Anfrage Antwort Abbildung 3.2: CACHEMISS Die Vorteile eines Proxy-Servers werden hieraus gut ersichtlich. Bei einem `cachemiss' treten fur den Client kaum spurbare Verzogerungen auf. Bei einem `cachehit' erfolgt jedoch eine wesentlich schnellere Antwort, die Netzlast zwischen Proxyund Storage-Server wird reduziert und der Storage-Server entlastet. Es ist allerdings auch zusatzlicher Verwaltungsaufwand erforderlich. So mu der Proxy-Server beispielsweise die Gultigkeit seines Cache-Inhalts uberprufen. Dies kann durch einen mitgelieferten Expires-Header erfolgen oder durch eine Anfrage beim Web-Server, ob das Dokument seit dem letzten Zugri geandert wurde. Da ein Web-Server auch einen Date-Header sendet, wirkt sich die lokale Systemzeit nicht auf die Gultigkeitsdauer eines Dokuments aus. Die Gultigkeitsdauer entspricht der Dierenz aus Expires- und Date-Header. Erfolgt eine Anfrage an ein Dokument im Cache des Proxy-Servers, uber dessen Gultigkeit nichts bekannt ist, so stellt der Proxy einen bedingten GET-Request, der einen If-Modified-SinceHeader enthalt, an den Web-Server dieser liefert das Dokument nur, wenn es neuer ist als das des Proxy-Servers. Dies ist bei dynamisch erzeugten Seiten immer der Fall. Deshalb mu das in Abschnitt 3.3.2 beschriebene CGI-Programm db interface einen Expires-Header erzeugen, um den Proxy-Servers die M oglichkeit zu geben, gespeicherte Dokumente aus dem Cache zu lesen und nicht erneut uber das Netz anzufordern. Fur einen Proxy-Server ergibt sich auch die Frage, welche Web-Seiten in den Cache ubernommen werden sollen. Als Grundsatz gilt, da nur solche URLs `gecached' werden, die bei wiederholtem Aufruf ein identisches Ergebnis liefern. Also insbesondere: 1. keine POST-Requests (nur GET-Requests) 3.2 Der Proxy-Server CLIENT 12 PROXY/CACHE STORAGE-SERVER Anfrage Zeit (kein Netzverkehr) Antwort Abbildung 3.3: CACHEHIT 2. keine URLs, die cgi-bin oder `?' enthalten 3. keine Dokumente mit einem Authorization-Header -Requests enthalten Parameter im Rumpf somit kann die Anfrage an einen URL ganz unterschiedliche Ergebnisse liefern. URLs, die cgi-bin oder `?' enthalten, deuten auf eine von einem CGI-Programm erzeugten Web-Seite hin. Diese kann bei wiederholter Anfrage unterschiedlich sein, wenn sich beispielsweise der Inhalt der Datenbank andert, auf die durch das Programm zugegrien wird. Fur Dokumente, die einer Autorisierung bedurfen, mu diese naturlich vom WebServer durchgefuhrt werden und nicht vom Proxy. POST Squid Squid ist ein hierarchischer Proxy-Server, der neben Eltern (parents) auch Nachbarn (neighbors) besitzen kann dies ermoglicht den Aufbau von Cache-Verbunden wie de-cache1 . Die Eltern und Nachbarn werden bei einem `cachemiss' in die Suche nach dem Dokument integriert. Dadurch erhoht sich die virtuelle Cache-Groe und damit auch die Treerrate. Desweiteren ist Squid ereignisgesteuert, d.h. Squid hort alle Kanale nach eingehenden Daten ab. Anfragen werden dann durch Handler-Funktionen bearbeitet dies geschieht aber nur in kleinen Teilstucken, um alle Requests gleichwertig zu behandeln. Diese Vorgehensweise ist zwar sehr Informationen zu de-cache gibt es in World Wide Web unter http://www.informatik.uni-bonn.de/dv/www/de-cache/ 1 der Adresse 3.2 Der Proxy-Server 13 sinnvoll, erschwert aber das Verstandnis des Programmablaufs und somit das Erweitern des Programms, zumal auch keine verwendbare Dokumentation fur Entwickler vorliegt. Wie eben gezeigt, vermeidet man es, dynamisch erzeugte Web-Seiten in den Cache aufzunehmen. Squid bietet dazu die Moglichkeit, in der Kongurationsdatei hinter dem Schlusselwort cache stoplist Zeichenketten anzugeben, deren Vorkommen im URL ein `cachen' verhindert. cache_stoplist cgi-bin ? Dies wirft allerdings ein groes Problem auf, da die vom Storage-Server gelieferten Daten das Ergebnis eines CGI-Programmes sind und somit cgi-bin und `?' im URL aufweisen. Um die Dokumente trotzdem `cachen' zu konnen, gibt es zwei Losungsmoglichkeiten. `cachen' aller dynamisch erzeugten Seiten nur die dynamisch erzeugten Seiten eines speziellen Servers `cachen' Die erste Moglichkeit ware zwar bequem und durch einfaches A ndern der Kongurationsdatei realisierbar es kann aber nicht das Ziel sein, alle dynamisch erzeugten Seiten zu `cachen'. Die zweite Alternative erfordert zwar einige A nderungen am Programmcode, bietet jedoch eine prazise Auswahl der `cachbaren' Seiten. Die Realisierung besteht aus der Einfuhrung eines neuen Schlusselwortes (multimediaserver) und einer damit verbundenen Zeichenkette in der Kongurationsdatei, die notwendige Unterstutzung in der Kongurationsroutine und einem zusatzlichen Stringvergleich bei der Entscheidung der `Cachebarkeit'. Steht die Zeile multimediaserver http://www.informatik.uni-augsburg.de:1082/ cgi-bin/db interface in der Kongurationsdatei des Proxy-Servers, so werden alle Web-Seiten, die vom CGI-Programm db interface des Storage-Servers erzeugt werden, in den Cache ubernommen, obwohl sie cgi-bin im URL enthalten. Die Integration von Konvertierungsprogrammen in den Proxy-Server besteht aus mehreren Teilaufgaben. Verwaltung der Abhangigkeiten unter den Formaten incl. Umwandlungsfunktionen Erkennen von Dokumenten, die aus einem im Cache vorhandenen Dokument durch Konvertierung erzeugt werden konnen 3.2 Der Proxy-Server 14 Austausch der Daten zwischen Proxy-Server und Konverterproze Generierung und Verwaltung von Kindprozessen, die die Konvertierung durchfuhren Bemerkung Im folgenden mu streng zwischen Konverter- und Konvertierungsproze (bzw. -programm) unterschieden werden. Ein Konverterproze ist ein Kindproze des Proxy-Servers er regelt die Verarbeitung der Header und ruft den Konvertierungsproze auf. Ein Konvertierungsproze ist der Kindproze eines Konverterprozesses er fuhrt die eigentliche Formatumwandlung durch. Dazu wird er von einem Konvertierungsprogramm, das ein Filter2 sein mu, uberlagert. Die Verwaltung der Umwandlungsfunktionen wird durch Eintrage in der Kongurationsdatei ermoglicht, wobei das Schlusselwort convert am Zeilenanfang einen solchen Eintrag einleitet. Die weiteren Felder stehen fur das Quellformat, das Zielformat, den MIME-Typ des Zielformats und die Umwandlungsfunktion. convert jpeg gif image/gif convert - gif:- Die vorangegangene Zeile z.B. bedeutet, da das jpeg-Format mit dem Kommando convert - gif:- ins gif-Format umgewandelt werden kann. Der neue MIME-Typ ist image/gif dieser mu im Content-Type-Header an den Client ubergeben werden. Diese Daten werden im Programm in einer einfach-verketteten Liste abgespeichert. Die einzelnen Knoten haben dabei folgende Struktur: typedef struct convert { char *src_type char *dest_type char *dest_mime char *command struct convert *next }convert_t 2 /* /* /* /* /* Quellformat Zielformat MimeType des Zielformats Konvertierungsfunktion naechster Eintrag */ */ */ */ */ Programme, die von Standardeingabe lesen und auf Standardausgabe schreiben 3.2 Der Proxy-Server 15 Diese Liste wird auch bei der Suche nach Dokumenten im Cache verwendet. Wird ein Dokument angefordert, dessen URL mit der durch multimediaserver spezizierten Zeichenkette beginnt, ist es moglich, dieses Dokument durch Formatumwandlung aus einem im Cache bendlichen Dokument zu generieren. Dazu wird, falls der URL nicht im Cache gefunden wird, nach Cache-Eintragen gesucht, deren Format in das angeforderte konvertiert werden kann. Dies geschieht durch Generierung neuer URLs, bei denen gegenuber des alten die Formatangabe ausgetauscht wurde, wobei das Format des ursprunglichen URL in der dest type Komponente eines Listenelements stehen mu. Der Inhalt der zugehorigen src type Komponente wird im neuen URL verwendet. Erfolgt z.B. eine Anfrage an den URL http://www.informatik.uni-ausgburg.de:1082/cgi-bin/ db interface?name=dilbert2&format=jpeg und stehen in der Kongurationsdatei die Zeilen convert convert convert convert tiff gif ppm png jpeg jpeg jpeg jpeg image/jpeg image/jpeg image/jpeg image/jpeg convert convert convert convert - jpeg:jpeg:jpeg:jpeg:- so wird im Cache auch nach URLs mit folgender Endung gesucht: .../cgi-bin/db .../cgi-bin/db .../cgi-bin/db .../cgi-bin/db interface?name=dilbert2&format=tiff interface?name=dilbert2&format=gif interface?name=dilbert2&format=ppm interface?name=dilbert2&format=png Die Suchreihenfolge entspricht dabei der Reihenfolge der convert-Eintrage in der Kongurationsdatei. Somit existiert eine Praferenzordnung unter den Konvertierungsfunktionen. Die im Cache abgelegten Dokumente enthalten auch den vom Web-Server gelieferten HTTP-Header dieser mu im Fall eines `cachehit' mit dem Dokument an den Client weitergegeben werden. Wird ein Dokument aus dem Cache geholt und konvertiert, mu der Content-Type-Header an das neue Format angepat werden. Diese Aufgabe wird vom Konverterproze ubernommen. Die dazu benotigten Informationen werden durch einen zusatzlichen Header, der im weiteren Verlauf als Convert-Header bezeichnet wird, ubertragen. Dieser besitzt die beiden Eintrage Command und Mime-Type, welche folgende Bedeutung haben: Command: convert - gif:- Programm zur Formatumwandlung incl. Kommandozeilenparameter. 3.2 Der Proxy-Server 16 Mime-Type: image/gif Content-Type des vom Konvertierungsproze zu liefernden Formats. Der Convert-Header wird durch eine Leerzeile vom HTTP-Header getrennt dies kann folgendes Aussehen haben, falls das Dokument aus dem Cache das jpegFormat besitzt und die Anfrage fur das gif-Format erfolgt: Command: convert - gif:Mime-Type: image/gif HTTP/1.1 200 OK Date: Thu, 18 Sep 1997 14:01:39 GMT Server: Apache/1.2b4 Connection: close Content-Type: image/jpeg Expires: Fri, 14 Nov 1997 14:01:39 GMT Der Konverterproze erhalt uber seine Standardeingabe den Convert-Header, den HTTP-Header und die zu konvertierenden Daten. Der Inhalt des Convert-Headers wird ausgewertet und fur die spateren Aktionen gespeichert. Der HTTP-Header wird unverandert ausgegeben, soweit es sich nicht um den Content-Type-Eintrag handelt, welcher durch den im Convert-Header ubergebenen Mime-Type ersetzt und ausgegeben wird. Der anschlieend erzeugte Kindproze (Konvertierungsproze) verarbeitet die restliche Eingabe { also die eigentlichen Daten. Dabei wird der Konvertierungsproze von dem im Command-Eintrag des Convert-Header angegebenen Kommando (in diesem Beispiel: convert - gif:-) uberlagert dieses fuhrt dann auch die Konvertierung durch und liefert das Ergebnis auf stdout. Da Unix uber eine gepuerte Ausgabe verfugt, mu darauf geachtet werden, da der Header komplett ausgegeben wurde, bevor die Ausgabe der Daten beginnt. Der Konverterproze liefert fur das aktuelle Beispiel den folgenden HTTP-Header, gefolgt von dem ins gif-Format konvertierten Dokument: HTTP/1.1 200 OK Date: Thu, 18 Sep 1997 14:01:39 GMT Server: Apache/1.2b4 Connection: close Content-Type: image/gif Expires: Fri, 14 Nov 1997 14:01:39 GMT Die Formatumwandlung ist bei der vorliegenden Implementierung auf einfache Filterfunktionen beschrankt. Im Abschnitt 3.3.2 wird die Konvertierung im Bereich des Storage-Servers mittels einer Reihe von durch Pipes verbundene Filterfunktionen realisiert. Dies kann beim Proxy-Server durch Erweiterungen des Programms converter (Quellcode siehe Anhang C.1 Seite C-1) erreicht werden. 3.3 Der Storage-Server 17 Fur die Verwaltung der Konverterprozesse wurde die Kongurationsdatei um zwei zusatzliche Eintrage erweitert. Die Zeile: convert program /home/wiss/db2ex/squid/bin/converter gibt den Pfad und den Namen des zu verwendenden Konverterprogramms an, wahrend die Zeile: conv children 5 die Anzahl der zu startenden Konverterprozesse festlegt. Die Konverterprozesse werden beim Start oder bei einer Neukonguration des Squid gestartet. Alle Konverterprozesse laufen parallel zum Proxy-Server ab. Sie fragen in einer Endlosschleife die Eingabe ab und geben nach erfolgter Konvertierung die Daten an den Proxy zuruck. Bemerkung (Proxyeinsatz) Soll ein Proxy-Server verwendet werden, mu der Client (Web-Browser) entsprechend konguriert werden. In den Abbildungen A.1 bis A.3 sind diese Schritte fur den Netscape Navigator dargestellt. Dabei wird der Proxy des Rechners ls2cip01.informatik.uni-augsburg.de unter Port 3128 f ur HTTP-Requests eingetragen. Ist die Verwendung eines Proxy-Servers eingestellt, gehen alle Anfragen an diesen und nicht mehr an den im URL spezizierten Web-Server. Der Proxy leitet die Requests an andere Proxy-Server oder den Web-Server weiter, oder aber holt das Dokument aus seinem Cache. 3.3 Der Storage-Server Der Storage-Server ist nur ein theoretisches Konstrukt. Er speichert die Dokumente und bietet dem Benutzer Zugrismoglichkeiten darauf. Dabei bleibt die Implementierung dem Endanwender weitgehend verborgen. Die Komponenten des Storage-Servers umfassen einen Web-Server, eine Datenbank und ein CGIProgramm, das die Verbindung zwischen beiden herstellt. 3.3.1 Der Web-Server Der Web-Server bearbeitet HTTP-Anfragen des Clients und liefert entsprechende Dateien zuruck. Der Apache Server wird uber zwei Dateien (httpd.conf und srm.conf) konguriert. Die wichtigsten Eintrage in httpd.conf sind dabei: 3.3 Der Storage-Server 18 Port 1082 Die Nummer des Ports, unter der der Web-Server erreichbar ist. Der Standardport fur HTTP ist 80, aber fur Ports kleiner als 1023 sind Root-Rechte erforderlich. User db2ex Group db2grp Benutzer- und Gruppenname, unter denen der Web-Server laufen soll. ServerAdmin [email protected] E-Mail Adresse des Administrators, an den Probleme gemeldet werden sollen. ServerRoot /home/wiss/db2ex/mywork Das Verzeichnis, das Kongurations-, Fehler- und Log-Dateien enthalt. ServerName www.informatik.uni-augsburg.de Der Servername, der an den Client zuruckgeliefert werden soll. Es mu sich dabei um einen gultigen DNS Namen des Rechners handeln. Die Datei srm.conf enthalt unter anderem folgende Eintrage: DocumentRoot /home/wiss/db2ex/html dir Das Verzeichnis, unter dem die Web-Seiten abgelegt sind. UserDir public html Der Verzeichnisname, unter dem Benutzer in ihrem Homeverzeichnis eigene Webseiten ablegen konnen. DirectoryIndex index.html Index.html welcome.html Welcome.html Standarddateien, die geladen werden, wenn in der Anfrage keine Datei speziziert wurde. ScriptAlias /cgi-bin/ /home/wiss/db2ex/mywork/cgi-bin/ Erkennungsmerkmal fur CGI-Programme und Pfad, unter dem sie abgelegt sind. An den Web-Server konnen drei Arten von Anfragen gestellt werden. 1. Anfragen an eine Datei im DocumentRoot-Verzeichnis oder einem Unterverzeichnis davon. http://www.informatik.uni-augsburg.de:1082/Index.html Index.html /home/wiss/db2ex/html dir www.informatik.uni-augsburg.de Liefert die Datei des Rechners unter Port 1082 lauft. im Verzeichnis uber den Web-Server der 3.3 Der Storage-Server 19 2. Anfragen an eine Datei im UserDir-Unterverzeichnis des Homeverzeichnisses eines Benutzers. http://www.informatik.uni-augsburg.de:1082/~db2ex/Index.html Index.html /home/wiss/db2ex/public html /home/wiss/db2ex db2ex Die Datei im Verzeichnis wird ubertragen, wobei das Homeverzeichnis des Benutzers ist. 3. Anfragen an ein CGI-Programm. http://www.informatik.uni-augsburg.de:1082/cgi-bin/dynform dynform /home/wiss/db2ex/mywork/cgi-bin Dadurch wird auf dem Web-Server das Programm im Verzeichnis gestartet und dessen Ausgabe uber den Web-Server an den Client ubertragen. 3.3.2 Das CGI-Programm Das Common Gateway Interface (CGI) ist eine Schnittstelle, die es dem Client erlaubt, Programme auf dem Web-Server auszufuhren3 und auch Parameter an diese zu ubergeben. Damit lassen sich dynamisch Web-Seiten generieren oder Datenbankzugrie ermoglichen. Die dabei verwendete Programmiersprache ist nicht von Bedeutung. Haug wird Perl verwendet, da es uber machtige Zeichenkettenoperationen verfugt. Wegen der notigen Datenbankanbindung und der benotigten Betriebssytemkomponenten wird hier C benutzt. Es werden zwei CGI-Programme verwendet, um die Verbindung zwischen Datenbank und Web-Server zu realisieren. Da ein CGI-Programm vom Web-Server gestartet wird, stehen nicht alle Umgebungsvariablen zur Verfugung, was der Fall ware, wenn der Aufruf aus einer Shell erfolgt. Fur Zugrie auf eine DB2Datenbank mu die Environment-Variable DB2INSTANCE gesetzt sein. Dies geschieht durch die Zeile putenv("DB2INSTANCE=db2ex") in den C-Programmen. Das Programm dynform (Quellcode in Anhang C.2) erzeugt als Ausgabe eine HTML-Seite, die ein Formular enthalt. Das Formular spiegelt den Inhalt des Storage-Server wider und dient zur Auswahl des Dokuments. In Anhang C.7 ist die Ausgabe des CGI-Programms im HTML-Code (vgl. Bor96]) dargestellt. Die von Netscape geladene Seite ist in Abbildung A.4 zu sehen. Wird vom Benutzer die Abschicken-Schaltache betatigt, so wird das zweite CGIProgramm gestartet und mit den ausgewahlten Listenelementen als Parameter versorgt. Die dafur erforderliche HTTP-Anfrage lautet: 3 Der Web-Server mu naturlich entsprechend konguriert sein, um dies zu erlauben. 3.3 Der Storage-Server 20 GET http://www.informatik.uni-ausgburg.de:1082/cgi-bin/ db interface?name=dilbert2&format=jpeg HTTP/1.0 Neben dem hier verwendeten GET-Request gibt es noch den POST-Request. Sie unterscheiden sich hauptsachlich in der Art der Parameterubergabe. Die Vorund Nachteile dieser Requestarten kann in Gun96] nachgelesen werden. Mit dem uncgi-Tool von Steven Grimm, das im World Wide Web unter der Adresse http://www.hyperion.com/~koreth/uncgi.html zu nden ist, lassen sich POST- und GET-Requests einheitlich behandeln. Die Parameter werden dabei verarbeitet und in Umgebungsvariablen gespeichert, deren Name aus WWW gefolgt von Parameternamen besteht. Fur das Programm db interface bedeutet das, da Name und Format des angeforderten Dokuments in den Environment-Variablen WWW name bzw. WWW format abgelegt sind und mit der Funktion getenv() ausgelesen werden konnen. Um ein Dokument aus der Datenbank zu holen, wird zunachst eine einfache SQLQuery benutzt. Das Attribut Inhalt wird dabei wegen der Fahigkeiten von DB2 (siehe Abschnitt 3.3.3) beim Verarbeiten von BLOBs in einer temporare Datei abgelegt. SELECT a.inhalt,f.mimetype,f.fid FROM artikel a, formate f WHERE a.fid=f.fid AND a.name=:docname AND f.name=:doctype Ist diese Anfrage erfolgreich, so wird ein HTTP-Header auf stdout ausgegeben und durch eine Leerzeile getrennt4 der Inhalt der temporaren Datei hinterher geschickt. Der Header kann dabei folgendes Aussehen haben: Content-type: image/gif Expires: Fri, 14 Nov 1997 13:07:31 GMT Content-length: 8728 Der Expires-Header ist erforderlich, um den Proxyserver das `Cachen' des Dokuments zu ermoglichen. Darauf wurde in Abschnitt 3.2 schon eingegangen. Falls das Dokument nicht im gewunschten Format abgespeichert ist, sondern durch Umwandlung erzeugt werden mu, liefert die erste Anfrage einen Fehler. In diesem Fall wird die etwas komplexere zweite Anfrage verwendet. 4 Darauf wurde in Abschnitt 2.2 schon eingegangen. 3.3 Der Storage-Server 21 SELECT a.inhalt, u.funktion, f.mimetype, a.fid, f.fid FROM artikel a, umwandlung u, formate f WHERE a.fid=u.quellformat AND u.zielformat=f.fid AND a.name=:docname AND f.name=:doctype AND u.praeferenz=(SELECT max(u1.praeferenz) FROM umwandlung u1, artikel a1 WHERE a1.name=a.name AND u1.zielformat=f.fid AND u1.quellformat=a1.fid) U ber die Spalte praeferenz wird diejenige Konvertierungsfunktion ausgewahlt, die in der optimalen Basis (siehe Abschnitt 4.1) enthalten ist und ein physikalisch reprasentiertes Format in das vom Client geforderte umwandeln kann. Gleichzeitig wird das gewunschte Dokument in dem fur die Konvertierung benotigten Format in eine temporare Datei ausgelesen. Fur die erforderliche Formatumwandlung werden Kindprozesse gestartet und ihre Ein- und Ausgabekanale uber Pipes verbunden (vgl. Her96]). Dies ist auch der Grund, fur die Konvertierungsprogramme zu fordern, da sie als Filter arbeiten. Wurde die SQL-Query als Ergebnis fur die Umwandlungsfunktion z.B. Filter 1 | Filter 2 | Filter 3 liefern, so h atte das einen Ablauf wie in Abbildung 3.4 zur Folge. Pipe CGIProgramm Filter_1 Kindprozeß 1 Pipe Filter_2 Kindprozeß 2 Pipe Filter_3 Kindprozeß 3 Pipe Abbildung 3.4: Ablauf der Formatumwandlung Falls die Umwandlungsfunktion Pipe-Symbole (`|') enthalt, mu sie in ihre einzelnen Befehle aufgespaltet werden. Dann mussen der Befehlsname und die zu- 3.3 Der Storage-Server 22 gehorigen Parameter in die entsprechenden Variablenkonstrukte fur die UNIXSystemaufrufe gebracht werden. Anschlieend wird die entsprechende Anzahl an Kindprozessen gestartet diese werden dabei durch Pipes verknupft. So wird die Ausgabe des einen Prozesses zur Eingabe des nachsten. Zu diesem Zeitpunkt konnen die Kindprozesse durch die Umwandlungsprogramme uberlagert werden. Erfolgt nun die Ausgabe des Dokuments (Inhalt der temporaren Datei) in die Pipe, so kann an deren Ende das umgewandelte Format entnommen werden. Dieses wird im Anschlu an einen HTTP-Header an den Web-Server weitergeleitet. Bemerkung zu den Konvertierungsprogrammen Die Forderung nach Filtern als Konvertierungsprogramme hat mehrere Grunde: einheitliche Aufrufsyntax: Die Programme mussen nicht mit Datein- amen versorgt werden die Ein- und Ausgabe erfolgt immer uber dieselben Kanale. einfache Verknupfung: Sind mehrere Konvertierungen nacheinander erforderlich, so konnen die einzelnen Programme uber Pipes miteinander verbunden werden. keine zusatzlichen Kosten: Es sind keine zeitraubenden Plattenzugrie erforderlich, da die Daten im Hauptspeicher von einem Proze zum anderen weitergegeben werden. Diese Tatsache wirkt sich auch auf die Optimierung im Kapitel 4 aus. Fur die Konvertierung von Bilddaten wurde das eingangs schon erwahnte Programm convert aus dem ImageMagick Paket von John Cristy ausgewahlt, da es eine Vielzahl von Formaten unterstutzt und wie gefordert als Filter eingesetzt werden kann. Um stdin und stdout als Ein- bzw. Ausgabekanal zu verwenden, mu beim Programmaufruf der sonst verwendete Dateiname durch ein Minuszeichen `-' ersetzt werden. Das Format der `Zieldatei' wird durch vorangestelltes und durch Doppelpunkt abgeschlossenes Formatkennzeichen vor dem `Dateinamen' angegeben. Ein konstruiertes Beispiel soll die Verwendung von convert als Filter verdeutlichen: cat dilbert.gif | convert - jpeg:- | xv - Hier wird die Datei dilbert.gif uber die Standardeingabe von convert gelesen und vom gif- ins jpeg-Format umgewandelt, bevor sie uber den Standardausgabekanal an stdin des Viewers xv weitergeleitet wird. Fur die Formatumwandlung der Audiodaten wird das Programm sox verwendet. Laut Handbuch kann sox statt auf Dateien auch auf stdin und stdout operieren. 3.3 Der Storage-Server 23 Versuche, sox als Filter zu verwenden, waren allerdings bisher nicht von Erfolg gekront. Mit dem Programm mkfilter (der Quellcode bendet sich im Anhang C.6) kann jedes Programm, das fur die Ein- und Ausgabe Dateien benutzt, als Filter verwendet werden. Das zu verwendende Programm wird inklusive seiner Aufrufparameter als Parameter an mkfilter ubergeben. Durch das Minuszeichen `-' werden die Optionen fur die Ein- und Ausgabedatei getrennt, d.h. Optionen vor dem Minuszeichen werden beim Programmaufruf vor der Eingabedatei angegeben, die anderen danach. mkfilter liest die Daten von stdin in eine temporare Datei. Anschlieend wird das ubergebene Kommando mit dieser Datei als Eingabedatei und einer weiteren temporaren Datei fur die Ausgabe aufgerufen. Die Ausgabedatei wird nach stdout kopiert, und die temporaren Dateien werden geloscht. Somit ist der Aufruf von mkfilter sox - -t .au mit folgender Befehlsfolge vergleichbar. cat >tmpfile1 sox tmpfile1 -t .au tmpfile2 cat tmpfile2 rm tmpfile1 rm tmpfile2 Letztere Methode hat allerdings den Nachteil, da teure Plattenzugrie durchgefuhrt werden. Aus Grunden der Performance sind immer Filter vorzuziehen, die keine Plattenzugrie benotigen. 3.3.3 Die Datenbank DB2 { eine "objekt-relationale\Datenbank DB2 von IBM ist eine relationale Datenbank mit objekt-relationalen Erweiterungen. Zu diesen Erweiterungen zahlen: Trigger User-Dened Functions (UDF) User-dened Distinct Types (UDT) Large Objects (LOB) 3.3 Der Storage-Server 24 Ein Trigger besteht aus einer Menge von Aktionen (nicht notwendigerweise Datenbankoperationen), die ausgefuhrt werden, wenn auf der Datenbank Veranderungen stattnden dies geschieht durch Insert-, Delete- oder Update-Anweisungen. Da Select-Anweisungen keine Trigger auslosen, konnen diese nicht zum Protokollieren der Datenbankzugrie verwendet werden. Durch User-Dened Functions ist der Anwender in der Lage, eigene Erweiterungen fur SQL zu entwickeln. UDFs erlauben auch das U berladen von Funktionen die konkrete Auswahl der passenden Funktion erfolgt uber die Signatur. UDFs werden in dieser Implementierung nicht verwendet, da sie nur auf den Ergebnistupeln der Anfrage arbeiten und selbst keinen weiteren Datenbankzugri erlauben. Es wurde jedenfalls grote Schwierigkeiten bereiten, nach erfolgter Konvertierung sowohl das Dokument im neuen Format als auch die benotigte Rechenzeit als Ergebnis einer UDF zuruckzugeben. UDTs erlauben es auf, bestehenden atomaren Typen (auch auf LOBs) eigene Datentypen zu denieren. Durch User-Dened Functions, die auf diesen UDTs operieren, kann das Verhalten dieser Datentypen genau festgelegt werden. Starke Typisierung gewahrleistet, da UDFs nur auf die UDTs angewendet werden konnen, auf denen sie deniert wurden. Das Casting zwischen UDTs kann, falls dabei zusatzlich eine Umrechnung stattndet soll (z.B. Umwandlung zwichen Millimeter und Inch), mit User-Dened Functions realisiert werden. DB2 verfugt uber drei Arten von LOBs: Character Large Objects (CLOB) CLOBs verwendet man fur Texte, falls die Groe von VARCHAR (4 KByte) uberschritten wird. Der Vergleich von CLOBs mit anderen Texttypen wird von DB2 unterstutzt. Double-Byte Character Objects (DBCLOB) DBCLOBs sind speziell fur die Aufnahme von Texten mit Zeichensatzen, deren einzelne Zeichen durch zwei Bytes kodiert sind. Binary Large Objects (BLOB) BLOBs konnen die verschiedensten Daten beinhalten (z.B.: Bild-, Videooder Audio-Daten) und werden auch fur UDTs verwendet. LOBs konnen bis zu 2 Gigabyte (GB) an Daten aufnehmen es mu aber im Datenbankschema immer die Obergrenze der LOB-Groe angegeben werden. Bei wachsendem Speicherbedarf sind dann A nderungen im Datenbankschema erforderlich. In dieser Implementierung werden die Dokumente, deren Inhalt vielfaltige Formate umfat, die keiner Einschrankung unterliegt, in BLOBs abgespeichert. 3.3 Der Storage-Server 25 Es gibt drei Moglichkeiten, mittels Embedded SQL auf LOBs zuzugreifen. Das LOB kann durch eine Select-Anweisung einer der folgenden Variablen zugewiesen werden: Hostvariable File-Reference-Variable LOB-Locator-Variable Bei einer Zuweisung an eine Hostvariable wird das komplette Objekt innerhalb eines Verbundes im Hauptspeicher des Rechners abgelegt. Dies ist bei groeren LOBs nicht praktikabel, da der Hauptspeicher sehr rasch an seine Grenzen stot. Die Zuweisung an eine File-Reference-Variable erzeugt eine Datei, die den Inhalt des LOBs aufnimmt. Der Dateiname mu zuvor in der name-Komponente der File-Reference-Variable abgelegt werden. Eine Zuweisung an eine LOB-Locator-Variable belat das LOB auf dem Datenbankserver er kann uber diese Variable lediglich referenziert werden. Es ist moglich durch Locator-Variablen Operationen auf den LOBs durchzufuhren und die so veranderten LOBs, wieder in die Datenbank einzufugen. Es besteht auch die Moglichkeit, uber Locator-Variablen, LOBs in kleinen Teilstucken aus der Datenbank auszulesen. Durch das anschlieende Programmsegment wird ein BLOB in 8 KByte groen Blocken aus der Datenbank gelesen und in einen Ausgabekanal geschrieben. EXEC SQL BEGIN DECLARE SECTION long db_len long first long b_len char docname21] char doctype11] SQL TYPE IS BLOB(8192) blobbuf SQL TYPE IS BLOB_LOCATOR blobloc EXEC SQL END DECLARE SECTION EXEC SQL DECLARE c2 CURSOR FOR SELECT a.inhalt, FROM artikel a, formate f WHERE a.fid = f.fid AND a.name = :docname AND f.name = :doctype EXEC SQL OPEN c2 3.3 Der Storage-Server 26 EXEC SQL FETCH c2 INTO :blobloc EXEC SQL VALUES(LENGTH(:blobloc)) INTO :db_len for(i = 0 i < db_len i += 8192) { first = i + 1 b_len = (db_len - i > 8192) ? 8192 : db_len-i EXEC SQL VALUES( SUBSTR( :blobloc, :first, :b_len ) ) INTO :blobbuf write(fd, blobbuf.data, blobbuf.length) } Dies ware die optimale Methode, um im CGI-Programm auf die BLOBs zuzugreifen, aber durch die verwendete Programmstruktur mit Kindprozessen ist dies nicht moglich. Deshalb werden File-Reference-Variablen verwendet. Bei einer grundlegenden U berarbeitung des CGI-Programms kann die Verwendung von Locator-Variablen ermoglicht werden. Das Datenmodell Fur die Speicherung der Daten genugt ein recht einfaches Schema, wie aus Abbildung 3.5 ersichtlich ist. Es handelt sich um zwei Entitaten und zwei Relationen, wobei Artikel eine schwache Entitat ist. Die Darstellung entspricht der aus LL95]. FID Name hat Format Artikel Formate Name Inhalt wandelt um Funktion MimeType Präferenz Abbildung 3.5: Datenbankschema Ein Artikel besitzt die Attribute Name und Inhalt. Inhalt ist ein BLOB, das die eigentlichen Daten enthalt. Name ist Schlusselattribut und bezeichnet den Dateninhalt. Da ein Artikel aber gleichzeitig in verschiedenen Formaten gespeichert 3.3 Der Storage-Server 27 sein kann, ist fur den tatsachlichen Schlussel noch das Schlusselattribut FID der Entitat Formate notig. In der Entitat Formate werden alle unterstutzten Formate gespeichert. Der kunstliche Schlussel FID (Integer) ist in den Anwendungsprogrammen ezienter als das Attribut Name und kann bei langen Formatbezeichnern auch Speicherplatz einsparen. In Name ist die Typbezeichnung (evtl. Dateiendung) gespeichert, wahrend MimeType den HTTP-Typen des Formats enth alt. Die Relation wandelt um enthalt im Attribut Funktion die komplette Aufrufsyntax des passenden Konvertierungsprogramms. Das Attribut Pr!aferenz dient zur Auswahl der Konvertierungsfunktion im CGI-Programm (siehe Abschnitt 3.3.2) und wird bei der Optimierung (siehe Abschnitt 4.2) gefullt. Die Relation hat Format kann im relationalen Datenbankschema mit in die Tabelle fur Artikel aufgenommen werden. Genau genommen ergibt sich das schon durch die Aufnahme des Schlusselattributs FID. Das sich daraus ergebende relationale Datenbankschema ist in Abbildung 3.6 dargestellt. Artikel Name FID Inhalt Formate varchar(30) FID integer varchar(10) Name MimeType BLOB(200M) Umwandlung integer varchar(100) Quellformat Zielformat Funktion Praeferenz integer integer varchar(200) integer Abbildung 3.6: Das relationale Schema Die Datenbankzugrie werden in einer Tabelle protokolliert, deren Schema in Abbildung 3.7 wiedergegeben ist. Die Bedeutung der einzelnen Attribute (Spalten) Log_Tabelle CL_Name Artikel varchar(100) varchar(30) CL_FID integer DB_FID integer CL_Size integer DB_Size UW_Dauer Datum integer dec(7,3) timestamp Abbildung 3.7: Die Log-Tabelle wird im folgenden erlautert. 3.3 Der Storage-Server 28 CL Name Rechnername mit Domane oder IP-Adresse des Clients, der ein Dokument anfordert. Datensatze die vom Optimierer eingefugt werden, haben die Eintrage 'optimierer' oder 'converter'. Artikel Name des angeforderten Dokuments. Wurde der Datensatz vom Optimierungsprogramm erzeugt, ist hier der Wert 'dummy' eingetragen. CL FID Das Schlusselattribut FID des angeforderten Formats. DB FID Das Schlusselattribut FID des aus der Datenbank gelesenen Formats. Sind CL FID und DB FID verschieden, dann wurde das angeforderte Format CL FID durch Konvertierung aus DB FID erzeugt. CL Size Groe in Bytes des angeforderten Formats. DB Size Groe in Bytes des dazu aus der Datenbank gelesenen Formats. UW Dauer CPU-Zeit in Sekunden, die fur die Konvertierung benotigt wurde. War keine Konvertierung notig (CL FID = DB FID), ist der Wert 0. Datum Datum und Zeitpunkt des Zugris mit Auosung in Mikrosekunden. Die in der Log-Tabelle enthaltenen Informationen bilden die Grundlage der statistischen Daten fur die Optimierung. Darauf wird in Abschnitt 4.2 noch genau eingegangen. Somit kann die gesamte Datenbank durch die folgenden DDL-Befehle erzeugt werden. create table ( FID name mimetype formate integer not null primary key, varchar(10), varchar(100)) create table artikel ( name varchar(30) not null, fid integer not null, inhalt BLOB(200000k), 3.3 Der Storage-Server 29 primary key(name,fid), foreign key(fid) references formate ) create table umwandlung ( quellformat integer not null, zielformat integer not null, funktion varchar(200), praeferenz integer, primary key(quellformat,zielformat), foreign key(quellformat) references formate(fid), foreign key(zielformat) references formate(fid) ) create table log_tabelle ( cl_name varchar(100) not null, artikel varchar(30) not null, cl_fid integer not null, db_fid integer not null, cl_size integer, db_size integer, uw_dauer dec(7,3), datum timestamp not null primary key, foreign key(cl_fid) references formate(fid), foreign key(db_fid) references formate(fid)) Bemerkung Wird ein neues Dokument in die Datenbank aufgenommen, so mu dieses nicht in allen zur Zeit physikalisch reprasentierten Formaten geschehen. Es mu nicht einmal in einem dieser Formate gespeichert werden. Die einzige Forderung an das/die Speicherformat/e ist, da sich alle anderen Formate durch die unterstutzten Konvertierungsfunktionen daraus generieren lassen. Dadurch ergeben sich fur die Benutzer keine Einschrankungen. Das CGI-Programm verwendet bei Anfragen auf dieses Dokument eine geeignete Umwandlungsfunktion, die nicht in der optimalen Basis enthalten sein mu. Nach einer Optimierung ist das neue Dokument wie alle anderen in allen physikalisch reprasentierten Formaten vorhanden. Kapitel 4 Optimierung Ziel der Optimierung ist es, fur die Clients alle unterstutzten Formate bereitzustellen, jedoch bei minimalem Aufwand, d.h. bei einem ausgewogenen Verhaltnis von Speicherbedarf und Rechenzeit. Konkret bedeutet das zu entscheiden, welche Formate abgespeichert und welche berechnet werden. Diese Entscheidung wird durch das Anfrageverhalten der Benutzer beeinut und mu somit regelmaig erneuert werden. In diesem Kapitel werden zunachst die theoretischen Grundlagen der Optimierung des Storage-Servers erlautert. Anschlieend erfolgt die Beschreibung der Implementierung. Auch hier wurde groer Wert auf Flexibilitat gelegt eine Erweiterung der unterstutzten Formate oder der Abhangigkeiten erfolgt durch Einfugen in die Datenbank. 4.1 Theoretische Grundlagen In KKK96] wurden Grundlagen und ein Algorithmus zur Optimierung der Speicherformate aufgrund des Query-Prols eines objekt-orientierten Schemas vorgestellt. Dieser Algorithmus wird hier in abgewandelter Form auf ein relationales Modell angewandt. A ist eine Menge von Attributen (Speicherformaten) Aphys sind die zu speichernden Attribute Acomp sind die zu berechnenden Attribute Es gilt desweiteren A = Aphys _ Acomp. 30 4.1 Theoretische Grundlagen 31 Denition (Functional Constraints) Seien A B Attribute und sei f eine Funktion. Dann nennt man fc (A = f (B )) functional constraint (funktionale Abhangigkeit). A heit Kopfattribut von fc (A = head(fc)). B heit Rumpfattribut von fc (B = body(fc)). Sei F eine Menge von functional constraints. Dann gilt: A hangt direkt von B ab () 9fc 2 F : fc (A = f (B )) Bemerkung Im Gegensatz zu KKK96] hat fc hier nur ein Rumpfattribut. Das ist keine groe Einschrankung, da in der Praxis nur Umwandlungen von einem Speicherformat in ein anderes erfolgen. Denition (Komposition) Seien (A = f (B )) (B = g(C )) 2 F functional constraints. Dann ist auch (A = f (g(C ))) eine functional constraint. Denition (Transitivitat, Irreexivitat) F heit transitiv genau dann, wenn A B C 2 A (A = f (B )) (B = g(C )) 2 F =) (A = h(C )) 2 F F heit irreexiv genau dann, wenn 8A 2 A gilt (A = f (A)) 2= F . (D.h. kein Attribut hangt direkt von sich selbst ab.) Denition (Functional Base) Sei F transitiv und irreexiv und Fcomp eine Teilmenge von F . Dann heit F functional base, falls folgendes gilt: 1. Die Mengen der Kopf- und Rumpfattribute sind disjunkt. (Ah = fhead(fc) j fc 2 Fcompg Ab = fbody(fc) j fc 2 Fcompg =) Ah \ Ab = ) 2. Fur alle Attribute A 2 A gibt es hochstens eine functional constraint mit A als Kopfattribut (j ffc j fc 2 Fcomp A = head(fc)g j 1). Bemerkung Diese Denition ist gleichwertig mit der aus KKK96], aber durch Punkt 1 und der Forderung nach Transitivitat und Irreexivitat von F entfallt der Test auf Zyklenfreiheit. Die Transitivitat und Irreexivitat kann beim Einfugen neuer Constraints sichergestellt werden und mu nicht Bestandteil der Optimierung sein. 4.1 Theoretische Grundlagen 32 Denition (Optimal Funktional Base) Sei cost eine Kostenfunktion und sei Fopt eine functional base, dann ist Fopt optimal, falls cost(Fopt ) = minfcost(Fcomp) j Fcomp ist functional baseg Algorithmus (Optimierung) Im Algorithmus wird zunachst Aphys (und somit auch Acomp) bestimmt, damit sind auch die functional constraints eingeschrankt (head(fc) 2 Acomp body(fc) 2 Aphys). Die Basis mu jetzt nur noch aus dieser Menge gebildet werden. (A F cost): (Aopt Fopt,min) OFB begin min := 1 Aopt := A Fopt := for do Aphys A Acomp := A n Aphys Fcomp := ffc j fc 2 F ^ head(fc) 2 Acomp ^ body(fc) 2 Aphysg for Fbase Fcomp where 8A 2 Acomp : 9_ fc 2 Fbase mit A = head(fc ) do if min then min fi done done end < costs(Aphys Fbase) := costs(Aphys Fbase) Fopt := Fbase Aopt := Aphys Erklarung des Optimierungsalgorithmus Der Optimierungsalgorithmus entscheidet mittels einer Kostenfunktion, welche Formate (Attribute) zukunftig abgespeichert werden sollen und durch welche Funktionen (functional constraints) die restlichen Formate zu berechnen sind. Dazu werden alle Teilmengen aus den Attributen gebildet. Die Elemente dieser Teilmenge sind die physikalisch reprasentierten Attribute alle anderen sind zu berechnen. Diese Einteilung reduziert die Anzahl der functional constraints, die als Kandidaten fur eine functional base in Betracht kommen. Um eine Basis zu erhalten, mu fur jedes zu berechnende Attribut eine functional constraint gewahlt werden, so da dieses Attribut Kopfattribut ist und das Rumpfattribut physikalisch reprasentiert ist. Fur jede gefundene Basis wird die Kostenfunktion 4.1 Theoretische Grundlagen 33 ausgewertet. Die Basis mit den geringsten Kosten ist optimal und entscheidet uber die zukunftigen Speicherformate und die zu verwendenden Konvertierungsfunktionen. Komplexitat Sei n die Anzahl der Attribute (n = j A j). Im schlimmsten Fall { falls jedes Atn tribut von allen anderen abhangt { ergeben sich fur die Optimierung P ni in;i i=0 Alternativen. n n n P n n;i i n P ni in;i n P ni in;i n i=0 i=0 i=0 i 2 3 6 1 057 10 2 237 921 3 10 7 6 322 11 18 210 094 4 41 8 41 393 12 157 329 097 5 196 9 293 608 13 1 436 630 092 In der Realitat werden diese Zahlen aber kaum erreicht, da die Anzahl der Abhangigkeiten geringer sein wird. Bemerkung In KKK96] wird gezeigt, da eine Zerlegung der functional constraints in Zusammenhangskomponenten moglich ist1, und der Algorithmus auf diesen Komponenten durchgefuhrt werden kann. Genau betrachtet ndet dabei eine Zerlegung der Attributmenge statt. Denition (Kostenfunktion) Die Kostenfunktion entspricht weitgehend der Denition aus KKK96] mit Veranderungen bei den Zugriskosten. Die Kosten setzen sich aus Speicher- und Zugriskosten zusammen. Die Konstante z 2 0 1] dient zur Gewichtung der beiden Komponenten. cost(Aphys Fbase) = (1 ; z) store cost(Aphys) + z acc cost(Fbase) Denition (Speicherkosten) Die Speicherkosten sind proportional zur durchschnittlichen Groe des entsprechenden Speicherformats (avg size). avg size wird aus statistischen Daten ermittelt. Durch die Konstante cS wird diese Groe auf einen geeigneten Wert skaliert. X avg size(A) store cost(Aphys) = cS A2Aphys 1 Bilder konnen nicht in Audiodaten umgewandelt werden und umgekehrt 4.2 Realisierung 34 Denition (Zugriskosten) Die Zugriskosten (acc cost) hangen direkt von der Anzahl der Zugrie auf das jeweilige Format (Queryprol) ab. Ist ein Format in der Datenbank gespeichert, so ist acc cost proportional zur durchschnittlichen Groe. Mu ein Format durch Umwandlung erzeugt werden, werden die Zugriskosten durch die Rechenzeit fur die Konvertierung und die Groe des Quellformats bestimmt. Die Konstante cA dient auch hier zum Skalieren. Die Werte fur avg acc, avg size und comp cost werden dabei aus der Zugrisstatistik entnommen. acc cost(Fbase) = wobei X A2A avg acc(A) acc cost(A) ( falls A 2 Aphys cA avg size(A) acc cost(A) = comp cost(A = f (B )) + cA avg size(B ) falls A 2 Acomp Dabei ist (A = f (B )) 2 Fbase und somit auch B 2 Aphys. Bemerkung Im Gegensatz zu KKK96] werden auch die Berechnungskosten (comp cost) aus statistischen Daten ermittelt. Dadurch werden verbesserte Konvertierungsalgorithmen und leistungsfahigere Hardware bei der Kostenbestimmung automatisch berucksichtigt. Fur die Konstanten cS , cA und z haben sich nach ersten Tests folgende Werte als gunstig erwiesen. cS = 0 000 002 cA = 0 000 000 1 z = 0 01 4.2 Realisierung In Abschnitt 4.1 wurde ein Algorithmus zur Optimierung der Speicherformate des Storage-Servers vorgestellt. Dazu werden statistische Daten uber die Groe der Speicherformate, die Dauer der Formatumwandlung und die Anzahl der Zugrie verwendet. Die Speicherformate entsprechen den Attributen im theoretischen Teil und die Konvertierungsfunktionen den functional constraints. Im weiteren Verlauf werden beide Bezeichnungen verwendet. Statistische Daten In der in Abschnitt 3.3.3 eingefuhrte Tabelle log tabelle wird jeder Zugri auf den Storage-Server protokolliert. Diese Datensatze werden vom Optimierungsprogramm zur Bestimmung der statistischen Werte benutzt. Allerdings werden nur 4.2 Realisierung 35 die Daten des letzten Monats berucksichtigt. A ltere Datensatze werden geloscht, um durch einen konstanten Auswertungszeitraum die Gesamtentwicklung der Zugrie erkennen zu konnen. Auerdem werden nach jedem Optimierungslauf Informationen uber die Umwandlungsdauer und die Formatgroe in dieser Tabelle abgelegt. Naturlich werden auch die Daten der letzten Optimierung geloscht, bevor die neuen eingefugt werden. Diese Vorgehensweise ist sinnvoll, da die Datenbank-Tabelle somit aktuelle Statistikdaten enthalt und trotzdem recht klein bleibt. Andernfalls ware folgendes Szenario vorstellbar: Sind beispielsweise aller Bilder im gif-Format gespeichert, wahrend die Anfragen nur fur das jpeg-Format erfolgen, dann sind in der Log-Tabelle Datensatze vorhanden, die den Speicherbedarf fur das gif- und das jpeg-Format sowie die Dauer der Konvertierung angeben. Wird jetzt durch Optimierung jpeg als einziges Speicherformat verwendet, und verlieren die alten Log-Daten an Aktualitat und werden dadurch geloscht, dann gibt es auch keine Informationen uber die Umwandlungsdauer vom gif- ins jpeg-Format. Findet auch kein Zugri im gifFormat mehr statt, so geht auch die Information uber dessen Groe verloren. Die statistischen Daten konnen durch einfache SQL-Queries aus der Datenbank extrahiert werden. Die folgende Query liefert beispielsweise die Anzahl der Requests und die durchschnittliche Groe der Formate: SELECT cl_fid, avg(cl_size), count(cl_fid) FROM log_tabelle GROUP BY cl_fid ORDER BY cl_fid DESC Anfanglich sind die statistischen Daten etwas sparlich, sie werden aber durch die zunehmende Anzahl der Client-Anforderungen und der Optimierungen immer besser und aussagekraftiger. In Kapitel 5 wird anhand eines Beispiels der Fortschritt durch wiederholte Optimierung aufgezeigt. Liegen noch keine Angaben uber die Groe eines Formats vor, so wird diese auf null gesetzt, und es entstehen keine Speicherkosten. Die vom letzten Optimierungslauf erzeugten Eintrage in der Log-Tabelle sorgen fur (geringe) Zugriskosten, was zur Folge hat, da das Format durch die Optimierung abgespeichert wird. Dafur ist aber eine Umwandlung aus einem bisher gespeicherten Format notig. Die dabei anfallenden Daten uber die Dateigroe des Quell- und Zielformats und der durchschnittlichen Rechenzeit werden wieder in die Log-Tabelle eingetragen, wodurch sich die Qualitat der statistischen Daten verbessert. Ist nichts uber die Dauer der Konvertierung zwischen zwei Formaten bekannt, so wird der Mittelwert aus den durchschnittlichen Rechenzeiten fur das entsprechende Zielformat und das entsprechende Quellformat als Abschatzung genommen. Diese Strategie wirkt sich nur bei den ersten beiden Optimierungen aus. 4.2 Realisierung 36 Die gewonnenen Werte sind in der Regel erheblich kleiner als die Rechenzeiten, die tatsachlich erforderlich sind. Dadurch werden solche Formatumwandlungen bei der Optimierung bevorzugt, also eher in die optimale Basis aufgenommen. Somit fuhren spatere Zugrie auf den Storage-Server haug zu Konvertierungen, folglich auch zu neuen, besseren statistischen Daten. Da vom Optimierungsprogramm nur ein Datensatz pro Umwandlungsfunktion eingefugt wird, ist dessen Gewichtung fur die Berechnung der Kosten gering, falls viele Anfragen genau diese Formatumwandlungen erzwingen. Wurde eine Umwandlungsfunktion seit geraumer Zeit nicht mehr verwendet, weil sich die gespeicherten Formate geandert haben, so bleiben durch diesen einen Datensatz die fruher gewonnenen Informationen erhalten. Die Datenstruktur Die grundlegende Datenstruktur fur den Optimierungsalgorithmus bilden einfachverkettete Listen dadurch gibt es im Optimierungsprogramm keine Einschrankungen hinsichtlich der Anzahl der Speicherformate (Attribute) oder der Umwandlungsfunktionen (functional constraints). Die Listen nehmen die Mengen aus dem Algorithmus auf und werden durch Datenbankanfragen gefullt. Es werden drei Arten von Listen verwendet. Variablen vom Typ list t enthalten die Speicherformate (siehe Abbildung 4.1) und statistische Informationen daruber. typedef struct list { long node long size long access double ct_src double ct_dest char is_phys char is_opt struct list * next } list_t gif tiff jpeg /* /* /* /* /* /* /* /* ps ID des Speicherformats (Attribut) durchschnittliche Groesse Anzahl der Zugriffe Abschaetzung fuer Konvertierungszeit Abschaetzung fuer Konvertierungszeit 1 = Attribut ist zu speichern 1 = speichern bei optimaler Basis naechstes Speicherformat ppm pgm png au */ */ */ */ */ */ */ */ wav Abbildung 4.1: Menge der Attribute Die Verbundkomponente is phys dient zur Unterscheidung zwischen physikalisch reprasentierten (1) und zu berechnenden (0) Formaten. In is opt wird dieses 4.2 Realisierung 37 Kennzeichen fur die optimale Basis gespeichert, d.h. die Zuweisung Aopt = Aphys im Algorithmus wird im Programm durch is opt = is phys auf allen Listenelementen realisiert. In den Variablen vom Typ list2 t werden die Konvertierungsfunktionen, bzw. deren Quell- und Zielformate gespeichert. typedef struct list2 { long node1 long node2 double comp_time char is_base struct list2 * next } list2_t /* /* /* /* /* ID des Quellformats ID des Zielformats durchschnittliche Konvertierungszeit 1 = f_c ist Basiselement naechste Konvertierungsfunktion */ */ */ */ */ Die Komponente comp time enthalt die durchschnittliche Rechenzeit fur die jeweilige Konvertierungsfunktion. In is base wird die Auswahl der in der zur Basis gehorenden Funktionen getroen. Der Variablentyp list list t dient zur Zerlegung der Formate in Zusammenhangskomponenten. typedef struct list_list { struct list * first struct list_list * next } list_list_t /* Liste der Attribute */ /* naechste Liste */ Die Verbundkomponente first ist ein Zeiger auf Attributmengen (Formatemengen). In Abbildung 4.2 ist die Zerlegung der Attributmenge aus Abbildung 4.1 in Grak- und Audioformate dargestellt. gif tiff au wav jpeg ps ppm Abbildung 4.2: Zusammenhangskomponenten pgm png 4.2 Realisierung 38 Der Programmablauf Bevor die eigentliche Optimierung stattnden kann, mussen die statistischen Daten { wie eben beschrieben { in die geeigneten Datenstrukturen eingefugt werden. Dabei werden auch die unterstutzten Formate und die vorhandenen Umwandlungsfunktionen aus der Datenbank ausgelesen. Anschlieend wird die Attributmenge A mit dem Union-Find -Algorithmus aus Sed83] in Zusammenhangskomponenten zerlegt (siehe Abb. 4.1 und Abb. 4.2) und die Optimierung auf diesen durchgefuhrt. Im Algorithmus aus Abschnitt 4.1 werden zunachst alle Teilmengen der Attributmenge A gebildet. Die darin enthaltenen Attribute werden physikalisch reprasentiert alle anderen durch Konvertierung erzeugt. Die Auswahl der physikalisch reprasentierten Attribute erfolgt durch binare Addition auf der Verbundkomponente is phys. Dabei wird beim ersten Listenelement is phys um eins erhoht tritt ein U bertrag auf (das Ergebnis ist zwei), wird is phys auf null gesetzt und die Addition beim nachsten Listenelement fortgefuhrt. Ein U bertrag am Ende der Liste signalisiert das Ende des Algorithmus. Dies ist in Abbildung 4.3 zu gif tiff jpeg png 0 1 0 1 0 0 0 1 1 0 0 0 0 0 1 0 0 0 0 0 1 0 1 0 1 0 1 0 Übertrag Abbildung 4.3: Auswahl der Attributmengen sehen. Dabei steht in den Zeilen unter den Listenelementen der Inhalt der zugehorigen is phys-Komponente. Der Wert eins bedeutet, da das entsprechende Format abgespeichert wird. Null steht dagegen bei zu berechnenden Formaten. Der Anteil der Speicherkosten an den Gesamtkosten kann schon zu diesem Zeitpunkt ermittelt werden, da er nur von der Auswahl der gespeicherten Formate abhangt. Im nachsten Schritt werden die noch in Frage kommenden Umwandlungsfunktionen Fcomp (Die Komponente is phys des Quellformats mu gleich eins sein is phys des Zielformats dagegen null.) selektiert und in eine eigene einfachverkettete Liste kopiert. Durch eine geschickte Datenbankanfrage beim Fullen 4.2 Realisierung 39 der ursprunglichen Liste ist die so entstehende Liste schon nach den Zielformaten sortiert. Da aber in der Basis fur jedes Zielformat nur eine Umwandlungsfunktion existieren darf, werden die Funktionen nach dem Zielformat gruppiert. Die Verbundkomponente is base des ersten Gruppenelements erhalt jeweils den Wert eins, alle anderen null. Der Eintrag eins in is base bedeutet, da diese Konvertierungsfunktion zur Basis gehort. Die Auswahl aller Basen erfolgt durch Rotation der is base-Komponenten innerhalb der Gruppen. Dabei erfolgt der Rotationsschritt der nachfolgenden Gruppe erst, wenn in der aktuellen Gruppe ein kompletter Zyklus durchlaufen wurde, d.h. die Eins wurde wieder nach vorn geschoben. Wurde auch in der letzten Gruppe ein vollstandiger Rotationszyklus durchgefuhrt, dann sind alle moglichen Basen gefunden. In Abbildung 4.4 ist die gif ps tiff ps jpeg ps gif png tiff png jpeg png 1 0 0 1 0 1 0 0 0 0 1 0 1 1 1 0 0 0 0 1 0 0 0 0 0 1 0 0 1 0 0 1 0 0 1 0 Abbruchkriterium Abbildung 4.4: Auswahl der Basiselemente Bestimmung der Basis dargestellt, falls gif, tiff und jpeg abgespeichert sind und sich ps und png aus jedem dieser drei Formate berechnen lassen. Ist eine Basis gefunden, kann dazu aus den statistischen Daten der Wert der Kostenfunktion ermittelt werden. Ist dieser kleiner als das bisherige Optimum, werden die Inhalte der is phys-Komponente nach is opt kopiert, um die zu speichernden Formate Aopt festzuhalten. Um Fopt zwischenzuspeichern wird die Liste kopiert, die Fcomp enthalt. Nach der Ausfuhrung des Optimierungsalgorithmus stehen die zu speichernden Formate und die zu benutzenden Konvertierungsfunktionen fest. Der Inhalt der Datenbank mu den neuen Anforderungen noch angepat werden. Dies geschieht in drei Schritten: 1. Die Dokumente mussen in die neu zu speichernde Formate umgewandelt werden. 2. Nicht mehr zu speichernde Formate mussen aus der Datenbank entfernt werden. 4.2 Realisierung 40 3. Die zu benutzenden Umwandlungsfunktionen mussen ausgewahlt werden. Der erste Punkt wird dabei durch folgenden Ablauf erfullt: Fur alle zu speichernden Formate werden alle Dokumente gesucht, die in einem Format aus derselben Zusammenhangskomponente, aber nicht in dem zu speichernden Format, vorhanden sind. Dies geschieht mit folgender SQL-Query: SELECT DISTINCT name FROM artikel WHERE fid=:db_fid AND name NOT IN (SELECT name FROM artikel WHERE fid=:cl_fid) Dabei enthalt cl fid das zu speichernde Format und db fid der Reihe nach alle anderen Formate. Fur jedes Format db fid werden dann mit dem Programm insert copy (der Quellcode ist im Anhang C.5) alle gefundenen Dokumente im gewunschten Format eingefugt. Das Programm insert copy verlangt als Parameter den Namen des Dokuments und das zu speichernde Format. Dann wird mit den Methoden des CGI-Programms db interface (siehe Abschnitt 3.3.2) aus einem in der Datenbank vorhandenen Format das gewunschte erzeugt. Im Gegensatz zum CGI-Programm erfolgt aber keine Ausgabe, sondern eine Speicherung in der Datenbank. Punkt zwei ist durch eine einfache SQL-Anweisung zu erledigen: DELETE FROM artikel WHERE fid = :cl_fid Diese Anweisung wird fur alle Formate cl fid ausgefuhrt, die in Zukunft zu berechnen sind. Auch der dritte Punkt ist durch eine SQL-Anweisung zu realisieren: UPDATE umwandlung SET praeferenz = :pref WHERE zielformat = :cl_fid AND quellformat = :db_fid Die Update-Anweisung wird nur fur die Funktionen durchgefuhrt, deren Quellformate in der Datenbank gespeichert werden, und deren Zielformate zu berechnen sind andere Funktionen konnen ohnehin nicht verwendet werden. Die Hostvariable pref enthalt hierbei den Wert der is base-Komponente, die Aufschlu daruber gibt, ob eine Konvertierungsfunktion benutzt wird (zur Basis gehort) oder nicht. Kapitel 5 Messungen In diesem Kapitel wird anhand eines Beispiels das Ergebnis des Optimierungsalgorithmus gezeigt. In der Datenbank sind 69 Bilder und sieben Audio-Clips abgelegt. Dabei werden folgende Formate unterstutzt: Bildformate Audioformate gif au ti wav jpeg ps ppm pgm png Zwischen den Speicherformaten gelten folgende Abhangigkeiten: Quellformat gif ti jpeg ps ppm pgm png au wav Zielformat gif ti jpeg ps ppm pgm png au wav 41 KAPITEL 5: MESSUNGEN 42 Die dabei verwendeten Konvertierungsfunktionen sind in Tabelle B.2 im Anhang aufgefuhrt. Das Beispiel konzentriert sich nur auf die Bilder, die anfanglich alle im jpegFormat gespeichert sind. Zunachst gibt es auch noch keine statistischen Daten, die als Grundlage fur die Optimierung herangenommen werden konnten. Diese Daten entstehen erst durch Zugrie auf den Storage-Server oder durch die Optimierung selbst, falls neue Attribute physikalisch reprasentiert werden. Deshalb wird hier der Optimierungsalgorithmus solange wiederholt, bis sich das Resultat nicht mehr verandert. Sind die statistischen Daten `vollstandig', dann werden sie durch die Optimierung nur noch unwesentlich beeinut, und es genugt ein Optimierungslauf. Fur die Konstanten in der Kostenfunktion werden die nachfolgenden Werte verwendet.1 cS = 0 000 002 cA = 0 000 000 1 z = 0 01 Im Beispiel wird immer eine U bersicht des aktuellen Queryprols gegeben. Dies ist eine einfache Aufschlusselung der Zahl der Anfragen auf die Formate. Anschlieend erfolgt die Darstellung des Resultats der Optimierung, wobei die zu speichernden Formate (Attribute) angezeigt werden hier gilt Aopt = Aphys. Auerdem wird die optimale functional base dargestellt. Dabei bedeutet die Notation `gif ;! ti', da in der optimalen functional base eine funktionale Abhangigkeit zwischen `gif' und `ti' besteht hierbei ist `ti' Kopf- und `gif' Rumpfattribut. Mit anderen Worten: Das tiff-Format wird durch Konvertierung aus dem gifFormat erzeugt. Zu Beginn werden 100 Anfragen fur das gif-Format gestellt. Format gif P Anfragen 100 100 Nach dem ersten Optimierungslauf ergibt sich: Aphys gif Basis gif ;! ti gif ;! jpeg gif ;! ps gif ;! ppm gif ;! pgm gif ;! png Die Konstanten konnen als Parameter an das Optimierungsprogramm ubergeben werden, z.B.: optimierer -s 0.000002 -a 0.0000001 -z 0.01 1 KAPITEL 5: MESSUNGEN 43 Nach dem zweiten Optimierungslauf ergibt sich: Aphys Basis gif png ;! ti png png ;! jpeg png ;! ps png ;! ppm png ;! pgm Nach dem dritten Optimierungslauf ergibt sich: Aphys Basis gif ppm ;! ti ppm ppm ;! jpeg ppm ;! ps ppm ;! pgm ppm ;! png Nach dem vierten Optimierungslauf ergibt sich: Aphys gif ti Basis ti ;! jpeg ti ;! ps ti ;! pgm ti ;! ppm ti ;! png Nach dem funften Optimierungslauf ergibt sich: Aphys gif ti ps pgm Basis ti ;! jpeg ti ;! ppm ti ;! png Nach dem sechsten Optimierungslauf ergibt sich: Aphys Basis gif png ;! ti png png ;! jpeg png ;! ps gif ;! ppm png ;! pgm Weitere Optimierungen andern nichts am Ergebnis. KAPITEL 5: MESSUNGEN 44 Es erfolgen zusatzlich 50 Anfragen fur das jpeg-Format. Format gif jpeg P Anfragen 100 50 150 Nach der Optimierung ergibt sich: Aphys gif Basis gif ;! ti gif ;! jpeg gif ;! ps gif ;! ppm gif ;! pgm gif ;! png Dieses Ergebnis bleibt auch nach weiteren Optimierungen erhalten. Nun werden noch 20 Anfragen im png-Format gestellt. Format gif jpeg png P Anfragen 100 50 20 170 Die Optimierung fuhrt zu folgendem Ergebnis: Aphys Basis gif gif ;! ti ppm ppm ;! jpeg ppm ;! ps ppm ;! pgm ppm ;! png Auch dieses Ergebnis bleibt schon nach der ersten Optimierung konstant. Von jetzt an bleibt die Summe der Anfragen konstant. Es ndet lediglich eine Verschiebung der Zusammenstellung statt. In der Log-Tabelle verlieren 50 Datensatze im gif-Format ihre Gultigkeit. Dafur werden 30 neue Anfragen fur das jpeg- und 20 f ur das png-Format gestellt. Format gif jpeg png P Anfragen 50 80 40 170 KAPITEL 5: MESSUNGEN 45 Die Optimierung liefert im diesem Fall: Aphys Basis gif png ;! ti png gif ;! jpeg png ;! ps png ;! ppm png ;! pgm Weitere Optimierungen beeinussen auch dieses Ergebnis nicht mehr. Nun verlieren die restlichen 50 Log-Daten fur das gif-Format ihre Gultigkeit. Dafur erfolgen 10 neue gif-, 10 jpeg- und 30 png-Anfragen. Format gif jpeg png P Anfragen 10 90 70 170 Als Ergebnis der Optimierung entsteht: Aphys Basis ti ti ;! gif png ti ;! jpeg png ;! ps ti ;! ppm png ;! pgm Das Ergebnis wird durch weitere Optimierungen nicht mehr verandert. Nun erfolgt eine Aufschlusselung der Kostenfunktion, die als Grundlage fur die letzte Optimierung verwendet wurde. Die fett gedruckten Zahlen gehen in die Berechnung der Kosten der optimalen functional base ein. Die nachste Tabelle enthalt die durchschnittliche Groe (in Bytes) der einzelnen Formate und die daraus resultierenden Speicherkosten: A 2 A avg size(A) store cost(A) gif 271058 0,54212 ti jpeg ps ppm pgm png P A2Aphys 71377 0,14275 101342 0,20268 0,34544 41838 81222 588465 31594 0,08368 0,16244 1,17693 0,06319 KAPITEL 5: MESSUNGEN 46 Die durchschnittlichen Rechenzeiten (in Sekunden), die von den Konvertierungsprogrammen fur eine Formatumwandlung beansprucht werden, sind in der anschlieenden Tabelle dargestellt: A2A gif ti jpeg ps ppm pgm png gif 0,0000 1,6517 0,0000 1,8380 0,0000 3,2775 comp cost(A = f (B )) B2A ti jpeg ps ppm pgm png 3,0930 6,3250 3,0930 3,0930 3,0930 1,8820 0,4370 0,0000 4,1646 1,1652 0,9610 3,0930 0,0000 0,0000 0,0000 3,0930 1,5940 0,4150 3,0930 0,0000 0,0000 1,4120 3,0930 5,8410 Zu den entsprechenden Konvertierungsfunktionen liegen noch keine verlalichen Informationen uber die benotigte Rechenzeit vor. Die nun folgende Tabelle enthalt die Zugriskosten, falls eine Formatumwandlung notwendig ist (A 2 Acomp): (A = f (B ) A 2 Acomp B 2 Aphys) B2A ti jpeg ps ppm pgm png 43,4019 88,6086 44,1259 43,4439 12,3887 7,7634 1,7885 0,6709 397,0082 110,4810 4,8407 15,4859 0,2942 0,0507 0,0286 12,3887 6,4165 2,1107 15,4859 0,2942 0,0507 105,0162 229,1916 436,5851 acc cost(A) A2A gif gif ti 0,1084 jpeg 157,8103 ps 0,1355 ppm 7,4604 pgm 0,1355 png 244,5391 Die letzte Tabelle enthalt neben dem Queryprol (Anzahl der Zugrie), die Zugriskosten fur physikalisch reprasentierte Formate (A 2 Aphys) und auch die Zugriskosten, die durch die optimale Basis bestimmt sind. KAPITEL 5: MESSUNGEN 47 A 2 A avg acc(A) acc cost(A) acc cost(A) A 2 Aphys A2A gif 14 0,37948 43,40193 ti 4 0,02855 0,02855 jpeg 94 0,39328 0,67094 ps 5 0,04061 0,05067 ppm 4 0,23539 0,02855 pgm 5 0,01580 0,05067 png 74 0,74993 0,74993 P A2A 44,98125 Somit ergibt sich fur die optimalen Kosten: cost(Aopt Fopt) = (1 ; 0 01) 0 34544 + 0 01 44 98125 = 0 79180 Kapitel 6 Zusammenfassung und Ausblick In dieser Arbeit wurde ein System zur Speicherung und Verwaltung von Datenbestanden mit Formatumwandlung und Optimierung vorgestellt. Dabei wurde groter Wert auf Flexibilitat gelegt. Erweiterungen der unterstutzten Speicherformate und deren Abhangigkeiten untereinander sind ohne Programmieraufwand und nur durch Administration des Systems moglich. Die Messungen in Kapitel 5 zeigen, da sich das System selbststandig an neue Anforderungen (wechselndes Queryprol) anpat und die dazu benotigten statistischen Informationen selbst sammelt. Die Art der speicherbaren Formate ist vielfaltig, jedoch sollten zwischen einzelnen Formaten Abhangigkeiten bestehen. Naturlich ist dieses System noch nicht perfekt es gibt noch eine Reihe von moglichen Erganzungen und Verbesserungen: 1. Bisher wird die Auslastung des Storage-Servers nicht berucksichtigt, da in die Berechnungskosten nur die reine CPU-Zeit einiet. Wird aber im CGIProgramm (db interface) die Zeitdierenz zwischen Beginn und Ende der Konvertierungsfunktion ermittelt und in der Log-Tabelle abgespeichert, so geht uber die Rechenzeit der parallel ablaufenden Prozesse die Serverauslastung ein. 2. Im Bereich des Proxy-Servers ist auch eine Optimierung denkbar. Dies wurde bedeuten, da die Auswahl des Formats, das beim Storage-Server angefordert wird, von den Zugris- und Berechnungskosten der einzelnen Formate abhangt. Die Menge der zu berechnenden Formate kann dabei in der Kongurationsdatei des Proxy-Servers abgespeichert werden. Eine Praferenzordnung unter den Konvertierungsfunktionen ware schon durch ihre Reihenfolge in der Kongurationsdatei gegeben, da die erste passende Funktion verwendet wird. 3. Das Sammeln von statistischen Daten, die die Grundlage fur die Kostenfunktion bilden, gestaltet sich etwas schwieriger. Die Zugrisinformationen 48 KAPITEL 6: ZUSAMMENFASSUNG UND AUSBLICK 49 muten in Log-Dateien gespeichert werden, deren Auswertung komplizierter ist als die von Datenbanktabellen (Aggregation). Dies fuhrt zu einem komplexeren Optimierungsprogramm. Nach erfolgter Optimierung mu das Ergebnis davon in die Kongurationsdatei des Proxy-Servers ubernommen werden. Beim verwendeten Proxy (squid) ist eine Neukonguration wahrend des Betriebs moglich, indem ein SIGHUP Signal an den Proxy-Server geschickt wird. Dies kann beispielsweise mit dem folgenden Kommando erreicht werden, falls in der Datei squid.pid im angegebenen Verzeichnis die Proze-ID des Proxy-Servers abgelegt ist: kill -HUP `cat ~/squid/logs/squid.pid` 4. Die im Zuge einer Optimierung notwendigen Veranderungen an den physikalisch reprasentierten Formaten { speziell die Aufnahme neuer Speicherformate in die Datenbank { ist bei groen Datenmengen sehr zeitraubend. Deshalb ware es wunschenswert, diesen Vorgang in kleine Teilaufgaben zu zerlegen, die ausgefuhrt werden, wenn die Belastung der Datenbank moglichst gering ist. 5. In die Datenbank konnten noch zusatzliche Metainformationen zu den einzelnen Dokumenten aufgenommen werden. Dies kann dann in der Anfrageschnittstelle fur Suchanfragen verwendet werden. Es ist auch eine mehrstuge Auswahl beim Request denkbar (1. Auswahl des Dokumenttitels 2. Auswahl des Formats). Damit konnte verhindert werden, da fur ein Dokument ein Format ausgewahlt wird, in das es nicht konvertiert werden kann, z.B. ein Audio-Format fur ein Bilddokument. Literaturverzeichnis Bor96] Gunter Born. HTML 2.0/3.2 Reference Guide. Addison-WesleyLongman, Bonn Reading, Mass. u.a.], 1996. FB96] N. Freed and N. Borenstein. Multipurpose Internet Mail Extensions (MIME) Part Two: Media Types. Network Working Group, November 1996. http://www.leo.org/pub/comp/doc/standards/rfc/rfc2000-2099/rfc2046. FGM+97] R. Fielding, J. Gettys, J. Mogul, H. Frystyk, and T. Berners-Lee. Hypertext Transfer Protocol { HTTP/1.1. Network Working Group, January 1997. http://www.leo.org/pub/comp/doc/standards/rfc/rfc2000-2099/rfc2068. Gun96] Shishir Gundavaram. CGI Programming on the World Wide Web. O'Reilly & Associates, Inc., Sebastopol, 1996. Her96] Helmut Herold. UNIX-Systemprogrammierung. Addison-Wesley, Bonn Paris Reading, Mass. u.a.], 1996. KKK96] W. Kowarschick, G. Kostler, and W. Kieling. Client-Server Optimation for Multimedia Document Exchange. Institut fur Informatik, Universitat Augsburg, Sep. 1996. Klu95] Rainer Klute. Das World Wide Web. Addison{Wesley, Bonn Paris Reading, Mass. u.a.], 1995. http://www.nads.de/klute/WWWBuch/1/. LL95] Stefan M. Lang and Peter C. Lockemann. Datenbankeinsatz. Springer Verlag, Berlin Heidelberg, 1995. Sed83] Robert Sedgewick. Algorithms. Addison{Wesley, Reading, Massachusetts u.a], 1983. 50 Anhang A Abbildungen Abbildung A.1: Proxy-Konguration 1 A-1 ANHANG A: ABBILDUNGEN Abbildung A.2: Proxy-Konguration 2 Abbildung A.3: Proxy-Konguration 3 A-2 ANHANG A: ABBILDUNGEN Abbildung A.4: Benutzerschnittstelle A-3 ANHANG A: ABBILDUNGEN Abbildung A.5: Anfrageergebnis A-4 Anhang B Tabellen MIME-Type Dateiendung application/octet-stream bin arj zoo dms exe application/oda oda application/pdf pdf application/postscript ai eps ps application/rtf rtf application/x-bcpio bcpio application/x-cpio cpio application/x-dvi dvi application/x-gtar gtar application/x-hdf hdf application/x-latex latex application/x-mif mif application/x-netcdf nc cdf application/x-shar shar application/x-sv4cpio sv4cpio application/x-sv4crc sv4crc application/x-tar tar application/x-tcl tcl application/x-tex tex application/x-texinfo texinfo texi application/x-tro t tr ro application/x-tro-man man application/x-tro-me me application/x-tro-ms ms application/x-ustar ustar application/x-wais-source src application/zip zip Fortsetzung nachste Seite B-1 ANHANG B: TABELLEN Fortsetzung MIME-Type audio/basic audio/x-ai audio/x-pn-realaudio audio/x-wav image/gif image/ief image/jpeg image/ti image/x-cmu-raster image/x-png image/x-portable-anymap image/x-portable-bitmap image/x-portable-graymap image/x-portable-pixmap image/x-rgb image/x-xbitmap image/x-xpixmap image/x-xwindowdump text/html text/plain text/richtext text/tab-separated-values text/x-setext video/mpeg video/quicktime video/x-msvideo video/x-sgi-movie B-2 Dateiendung au snd aif ai aifc ra ram wav gif ief jpeg jpg jpe ti tif ras png pnm pbm pgm ppm rgb xbm xpm xwd html htm txt rtx tsv etx mpeg mpg mpe qt mov avi i movie Tabelle B.1: MIME-Typen ANHANG B: TABELLEN Quellformat Zielformat Umwandlungsfunktion gif ti convert - ti:jpeg ti convert - ti:ppm ti convert - ti:png ti convert - ti:ti gif convert - gif:jpeg gif convert - gif:ppm gif convert - gif:png gif convert - gif:gif jpeg convert - jpeg:ti jpeg convert - jpeg:ppm jpeg convert - jpeg:png jpeg convert - jpeg:gif ps convert - ps:ti ps convert - ps:jpeg ps convert - ps:ppm ps convert - ps:png ps convert - ps:gif ppm convert - ppm:ti ppm convert - ppm:jpeg ppm convert - ppm:png ppm convert - ppm:gif pgm convert - pgm:ti pgm convert - pgm:jpeg pgm convert - pgm:ppm pgm convert - pgm:png pgm convert - pgm:gif png convert - png:ti png convert - png:jpeg png convert - png:ppm png convert - png:wav au mklter sox - -t .au au wav mklter sox - -t .wav Tabelle B.2: Konvertierungsfunktionen B-3 Anhang C Programme C.1 converter.c /****************************************************************** * * * converter.c * * * * Konverterprogramm f"ur den Proxy-Server * * liest Daten von stdin, schreibt auf stdout * * erh"alt die Konvertierungsfunktion und den Content-Type des * * Zielformats "uber den Convert-Header * ******************************************************************/ #include #include #include #include #include <stdio.h> "squid.h" <string.h> <strings.h> <sys/types.h> typedef struct _cmd { char *cmd char **args } cmd_t static const char *const w_space = " \t\n\r" char input_lineBUFSIZ] C-1 C.1 converter.c C-2 int getTokenNumber(char *s) { int n=0 while(*s) { while(*s && isspace(*s)) s++ if(!*s) break while(*s && !isspace(*s)) s++ n++ } return n } /* Convert-Header auswerten nach Aufforderung Programm beenden */ void parseMyHeader(cmd_t *f, char **mime) { char *token = NULL, **s while (1) { if (fgets(input_line, BUFSIZ, stdin) == NULL) exit(0) if ((token = strchr(input_line, '\n'))) *token = '\0' if ((token = strchr(input_line, '\r'))) *token = '\0' if (strcmp(input_line,"$shutdown") == 0) exit(0) if (strcmp(input_line,"$hello") == 0) { printf("$alive\n") fflush(stdout) continue } if ((token = strtok(input_line, w_space)) == NULL) return if (strcasecmp(token,"Mime-Type:")==0) *mime=xstrdup(strtok(NULL,w_space)) else if (strcasecmp(token,"Command:")==0) { if ((token = strtok(NULL,w_space))==NULL) exit(1) f->cmd = xstrdup(token) C.1 converter.c C-3 s=f->args=(char**)calloc(getTokenNumber(token+ strlen(token+1))+1,sizeof(char*)) *s++=xstrdup(token) while ((token = strtok(NULL,w_space))!=NULL) *s++=xstrdup(token) *s=NULL } } } /* HTTP-Header lesen, ver"andern und ausgeben */ void parseHTTPHeader(char *t) { char *s while (fgets(input_line, BUFSIZ, stdin)) { for(s=input_line*s&&isspace(*s)s++) if (!*s) { puts("") return } fprintf(stderr,"<%s>\n",input_line) fflush(stderr) if (strncasecmp(input_line,"Content-Type:",12)!=0) printf("%s",input_line) else printf("Content-Type: %s\n",t) } } /* Konvertierungsproze"s starten und Konvertierung durchf"uhren */ void convert(cmd_t *c) { int pid, status switch(pid = fork()) { case -1: fprintf(stderr,"Fehler beim Aufruf von fork %s %s\n", c->cmd,*(c->args)) exit(1) C.2 dynform.sqc case C-4 0: execvp(c->cmd,c->args) fprintf(stderr,"ACHTUNG FEHLER kein Aufruf von %s\n", c->cmd) fprintf(stderr," %s %s \n",*(c->args),*(c->args+1)) exit(-1) default: wait(&status) } return } /* Hauptprogramm */ void main(void) { char *mime cmd_t fkt setbuf(stdin,NULL) for() { parseMyHeader(&fkt,&mime) fflush(stdout) parseHTTPHeader(mime) fflush(stdout) convert(&fkt) fflush(stdout) } } C.2 dynform.sqc /****************************************************************** * * * dynform.sqc * * * * dynform liefert den Inhalt der Datenbank als HTML-Formular * * und erm"oglicht "uber den Aufruf von db_interface den Zugriff * C.2 dynform.sqc C-5 * auf die Dokumente * * * ******************************************************************/ #include <stdio.h> #include <stdlib.h> #include <string.h> #include "util.h" EXEC SQL INCLUDE SQLCA #define CHECKERR(CE_STR) if (check_error (CE_STR, &sqlca) != 0) \ return 1 int main(int argc, char *argv]) { int first char errtxt200] EXEC SQL BEGIN DECLARE SECTION char doctype11] char docname21] EXEC SQL END DECLARE SECTION /* Environment-Variable beim Aufruf "uber CGI nicht bekannt! */ putenv("DB2INSTANCE=db2ex") EXEC SQL CONNECT TO peter_db USER db2ex USING daba2 sprintf(errtxt,"Content-type: text/plain \n\n" "CONNECT TO PETER_DB\n%s\n",getenv("DB2INSTANCE")) CHECKERR (errtxt) EXEC SQL DECLARE c1 CURSOR FOR SELECT distinct name FROM formate CHECKERR ("Content-type: text/plain \n\nDECLARE CURSOR c1\n") EXEC SQL DECLARE c2 CURSOR FOR SELECT distinct name FROM artikel CHECKERR ("Content-type: text/plain \n\nDECLARE CURSOR c2\n") EXEC SQL CHECKERR EXEC SQL CHECKERR OPEN c1 ("Content-type: text/plain \n\nOPEN CURSOR c1\n") OPEN c2 ("Content-type: text/plain \n\nOPEN CURSOR c2\n") C.2 dynform.sqc printf("Content-type: text/html\n\n") printf("<HTML>\n<HEAD><TITLE>Eingabeformular" "</TITLE></HEAD>\n<BODY>\n") printf("<H1>Eingabeformular</H1>\n<HR>\n") /* auszuf"uhrendes CGI-Programm */ printf("<FORM ACTION=\"/cgi-bin/db_interface\"") /* Formular mit den Dokumentnamen */ printf(" METHOD=\"GET\">\nName des Artikels: <BR>") printf("<SELECT NAME=\"name\" SIZE=8>\n") first=1 do { EXEC SQL FETCH c2 INTO :docname if (SQLCODE != 0) break else { printf ("<OPTION%s>%s\n",first?" SELECTED":"",docname) first=0 fflush(stdout) } } while ( 1 ) printf("</SELECT>\n<BR>\n") /* Formular mit den unterst"utzten Dokumentformaten */ printf("gew&uumlnschtes Format: <BR>\n") printf("<SELECT NAME=\"format\" SIZE=8>\n") first=1 do { EXEC SQL FETCH c1 INTO :doctype if (SQLCODE != 0) break else { printf ("<OPTION%s>%s\n",first?" SELECTED":"",doctype) first=0 fflush(stdout) } } while ( 1 ) printf("</SELECT>\n<BR>\n") printf("<P>\n<INPUT TYPE=\"submit\" VALUE=\"Abschicken\">\n") printf("</FORM>\n<HR>\n</BODY>\n</HTML>\n") C-6 C.3 db interface.sqc C-7 EXEC SQL CLOSE c1 CHECKERR ("Content-type: text/plain \n\nCLOSE CURSOR\n") EXEC SQL CONNECT RESET CHECKERR ("Content-type: text/plain \n\nCONNECT RESET\n") return 0 } C.3 db interface.sqc /****************************************************************** * * * db_interface.sqc * * * * CGI-Programm zur Datenbankanbindung mit Konvertierung * * db_interface liefert ein das angeforderte Dokument im * * gew"unschten Format zur"uck, notfalls mit Konvertierung * * * ******************************************************************/ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <time.h> #include <sys/timers.h> #include <sys/ipc.h> #include <sys/types.h> #include <sql.h> #include <fcntl.h> #include "stat.h" #ifndef USE_MSG # ifndef USE_SHM # define USE_SHM # endif #endif #ifdef USE_MSG # include <sys/msg.h> # define MSG_KEY IPC_PRIVATE #endif C.3 db interface.sqc C-8 #ifdef USE_SHM # include <sys/shm.h> # define SHM_KEY IPC_PRIVATE #endif #include "util.h" EXEC SQL INCLUDE SQLCA #define CHECKERR(CE_STR) #define #define #define #define MAXPIPES 15 MAXOPTS 15 EIN 1 AUS 0 if (check_error (CE_STR, &sqlca) != 0) \ return 1 struct fork_cmd { char *cmd char *opt struct fork_cmd *next } #ifdef USE_MSG struct SEND_MESG { long type unsigned long bytes } sendmessage #endif typedef struct fork_cmd s_fork_cmd int pipesMAXPIPES]2] #ifdef USE_MSG int msg_queue #endif #ifdef USE_SHM int shmhandle long *b_p #endif s_fork_cmd* split_cmd(char *s) void ausgabe(int nos) void eingabe(char *fname, int nos) C.3 db interface.sqc C-9 void split_opts(char **spl, char *cmd, char *opts) unsigned long umwandlung(char *fname, char *cmd) int main(int argc, char *argv]) { long len char errtxt200], expdate200], buf4096] FILE *fp1 clock_t start_z struct tm *tp2 struct timespec tp1 struct stat filestat int fd EXEC SQL BEGIN DECLARE SECTION SQL TYPE IS BLOB_FILE tmp_file short lobind long cl_fid long db_fid long db_len long cl_len double r_zeit char docname21] char doctype11] char mime101] char cl_host101] char uw_fkt201] EXEC SQL END DECLARE SECTION /* Environment-Variable beim Aufruf "uber CGI nicht bekannt! */ putenv("DB2INSTANCE=db2ex") fp1 = freopen("mycgi.err", "a", stderr) getclock(TIMEOFDAY,&tp1) fprintf(stderr,"TimeOfDay:%ld.%06ld\n",tp1.tv_sec,tp1.tv_nsec) tp1.tv_sec+=5184000 tp2=gmtime(&tp1.tv_sec) strftime(expdate,199,"%a, %d %b %Y %H:%M:%S GMT",tp2) fprintf(stderr,"Expires: %s\n",expdate) /* Parameter des CGI Aufrufs auswerten */ uncgi() C.3 db interface.sqc strcpy(docname,getenv("WWW_name")) strcpy(doctype,getenv("WWW_format")) strcpy(cl_host,getenv("REMOTE_HOST")) EXEC SQL CONNECT TO peter_db USER db2ex USING daba2 sprintf(errtxt,"Content-type: text/plain \n\n" "CONNECT TO PETER_DB\n%s\n",getenv("DB2INSTANCE")) CHECKERR (errtxt) fprintf(stderr,"Connect OK\n %s %s\n",docname,doctype) strcpy(tmp_file.name,tmpnam(NULL)) tmp_file.name_length = strlen(tmp_file.name) tmp_file.file_options = SQL_FILE_CREATE EXEC SQL SELECT a.inhalt,f.mimetype,f.fid INTO :tmp_file :lobind, :mime, :db_fid FROM artikel a, formate f WHERE a.fid=f.fid AND a.name=:docname AND f.name=:doctype if (SQLCODE != 0) { EXEC SQL DECLARE c2 CURSOR FOR SELECT a.inhalt, u.funktion, f.mimetype, a.fid, f.fid FROM artikel a, umwandlung u, formate f WHERE a.fid=u.quellformat AND u.zielformat=f.fid AND a.name=:docname AND f.name=:doctype AND u.praeferenz=(SELECT max(u1.praeferenz) FROM umwandlung u1, artikel a1 WHERE a1.name=a.name AND u1.zielformat=f.fid AND u1.quellformat=a1.fid) EXEC SQL OPEN c2 EXEC SQL FETCH c2 INTO :tmp_file :lobind, :uw_fkt, :mime, :db_fid, :cl_fid if (SQLCODE != 0) { printf("Content-type: text/plain\n") C-10 C.3 db interface.sqc C-11 printf("Status: 404 Not Found\n\n") printf("FALSCHES FORMAT\nArtikel ") printf("%s.%s ist nicht vorhanden\n",docname,doctype) printf("bzw. kann durch Umwandlung nicht erzeugt werden.\n") return } else { /* HTTP-Header ausgeben */ printf("Content-type: %s\n",mime) printf("Expires: %s\n\n",expdate) fflush(stdout) stat(tmp_file.name,&filestat) start_z=clock() cl_len=umwandlung(tmp_file.name,uw_fkt) r_zeit=(clock()-start_z)/(double)CLOCKS_PER_SEC db_len=filestat.st_size fprintf(stderr,"Umwandlung von %s.%s mit %s nach %s\n", docname,doctype,uw_fkt,mime) } } else { if (lobind < 0) { printf("Content-type: text/plain\n\n") printf("Artikel %s.%s ist leer\n",docname,doctype) } else { fprintf(stderr,"Tempfile: %s\n",tmp_file.name) fd=open(tmp_file.name,O_RDONLY) fstat(fd,&filestat) /* HTTP-Header ausgeben */ printf ("Content-type: %s\n",mime) printf ("Expires: %s\n",expdate) printf ("Content-length: %ld\n\n",filestat.st_size) fflush(stdout) /* BLOB ausgeben */ while(len=read(fd,buf,4096)) write(fileno(stdout),buf,len) C.3 db interface.sqc C-12 fflush(stdout) close(fd) cl_len=db_len=filestat.st_size cl_fid=db_fid remove(tmp_file.name) r_zeit=0.0 } } /* Zugriffsdaten in Log-Tabelle schreiben */ EXEC SQL INSERT INTO log_tabelle VALUES ( :cl_host, :docname, :cl_fid, :db_fid, :cl_len, :db_len, :r_zeit, CURRENT TIMESTAMP ) CHECKERR ("Content-type: text/plain\n\nINSERT INTO log_tab\n") EXEC SQL CONNECT RESET CHECKERR ("Content-type: text/plain \n\nCONNECT RESET\n") return 0 } /* Formatumwandlung mittels Kindprozessen */ unsigned long umwandlung(char *fname, char *cmd) { int nos=0, status unsigned long len char *optvMAXOPTS] s_fork_cmd *f1, *f #ifdef USE_MSG msg_queue=msgget(MSG_KEY,IPC_CREAT | 0777) if(msg_queue==-1) { perror("Fehler bei msgget()") exit(1) } #endif f1=f=split_cmd(cmd) while(f!=NULL) { f=f->next pipe(pipes++nos]) C.3 db interface.sqc } nos=0 pipe(pipesnos]) f=f1 eingabe(fname,nos) while(f!=NULL) { nos++ fprintf(stderr,"%d:%s p1: %d p2: %d\n",nos,f->cmd, pipesnos]0],pipesnos]1]) split_opts(optv,f->cmd,f->opt) switch(fork()) { case -1: fprintf(stderr,"Fehler beim Aufruf von fork " "%s %s\n",f->cmd,f->opt) exit(1) case 0: dup2(pipesnos-1]AUS],0) close(pipesnos-1]AUS]) close(pipesnos-1]EIN]) dup2(pipesnos]EIN],1) close(pipesnos]EIN]) fprintf(stderr,"Aufruf von execvp '%s' '%s'\n", f->cmd,f->opt) close(pipesnos]AUS]) execvp(f->cmd,optv) fprintf(stderr,"ACHTUNG FEHLER kein Aufruf von " "%s\n",f->cmd) exit(-1) default: close(pipesnos-1]AUS]) close(pipesnos]EIN]) } f=f->next } #ifdef USE_SHM shmhandle=shmget(SHM_KEY, sizeof(long), 0700) fprintf(stderr,"SHMHANDLE %d\n",shmhandle) if(shmhandle==-1) { perror("Fehler bei shmget()") exit(1) } C-13 C.3 db interface.sqc b_p=(long *)shmat(shmhandle,0,0) if(b_p==(void *)-1) { perror("Fehler bei shmat()") exit(1) } *b_p=0 #endif ausgabe(nos) f=f1 wait(&status) wait(&status) while(f!=NULL) { f=f->next wait(&status) } len=1 #ifdef USE_MSG if(msgrcv(msg_queue,(struct msgbuf *)&sendmessage, sizeof(unsigned long),0,MSG_NOERROR)==-1) { perror("Fehler bei msgrcv()") exit(1) } msgctl(msg_queue,IPC_RMID,NULL) len=sendmessage.bytes #endif #ifdef USE_SHM len=*b_p shmdt((void *) b_p) shmctl(shmhandle,IPC_RMID,NULL) #endif fprintf(stderr,"Zielformat hat %ld Bytes\n",len) return len } C-14 C.3 db interface.sqc /* Schreiben des BLOBs in die Eingabepipe des */ /* Konvertierungsprozesses */ void eingabe(char *fname, int nos) { char buf4096] int len, fd switch(fork()) { case -1: fprintf(stderr,"Fehler beim Aufruf von fork %d\n", nos) exit(1) case 0: fd=open(fname,O_RDONLY) while(len=read(fd,buf,4096)) write(pipes0]EIN],buf,len) close(fd) remove(fname) exit(0) default: close(pipes0]EIN]) } } /* Lesenen des umgewandelten BLOBs aus der Ausgabepipe des */ /* Konvertierungsprozesses */ void ausgabe(int nos) { char buf4096] int nob unsigned long len=0 switch(fork()) { case -1: fprintf(stderr,"Fehler beim Aufruf von fork %d\n", nos) exit(1) case 0: while(nob=read(pipesnos]AUS],buf,4096)) { write(1,buf,nob) len+=nob } #ifdef USE_MSG sendmessage.type=1 C-15 C.3 db interface.sqc C-16 sendmessage.bytes=len if( msgsnd(msg_queue,(struct msgbuf *) &sendmessage, sizeof(unsigned long),MSG_NOERROR) == -1 ) { perror("Fehler bei msgsnd()") fprintf(stderr,"%d\n",errno) exit(1) } #endif #ifdef USE_SHM *b_p=len shmdt((void *)b_p) #endif exit(0) default: close(pipesnos]AUS]) } } /* Aufspalten des Kommandostrings in Befehle und Komponenten */ s_fork_cmd* split_cmd(char *s) { char *del, *opt s_fork_cmd *f_base, *f_runner f_base=NULL while(*s && isspace(*s)) s++ while(*s) { if(f_base==NULL) f_runner=f_base=malloc(sizeof(s_fork_cmd)) else { f_runner->next=malloc(sizeof(s_fork_cmd)) f_runner=f_runner->next } f_runner->next=NULL del=opt=s while(*del && *del !='|') del++ while(*opt && !isspace(*opt) && *opt!='|') opt++ f_runner->cmd=malloc(opt-s+1) strncpy(f_runner->cmd,s,opt-s) C.4 optimierer.sqc C-17 f_runner->cmdopt-s]=0 while(*opt && isspace(*opt)) opt++ if (del-opt) { f_runner->opt=malloc(del-opt+1) strncpy(f_runner->opt,opt,del-opt) f_runner->optdel-opt]=0 } else f_runner->opt="" s=del while(*s && (isspace(*s)||*s=='|')) s++ } return f_base } /* Aufspalten der Optionen */ void split_opts(char **spl, char *cmd, char *opts) { *spl++=cmd while(*opts) { *spl++=opts while(*opts && !isspace(*opts)) opts++ if(*opts) *opts++=0 while(*opts && isspace(*opts)) opts++ } *spl=NULL } C.4 optimierer.sqc /****************************************************************** * * * optimierer.sqc * * * * USAGE: optimierer -a value] -s value] -z value] * * * C.4 optimierer.sqc C-18 * optimierer entscheidet ausgrund einer Kostenfunktion "uber die * * zu speichernden Formate und die zu verwendenden Umwandlungs* * funktionen. * * Die Datenbank wird den neuen Anforderungen angepa"st! * * * ******************************************************************/ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <time.h> #include <sys/timers.h> #include <sys/ipc.h> #include <sys/types.h> #include <sql.h> #include <fcntl.h> #include "stat.h" EXEC SQL INCLUDE SQLCA #define CHECKERR(CE_STR) #define #define MAXPIPES 15 MAXOPTS 15 struct list { long node long size long access double ct_src double ct_dest char is_phys char is_opt struct list * next } typedef struct list list_t struct list2 { long node1 long node2 if (check_error (CE_STR, &sqlca) != 0) \ return 1 C.4 optimierer.sqc double comp_time char is_base struct list2 * next } typedef struct list2 list2_t struct list_list { struct list * first struct list_list * next } typedef struct list_list list_list_t /* Liste der Attribute (Formate) */ list_t *attr /* Liste der functional constraints (Umwandlungsfkts.) */ list2_t *fc /* Liste der Zusammenhangskomponenten */ list_list_t *compo /* Konstanten fuer die Kostenfunktion */ double const_ca=0.0000001, const_cs=0.000002, const_z=0.01 EXEC SQL BEGIN DECLARE SECTION long num long cl_fid long db_fid long db_len long cl_len long pref double r_zeit char docname21] char doctype11] char mime101] char cl_host101] char uw_fkt201] EXEC SQL END DECLARE SECTION int init_lists(list_t **a, list2_t **f) void split2compo(list_t **a, list2_t **f, C-19 C.4 optimierer.sqc list_list_t **c, long size) int next_base(list_t *a) int get_fcs(list_t *a, list2_t *fc, list2_t **fcn) void free_list2(list2_t **l) int next_fc_base(list2_t *fc) double get_ctime(long s, long d, list_t *l) double store_costs(list_t *base) double acc_costs_phys(list_t *base) double acc_costs_comp(list2_t *fc, list_t *base) void copy_fc(list2_t **d, list2_t *s) long add_phys_type(list_t *base) void del_comp_type(list_t *base) void get_args(int argc, char *argv]) int main(int argc, char *argv]) { long i, len, maxa char errtxt200], expdate200], buf4096] clock_t start_z struct stat filestat int fd char *c unsigned long idx, lines list_t *runner list2_t *runner2, *new_fc, *opt_fc=NULL list_list_t *runnerl, *ll double cost_s, cost_a_p, cost_a_c, costs putenv("DB2INSTANCE=db2ex") /* Optionen auswerten */ get_args(argc,argv) EXEC SQL CONNECT TO peter_db USER db2ex USING daba2 sprintf(errtxt,"main: CONNECT TO PETER_DB\n%s\n", getenv("DB2INSTANCE")) CHECKERR (errtxt) maxa=init_lists(&attr, &fc) EXEC SQL DELETE FROM log_tabelle WHERE cl_name = 'optimierer' OR cl_name = 'converter' C-20 C.4 optimierer.sqc C-21 strcpy(docname,"dummy") strcpy(cl_host,"optimierer") for(runner2=fcrunner2!=NULLrunner2=runner2->next) { db_fid=runner2->node1 cl_fid=runner2->node2 r_zeit=runner2->comp_time for(runner=attrrunner->node!=db_fidrunner=runner->next) db_len=runner->size for(runner=attrrunner->node!=cl_fidrunner=runner->next) cl_len=runner->size EXEC SQL INSERT INTO log_tabelle VALUES (:cl_host, :docname, :cl_fid, :db_fid, :cl_len, :db_len, :r_zeit, CURRENT TIMESTAMP) } split2compo(&attr, &fc, &compo,maxa) /* OPTIMIERUNGSALGORITHMUS */ for(ll=compoll!=NULLll=ll->next) { costs=1e300 for(i=0!i) { i=next_base(ll->first) cost_s=store_costs(ll->first) cost_a_p=acc_costs_phys(ll->first) if(get_fcs(ll->first,fc,&new_fc)) do { cost_a_c=acc_costs_comp(new_fc,ll->first) if(costs>cost_a_c+cost_a_p+cost_s) { costs=cost_a_c+cost_a_p+cost_s for(runner=ll->firstrunner!=NULLrunner=runner->next) runner->is_opt=runner->is_phys free_list2(&opt_fc) copy_fc(&opt_fc,new_fc) } }while(!next_fc_base(new_fc)) C.4 optimierer.sqc free_list2(&new_fc) } for(runner=ll->firstrunner!=NULLrunner=runner->next) printf("->%ld%d]",runner->node,runner->is_opt) /* Anpassung des Datenbankinhalts */ add_phys_type(ll->first) del_comp_type(ll->first) printf("\nfc: ") for(runner2=opt_fcrunner2!=NULLrunner2=runner2->next) { printf("->(%ld/%ld)%d]",db_fid=runner2->node1, cl_fid=runner2->node2,runner2->is_base) pref=runner2->is_base EXEC SQL UPDATE umwandlung SET praeferenz = :pref WHERE zielformat = :cl_fid AND quellformat = :db_fid } printf("\nOPT_COST: %f\n",costs) } EXEC SQL CONNECT RESET CHECKERR ("Content-type: text/plain \n\nCONNECT RESET\n") return 0 } /* Lesen der statistischen Daten, Formate und Funktionen */ int init_lists(list_t **a, list2_t **f) { list_t *l1 list2_t *l2 long maxattr=0 *a=NULL *f=NULL EXEC SQL DECLARE c1 CURSOR FOR SELECT fid FROM formate ORDER BY fid CHECKERR ("init_list: DECLARE CURSOR c1\n") EXEC SQL DECLARE c2 CURSOR FOR SELECT cl_fid, avg(cl_size), count(cl_fid) FROM log_tabelle C-22 C.4 optimierer.sqc GROUP BY cl_fid ORDER BY cl_fid DESC CHECKERR ("init_list: DECLARE CURSOR c2\n") EXEC SQL DECLARE c3 CURSOR FOR SELECT db_fid, avg(db_size), count(db_fid) FROM log_tabelle GROUP BY db_fid ORDER BY db_fid DESC CHECKERR ("init_list: DECLARE CURSOR c3\n") EXEC SQL DECLARE c4 CURSOR FOR SELECT cl_fid, avg(uw_dauer) FROM log_tabelle WHERE cl_fid <> db_fid GROUP BY cl_fid ORDER BY cl_fid CHECKERR ("init_list: DECLARE CURSOR c4\n") EXEC SQL DECLARE c5 CURSOR FOR SELECT db_fid, avg(uw_dauer) FROM log_tabelle WHERE cl_fid <> db_fid GROUP BY db_fid ORDER BY db_fid CHECKERR ("init_list: DECLARE CURSOR c5\n") EXEC SQL DECLARE c6 CURSOR FOR SELECT quellformat, zielformat FROM umwandlung ORDER BY zielformat, quellformat CHECKERR ("init_list: DECLARE CURSOR c6\n") EXEC SQL DECLARE c7 CURSOR FOR SELECT db_fid, cl_fid, avg(uw_dauer) FROM log_tabelle WHERE cl_fid <> db_fid GROUP BY cl_fid, db_fid ORDER BY cl_fid, db_fid CHECKERR ("init_list: DECLARE CURSOR c7\n") EXEC SQL DELETE FROM log_tabelle WHERE datum < CURRENT TIMESTAMP - 1 MONTH AND C-23 C.4 optimierer.sqc C-24 artikel<>'dummy' EXEC SQL OPEN c1 CHECKERR ("init_list: OPEN CURSOR c1\n") do { EXEC SQL FETCH c1 INTO :cl_fid if (SQLCODE != 0) break l1=*a *a=malloc(sizeof(list_t)) (*a)->node=cl_fid (*a)->size=0 (*a)->access=0 (*a)->ct_src=0.0 (*a)->ct_dest=0.0 (*a)->next=l1 maxattr=(maxattr>cl_fid)?maxattr:cl_fid } while ( 1 ) EXEC SQL CLOSE c1 CHECKERR ("init_list: CLOSE CURSOR c1") EXEC SQL OPEN c2 CHECKERR ("init_list: OPEN CURSOR c2\n") do { EXEC SQL FETCH c2 INTO :cl_fid, :cl_len, :num if (SQLCODE != 0) break for(l1=*al1!=NULLl1=l1->next) if(l1->node==cl_fid) { l1->size=cl_len l1->access=num } } while ( 1 ) EXEC SQL CLOSE c2 CHECKERR ("init_list: CLOSE CURSOR c2") EXEC SQL OPEN c3 CHECKERR ("init_list: OPEN CURSOR c3\n") do { C.4 optimierer.sqc C-25 EXEC SQL FETCH c3 INTO :db_fid, :db_len, :num if (SQLCODE != 0) break for(l1=*al1!=NULLl1=l1->next) if(l1->node==db_fid) l1->size=((double)l1->size*l1->access+(double)db_len*num)/ (l1->access+num) } while ( 1 ) EXEC SQL CLOSE c3 CHECKERR ("init_list: CLOSE CURSOR c3") EXEC SQL OPEN c4 CHECKERR ("init_list: OPEN CURSOR c6\n") do { EXEC SQL FETCH c4 INTO :cl_fid, :r_zeit if (SQLCODE != 0) break for(l1=*al1!=NULLl1=l1->next) if(l1->node==cl_fid) l1->ct_dest=r_zeit } while ( 1 ) EXEC SQL CLOSE c4 CHECKERR ("init_list: CLOSE CURSOR c4") EXEC SQL OPEN c5 CHECKERR ("init_list: OPEN CURSOR c5\n") do { EXEC SQL FETCH c5 INTO :db_fid, :r_zeit if (SQLCODE != 0) break for(l1=*al1!=NULLl1=l1->next) if(l1->node==db_fid) l1->ct_src=r_zeit } while ( 1 ) EXEC SQL CLOSE c5 CHECKERR ("init_list: CLOSE CURSOR c5") EXEC SQL OPEN c6 CHECKERR ("init_list: OPEN CURSOR c6\n") do { C.4 optimierer.sqc EXEC SQL FETCH c6 INTO :db_fid, :cl_fid if (SQLCODE != 0) break l2=*f *f=malloc(sizeof(list2_t)) (*f)->node1=db_fid (*f)->node2=cl_fid (*f)->comp_time=get_ctime(db_fid,cl_fid,*a) (*f)->next=l2 } while ( 1 ) EXEC SQL CLOSE c6 CHECKERR ("init_list: CLOSE CURSOR c6") EXEC SQL OPEN c7 CHECKERR ("init_list: OPEN CURSOR c7\n") do { EXEC SQL FETCH c7 INTO :db_fid, :cl_fid, :r_zeit if (SQLCODE != 0) break for(l2=*fl2!=NULL && r_zeit>0l2=l2->next) if(l2->node1==db_fid && l2->node2==cl_fid) l2->comp_time=r_zeit } while ( 1 ) EXEC SQL CLOSE c7 CHECKERR ("init_list: CLOSE CURSOR c7") return maxattr } /* Aproximation der Rechenzeit */ double get_ctime(long s, long d, list_t *l) { double result=0.0 while(l!=NULL) { if(l->node == s) result+=l->ct_src if(l->node == d) result+=l->ct_dest l=l->next } C-26 C.4 optimierer.sqc return result/2 } /* UNION-FIND-Algorithmus */ int fastfind(long *dad, int x, int y, int union1) { int i, j, t for(i=xdadi]>0i=dadi]) for(j=ydadj]>0j=dadj]) while(dadx]>0) { t=x x=dadx] dadt]=i } while(dady]>0) { t=y y=dady] dadt]=j } if(union1&&(i!=j)) if(dadj]<dadi]) { dadj]=dadj]+dadi]-1 dadi]=j } else { dadi]=dadi]+dadj]-1 dadj]=i } return i==j } /* Aufteilung in Zusammenhangskomponenten */ void split2compo(list_t **a, list2_t **f, list_list_t **c, long size) { list_t *l, *runner1, *runner3, *last, *runner list2_t *runner2 C-27 C.4 optimierer.sqc list_list_t *ll, *runnerl long *dad, i ll=NULL dad=calloc(size+1,sizeof(long)) memset(dad,0,(size+1)*sizeof(long)) for(runner2=*frunner2!=NULLrunner2=runner2->next) fastfind(dad,runner2->node1,runner2->node2,1) ll=*c *c=malloc(sizeof(list_list_t)) (*c)->next=ll (*c)->first=malloc(sizeof(list_t)) (*c)->first->node=1 (*c)->first->is_phys=0 (*c)->first->next=NULL for(runner=*arunner->node!=1runner=runner->next) (*c)->first->size=runner->size (*c)->first->access=runner->access for(i=2i<=sizei++) { for(ll=*cll!=NULLll=ll->next) if(fastfind(dad,ll->first->node,i,0)) { l=malloc(sizeof(list_t)) l->node=i l->is_phys=0 l->next=(*c)->first for(runner=*arunner->node!=irunner=runner->next) l->size=runner->size l->access=runner->access (*c)->first=l break } if(ll==NULL) { ll=*c *c=malloc(sizeof(list_list_t)) (*c)->next=ll (*c)->first=malloc(sizeof(list_t)) (*c)->first->node=i (*c)->first->is_phys=0 C-28 C.4 optimierer.sqc (*c)->first->next=NULL for(runner=*arunner->node!=irunner=runner->next) (*c)->first->size=runner->size (*c)->first->access=runner->access } } free(dad) } /* bin"are Addition auf den Attributen */ int next_base(list_t *a) { int old, add=1 while(add && a!=NULL) { old=a->is_phys a->is_phys=add-old add&=old a=a->next } return add } /* Kandidaten f"ur die Basis aus den Funktionen ausw"ahlen*/ int get_fcs(list_t *a, list2_t *fc, list2_t **fcn) { list2_t *l2 list_t *runner, *fastrun int status=1 long last *fcn=NULL while(fc!=NULL) { for(runner=arunner!=NULLrunner=runner->next) if(runner->node==fc->node1 && runner->is_phys==1) for(fastrun=afastrun!=NULLfastrun=fastrun->next) if(fastrun->node==fc->node2 && fastrun->is_phys!=1) { l2=*fcn *fcn=malloc(sizeof(list2_t)) (*fcn)->next=l2 C-29 C.4 optimierer.sqc C-30 (*fcn)->node1=fc->node1 (*fcn)->node2=fc->node2 (*fcn)->comp_time=fc->comp_time (*fcn)->is_base=0 fastrun->is_phys=2 } fc=fc->next } for(l2=*fcn,last=0l2!=NULLl2=l2->next) { l2->is_base=(l2->node2!=last) last=l2->node2 } for(runner=arunner!=NULLrunner=runner->next) if(!runner->is_phys) status=0 else runner->is_phys&=1 return status } /* Rotation auf den Gruppen der Funktionen zur Basisbestimmung */ int next_fc_base(list2_t *fc) { list2_t *l2 long last int old=1, h if(fc!=NULL) { last=fc->node2 old=0 } for(l2=fcl2!=NULL && l2->node2==last l2=l2->next) { h=l2->is_base l2->is_base=old old=h } if(l2!=NULL && old) { fc->is_base=old return next_fc_base(l2) C.4 optimierer.sqc } else return old } void free_list2(list2_t **l) { list2_t *runner while(*l!=NULL) { runner=(*l)->next free(*l) *l=runner } } /* Kostenfunktion: Speicherkosten */ double store_costs(list_t *base) { double result for(result=0.0base!=NULLbase=base->next) if(base->is_phys) result+=base->size return (1 - const_z) * const_cs * result } /* Kostenfunktion: Zugriffskosten f"ur A_phys */ double acc_costs_phys(list_t *base) { double result for(result=0.0base!=NULLbase=base->next) if(base->is_phys) result+=base->size*base->access return const_z * const_ca * result } /* Kostenfunktion: Zugriffskosten f"ur A_comp */ double acc_costs_comp(list2_t *fc, list_t *base) C-31 C.4 optimierer.sqc { list_t *l double result, avg_acc for(result=0.0fc!=NULLfc=fc->next) if(fc->is_base) { for(l=basel->node!=fc->node2l=l->next) avg_acc=l->access result+=fc->comp_time*avg_acc for(l=basel->node!=fc->node1l=l->next) result+=l->size*avg_acc*const_ca } return const_z * result } /* Optimale Basis sichern */ void copy_fc(list2_t **d, list2_t *s) { list2_t *l2 while(s!=NULL) { l2=*d *d=malloc(sizeof(list2_t)) (*d)->node1=s->node1 (*d)->node2=s->node2 (*d)->comp_time=s->comp_time (*d)->is_base=s->is_base (*d)->next=l2 s=s->next } } /* Neu zu speichernde Formate in die Datenbank "ubernehmen */ long add_phys_type(list_t *base) { list_t *comp, *phys long n_o_new=0 char command255] for(comp=basecomp!=NULLcomp=comp->next) C-32 C.4 optimierer.sqc C-33 { if(comp->is_opt==0) continue cl_fid=comp->node EXEC SQL SELECT name INTO :doctype FROM formate WHERE fid=:cl_fid for(phys=basephys!=NULLphys=phys->next) { if(phys->node==comp->node) continue db_fid=phys->node EXEC SQL DECLARE c CURSOR FOR SELECT DISTINCT name FROM artikel WHERE fid=:db_fid AND name NOT IN (SELECT name FROM artikel WHERE fid=:cl_fid) CHECKERR ("add_phys_type: DECLARE CURSOR c\n") EXEC SQL OPEN c CHECKERR ("add_phys_type: OPEN CURSOR c\n") do { EXEC SQL FETCH c INTO :docname if (SQLCODE != 0) break sprintf(command,"insert_copy %s %s",docname,doctype) printf("%s\n",command) system(command) n_o_new++ } while ( 1 ) EXEC SQL CLOSE c CHECKERR ("init_list: CLOSE CURSOR c1") } } EXEC SQL INSERT INTO log_tabelle SELECT cl_name, 'dummy', cl_fid, db_fid, AVG(cl_size), AVG(db_size), AVG(uw_dauer), CURRENT TIMESTAMP FROM log_tabelle WHERE cl_name='converter' GROUP BY cl_name, cl_fid, db_fid C.4 optimierer.sqc C-34 EXEC SQL DELETE FROM log_tabelle WHERE cl_name='converter' AND artikel<>'dummy' return n_o_new } /* nicht mehr zu speichernde Formate l"oschen */ void del_comp_type(list_t *base) { while(base!=NULL) { if(base->is_opt==0) { cl_fid=base->node EXEC SQL DELETE FROM artikel WHERE fid = :cl_fid printf("FID:%d deleted\n",cl_fid) } base=base->next } } /* Kommandozeile auswerten */ void get_args(int argc, char *argv]) { int ch int bflg, aflg, errflg extern char *optarg extern int optind, optopt while ((ch = getopt(argc, argv, ":a:s:z:")) != -1) switch (ch) { case 'a': const_ca=atof(optarg) break case 's': const_cs=atof(optarg) break case 'z': const_z=atof(optarg) break case ':': /* -a, -s or -z without arguments */ fprintf(stderr,"Option -%c requires an argument\n", optopt) errflg++ C.5 insert copy.sqc C-35 break case '?': fprintf(stderr,"Unrecognized option: - %c\n",optopt) errflg++ } if (errflg) { fprintf(stderr,"USAGE: %s -a value] -s value] -z value]\n", argv0]) exit (2) } } C.5 insert copy.sqc /****************************************************************** * * * insert_copy.sqc * * * * USAGE: insert_copy documentname documenttyp * * * * insert_copy liest das Dokument documentname in einem Format, * * das nach documenttyp umgewandelt werden kann, wandelt es um und * * speichert es wieder in der Datenbank. * * * ******************************************************************/ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <time.h> #include <sys/timers.h> #include <sys/ipc.h> #include <sys/types.h> #include <sql.h> #include <fcntl.h> #include "stat.h" #include "util.h" EXEC SQL INCLUDE SQLCA C.5 insert copy.sqc #define CHECKERR(CE_STR) #define #define #define #define MAXPIPES 15 MAXOPTS 15 EIN 1 AUS 0 C-36 if (check_error (CE_STR, &sqlca) != 0) \ return 1 struct fork_cmd { char *cmd char *opt struct fork_cmd *next } typedef struct fork_cmd s_fork_cmd int pipesMAXPIPES]2] s_fork_cmd* split_cmd(char *s) char * ausgabe(int nos) void eingabe(char *fname, int nos) void split_opts(char **spl, char *cmd, char *opts) char * umwandlung(char *fname, char *cmd) int main(int argc, char *argv]) { char errtxt200], *fname clock_t start_z struct stat filestat FILE *fp1 EXEC SQL BEGIN DECLARE SECTION SQL TYPE IS BLOB_FILE tmp_file short lobind long cl_fid long db_fid long db_len long cl_len double r_zeit char docname21] char doctype11] char uw_fkt201] C.5 insert copy.sqc C-37 char cl_host101] EXEC SQL END DECLARE SECTION /* Environment-Variable beim Aufruf "uber CGI nicht bekannt! */ putenv("DB2INSTANCE=db2ex") fp1 = freopen("insert.err", "a", stderr) if(argc!=3) { fprintf(stderr,"Number of args should be 3 NOT %d\n",argc) exit(1) } strncpy(docname,argv1],20) docname20]=0 strncpy(doctype,argv2],10) doctype10]=0 EXEC SQL CONNECT TO peter_db USER db2ex USING daba2 sprintf(errtxt,"CONNECT TO PETER_DB\n%s\n", getenv("DB2INSTANCE")) CHECKERR (errtxt) strcpy(tmp_file.name,tmpnam(NULL)) tmp_file.name_length = strlen(tmp_file.name) tmp_file.file_options = SQL_FILE_CREATE EXEC SQL SELECT a.inhalt INTO :tmp_file :lobind FROM artikel a,formate f WHERE a.fid=f.fid AND a.name=:docname AND f.name=:doctype if (SQLCODE != 0 || lobind < 0) { EXEC SQL DECLARE c2 CURSOR FOR SELECT a.inhalt, u.funktion, a.fid, f.fid FROM artikel a, umwandlung u, formate f WHERE a.fid=u.quellformat AND u.zielformat=f.fid AND a.name=:docname AND f.name=:doctype AND u.praeferenz=(SELECT max(u1.praeferenz) C.5 insert copy.sqc C-38 FROM umwandlung u1, artikel a1 WHERE a1.name=a.name AND u1.zielformat=f.fid AND u1.quellformat=a1.fid) EXEC SQL OPEN c2 EXEC SQL FETCH c2 INTO :tmp_file :lobind, :uw_fkt, :db_fid, :cl_fid if (SQLCODE != 0) { fprintf(stderr,"Can't create %s.%s\n",docname,doctype) remove(tmp_file.name) } else { stat(tmp_file.name,&filestat) db_len=filestat.st_size start_z=clock() fname=umwandlung(tmp_file.name,uw_fkt) r_zeit=(clock()-start_z)/(double)CLOCKS_PER_SEC stat(fname,&filestat) cl_len=filestat.st_size strcpy(tmp_file.name,fname) tmp_file.name_length = strlen(tmp_file.name) tmp_file.file_options = SQL_FILE_READ EXEC SQL INSERT INTO artikel VALUES(:docname,:cl_fid,:tmp_file) CHECKERR ("INSERT") strcpy(cl_host,"converter") EXEC SQL INSERT INTO log_tabelle VALUES (:cl_host, :docname, :cl_fid, :db_fid, :cl_len, :db_len, :r_zeit, CURRENT TIMESTAMP) CHECKERR ("log") EXEC SQL COMMIT fprintf(stderr,"Added %s.%s to database\n",docname,doctype) remove(tmp_file.name) C.5 insert copy.sqc C-39 } } else { fprintf(stderr,"Nothing to do!\n %s.%s is in the database\n", docname,doctype) remove(tmp_file.name) } EXEC SQL CONNECT RESET CHECKERR ("CONNECT RESET\n") return 0 } /* Formatumwandlung mittels Kindprozessen */ char * umwandlung(char *fname, char *cmd) { int nos=0, status char *optvMAXOPTS] s_fork_cmd *f1, *f f1=f=split_cmd(cmd) while(f!=NULL) { f=f->next pipe(pipes++nos]) } nos=0 pipe(pipesnos]) f=f1 eingabe(fname,nos) while(f!=NULL) { nos++ split_opts(optv,f->cmd,f->opt) switch(fork()) { case -1: fprintf(stderr,"Fehler beim Aufruf von fork " "%s %s\n",f->cmd,f->opt) exit(1) case 0: dup2(pipesnos-1]AUS],0) C.5 insert copy.sqc close(pipesnos-1]AUS]) close(pipesnos-1]EIN]) dup2(pipesnos]EIN],1) close(pipesnos]EIN]) fprintf(stderr,"Aufruf von execvp '%s' '%s'\n", f->cmd,f->opt) close(pipesnos]AUS]) execvp(f->cmd,optv) fprintf(stderr,"ACHTUNG FEHLER kein Aufruf von " "%s\n",f->cmd) exit(-1) default: close(pipesnos-1]AUS]) close(pipesnos]EIN]) } f=f->next } fname=ausgabe(nos) f=f1 wait(&status) wait(&status) while(f!=NULL) { f=f->next wait(&status) } return fname } /* Schreiben des BLOBs in die Eingabepipe des */ /* Konvertierungsprozesses */ void eingabe(char *fname, int nos) { char buf4096] int len, fd switch(fork()) { case -1: fprintf(stderr,"Fehler beim Aufruf von fork %d\n", C-40 C.5 insert copy.sqc nos) exit(1) case 0: fd=open(fname,O_RDONLY) while(len=read(fd,buf,4096)) write(pipes0]EIN],buf,len) close(fd) remove(fname) exit(0) default: close(pipes0]EIN]) } } /* Lesenen des umgewandelten BLOBs aus der Ausgabepipe des */ /* Konvertierungsprozesses */ char * ausgabe(int nos) { char buffer4096], *fname int nob, fd fname=strdup(tmpnam(NULL)) switch(fork()) { case -1: fprintf(stderr,"Fehler beim Aufruf von fork %d\n", nos) exit(1) case 0: fd=creat(fname,0666) while(nob=read(pipesnos]AUS],buffer,4096)) write(fd,buffer,nob) close(fd) exit(0) default: close(pipesnos]AUS]) } return fname } /* Aufspalten des Kommandostrings in Befehle und Komponenten */ s_fork_cmd* split_cmd(char *s) { char *del, *opt s_fork_cmd *f_base, *f_runner f_base=NULL C-41 C.5 insert copy.sqc while(*s && isspace(*s)) s++ while(*s) { if(f_base==NULL) f_runner=f_base=malloc(sizeof(s_fork_cmd)) else { f_runner->next=malloc(sizeof(s_fork_cmd)) f_runner=f_runner->next } f_runner->next=NULL del=opt=s while(*del && *del !='|') del++ while(*opt && !isspace(*opt) && *opt!='|') opt++ f_runner->cmd=malloc(opt-s+1) strncpy(f_runner->cmd,s,opt-s) f_runner->cmdopt-s]=0 while(*opt && isspace(*opt)) opt++ if (del-opt) { f_runner->opt=malloc(del-opt+1) strncpy(f_runner->opt,opt,del-opt) f_runner->optdel-opt]=0 } else f_runner->opt="" s=del while(*s && (isspace(*s)||*s=='|')) s++ } return f_base } /* Aufspalten der Optionen */ void split_opts(char **spl, char *cmd, char *opts) { *spl++=cmd while(*opts) { *spl++=opts C-42 C.6 mklter.c C-43 while(*opts && !isspace(*opts)) opts++ if(*opts) *opts++=0 while(*opts && isspace(*opts)) opts++ } *spl=NULL } C.6 mklter.c /*************************************************************** * * * source: mkfilter.c * * * * USAGE: mkfilter command args1 - args2 * * * * mkfilter f"uhrt den Befehl command als Filter aus. * * mkfilter entspricht vom Verhalten einer Folge von Befehlen * * * * cat >tmp_file1 * * command args1 tmp_file1 args2 tmp_file2 * * cat tmp_file2 * * rm tmp_file1 tmp_file2 * * * ***************************************************************/ #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <sys/types.h> #include <fcntl.h> #include "stat.h" void main(int argc, char *argv]) { char buffer8192], *out_file_name, *in_file_name int fd, i, len, flag=0 /* Namen f"ur tempor"are Dateien generieren */ in_file_name=strdup(tmpnam(NULL)) out_file_name=strdup(tmpnam(NULL)) C.6 mklter.c /* stdin in Datei umleiten */ fd=creat(in_file_name,0666) while(len=read(fileno(stdin),buffer,8192)) write(fd,buffer,len) close(fd) /* Kommandostring erzeugen */ buffer0]=0 for(i=1i<argci++) { if(!strcmp(argvi],"-")) { strcat(buffer,in_file_name) flag=1 } else strcat(buffer,argvi]) strcat(buffer," ") } if(!flag) { strcat(buffer,in_file_name) strcat(buffer," ") } strcat(buffer,out_file_name) fprintf(stderr,"EXECUTE: %s\n",buffer) /* Kommando ausf"uhren */ i=system(buffer) /* Ausgabedatei nach stdout kopieren */ fd=open(out_file_name,O_RDONLY) while(len=read(fd,buffer,8192)) write(fileno(stdout),buffer,len) close(fd) /* tempor"are Dateien l"oschen */ remove(in_file_name) remove(out_file_name) } C-44 C.7 Eingabeformular (HTML-Code) C-45 C.7 Eingabeformular (HTML-Code gekurzt) Content-type: text/html <HTML> <HEAD><TITLE>Eingabeformular</TITLE></HEAD> <BODY> <H1>Eingabeformular</H1> <HR> <FORM ACTION="/cgi-bin/db_interface" METHOD="GET"> Name des Artikels: <BR> <SELECT NAME="name" SIZE=8> <OPTION SELECTED>au <OPTION>bild1_14 <OPTION>bild1_15 <OPTION>bild1_16 <OPTION>bild1_17 <OPTION>bild1_18 <OPTION>bild1_19 <OPTION>bild1_20 <OPTION>bild1_21 <OPTION>bild1_22 <OPTION>bild1_23 <OPTION>bild1_24 . . . <OPTION>s_eastwood <OPTION>s_houdini <OPTION>s_kabwin95 <OPTION>s_schaf <OPTION>s_zucht <OPTION>scherneck_karte1 <OPTION>scherneck_karte2 <OPTION>scherneck_schloss </SELECT> <BR> gew&uumlnschtes Format: <BR> <SELECT NAME="format" SIZE=8> <OPTION SELECTED>au <OPTION>gif <OPTION>jpeg C.7 Eingabeformular (HTML-Code) <OPTION>pgm <OPTION>png <OPTION>ppm <OPTION>ps <OPTION>tiff <OPTION>wav </SELECT> <BR> <P> <INPUT TYPE="submit" VALUE="Abschicken"> </FORM> <HR> </BODY> </HTML> C-46