RRDTool für Oracle

Kürzlich bin ich im Internet auf ein interessantes Tool namens RRDTool gestoßen. RRD steht für Round-Robin-Database, was sich ungefähr mit “Reihum Datenbank” übersetzen lässt. Kurz erklärt bedeutet dies, die Datenbank speichert eine gewisse Menge an Daten, und sobald die Menge erreicht ist, werden alte Werte gelöscht bzw. überschrieben. Ein solches Vorgehen eignet sich natürlich besonders bei zeitabhängigen Daten, z.B. wenn Informationen immer nur für einen Monat in die Vergangenheit zur Verfügung stehen sollen.

Exakt hierfür wurde RRDTool auch entwickelt, nämlich zur Protokollierung von Netzwerktraffic, Routeraktivitäten und ähnlichem. Mittlerweile verwenden aber z.B. auch Wetterdienste das Tool zur Speicherung und Darstellung von Temperaturkurven. Eine lange Liste mit verschiedenen Auswertungsmöglichkeiten, die gleichzeitig auch die Mächtigkeit des recht schlanken Tools zeigt finden Sie hier.

Natürlich lies sich auch in meinem Umfeld schnell eine praktische Anwendungsmöglichkeit für das RRDTool finden…

Nach anfänglichen Verständnisschwierigkeiten mit der Syntax und dem Grundprinzip hinter RRDTool war schnell klar, dass sich RRDTool nur in Verbindung mit einer Wrapper Applikation sinnvoll einsetzen lässt. Die meisten Beispiele auf der Projektseite verwenden entweder die Linux Shell oder Perl. Auch für Ruby gibt es ein kleines Beispiel und sogar Bindings für die rrdtool.lib, ich habe mich jedoch auf den einfachen Aufruf des Commandline Tools aus Ruby heraus entschieden.

Nun aber zum Anwendungsfall: Für mich als Datenbankentwickler und “Teilzeit DBA” kann es natürlich nicht genug Performance Informationen über eine Datenbank bzw. ihre Laufzeit geben. Wann wird wieviel I/O erzeugt? Entsteht der I/O durch Full Table Scans obder durch Index Zugriffe? Klar, genau dafür gibt es Grid Control oder die Database Console, aber gerade auf Entwicklungsdatenbanken fehlt hin und wieder mal ein Grid Agent, oder mir der zugang zum Grid Control.

Als Vorbereitung meiner I/O Monitoring Lösung muss zunächst eine RR-Datenbank erzeugt werden. Unter Windows müssen alle kommenden Befehle an das RRDTool übrigens in einer Zeile stehen. Aus Gründen der Lesbarkeit habe ich hier allerdings darauf verzichtet und die Zeilen mit einem “\” getrennt, wie es eigentlich unter Linux üblich wäre.

rrdtool create intdev02.rrd \
-s 10 \
DS:scatteredread:DERIVE:100:0:U \
DS:sequentialread:DERIVE:100:0:U \
DS:parallelwrite:DERIVE:100:0:U \
DS:parallelread:DERIVE:100:0:U \
DS:singlewrite:DERIVE:100:0:U \
RRA:MAX:0.5:1:43200

Der Befehl erzeugt eine Datenbankdatei namens INTDEV02.rrd.  Der Parameter -s definiert den zeitlichen Abstand der Messwerte, d.h. die Datenbank speichert pro 10 Sekunden einen Messwert. Mit DS: werden mehrere Datesources, also Datenquellen definiert. Dies lässt sich ungefähr mit Tabellen in einer Datenbank vergleichen, wobei jede Tabelle immer exakt zwei Spalten hat, nämlich Zeitstempel und Wert. In meinem Beispiel legen wir 5 Datasources für die unterschiedlichen DB File I/O Typen and, also z.B. “db file scattered read” oder “db file parallel write”. Da der Wert in der Datenbank stets wächst, uns aber eigentlich nur die Veränderung seit dem letzten Messwert interessiert, wird die Datasource als DERIVE angelegt. Möglich wäre u.a. auch der Typ GAUGE, welcher sich z.B. bei Temperaturmessungen anbietet. Die letzten 3 Spalten definierten den Heartbeat der Datasource, den Minimal- und den Maximalwert. Auf den Heartbeat werde ich hier nicht genauer eingehen, wichtig ist jedoch, dass er eng mit dem “-s” Parameter verknüpft ist. Der minimale Wert für I/O liegt natürlich bei 0, der maximale bei “U” = unbekannt.

Verantwortlich für die Ermittlung der I/O Daten, sowie zum Update der RR-Datenbank ist wie schon erwähnt ein einfaches Ruby Script:

require 'rubygems'
require 'OCI8'

tool  = "/path/to/rrd"
filename = "intdev02.rrd"

sql      =<<EOL
   SELECT EVENT, TIME_WAITED
   FROM   V$SYSTEM_EVENT
   WHERE  EVENT LIKE 'db file%'
EOL

db = OCI8.new("username", "pwd", "dbid")
  while true do
    start_time = Time.now

    events = []
    times  = []

    db.exec(sql) do |event, time|
      events << event.sub!("db file ", "").sub!(" ", "")
      times  << time.to_i
    end

    `#{tool} updatev #{ filename} -t #{ events.join(":")} N:#{ times.join(":")}`

    total_time = Time.now - start_time
    sleep(10.0 - total_time)
  end
db.logoff

Die Funktion des Scripts ist schnell erklärt. Zunächst findet ein Connect auf die Datenbank statt. Um eine zyklische Ausführung des Statements zu ermöglichen wird innerhalb einer Schleife die Zeit gemessen und am Ende des Schleifendurchlaufs gewartet, bis die nächste Ausführung ansteht. In meinem Beispiel exakt 10 Sekunden.

Das SELECT Statement liefert alle “db file” Wait Events aus der Tabelle V$SYSTEM_EVENT. Die etwas kryptische Zeile erzeugt das Update Statement für die RR-Datenbank und speichert alle Messwert mit dem aktuellen Zeitstempel gleichzeitig für alle 5 Datasources.

Seiten: 1 2

Schlagworte: , ,

Kommentieren