Seite 1 von 3

Performanceprobleme bei der Nutzung von MySQL

Verfasst: Sonntag 29. August 2010, 13:11
von Colin Finck
Hallo zusammen,

ich versuche momentan JVerein an eine externe MySQL-Datenbank auf einem anderen Server anzubinden.
Dies funktioniert generell mit der beschriebenen Konfiguration zur Nutzung von MySQL, jedoch stoße ich auf arge Performanceprobleme: Meine Buchungsliste enthält zur Zeit knapp 250 Einträge. Nach dem Klick auf Buchungen benötigt JVerein 3 Minuten und 18 Sekunden (handgestoppt), bis die Liste angezeigt wird.

Ich habe nun das Plugin mal durch den Profiler gejagt und habe als einen Grund die Methode checkConnection in der Klasse DBSupportMySQLImpl ausgemacht. Durch das Entfernen der kompletten Methode sinkt die Zeit auf "nur" noch 1 Minute 20 Sekunden.
Da diese Methode lediglich eine "SELECT 1"-Abfrage ausführt, jedoch im Falle eines Verbindungsverlustes nur die Exception behandelt, sehe ich auch keinen Grund, diese länger zu behalten. Sollte die Verbindung an einer Stelle im Code abbrechen, wird halt der dortige Exception-Handler und nicht mehr die checkConnection-Methode die Exception abfangen.

Die restliche Zeit wird laut Profiler größtenteils in der Methode load der Klasse AbstractDBObject aus de.willuhn.datasource.db verbracht. Aufgrund von CVS-Problemen bei BerliOS konnte ich mir den entsprechenden Code jedoch noch nicht ansehen.

Beste Grüße,

Colin Finck

Re: Performanceprobleme bei der Nutzung von MySQL

Verfasst: Sonntag 29. August 2010, 13:40
von heiner
Hallo Colin,

hast du mal mit einem MySQL-Client von deinem Rechner aus SQL-Statements gegen deine Datenbank abgesetzt? Die Performance-Probleme können diverse Gründe haben.

Netzwerkgeschwindigkeit? Geschwindigkeit des DB-Servers?

Kannst du mal die Gründe für den Einsatz einer MySQL-Datenbank nennen? Hast du dir schon mal http://www.jverein.de/forum/viewtopic.p ... p=583#p583 angesehen?

Heiner

Re: Performanceprobleme bei der Nutzung von MySQL

Verfasst: Sonntag 29. August 2010, 15:16
von Colin Finck
heiner hat geschrieben:hast du mal mit einem MySQL-Client von deinem Rechner aus SQL-Statements gegen deine Datenbank abgesetzt?
Ja, auch mein eigenes Jameica-Plugin zur Formatierung einer Spendenliste für die Website greift über eine von den Jameica-Klassen abgeleitete MySQL-Klasse auf einen ähnlichen Datenbestand zu. Das Laden der gesamten Liste, welche innerhalb des Datenbanksystems komplexer als die Buchungstabelle aufgebaut ist, dauert hier nur 3 Sekunden.
heiner hat geschrieben:Kannst du mal die Gründe für den Einsatz einer MySQL-Datenbank nennen? Hast du dir schon mal http://www.jverein.de/forum/viewtopic.p ... p=583#p583 angesehen?
Für mein oben genanntes Plugin führe ich über Views und Stored Procedures Daten aus der JVerein-Buchungstabelle und einer anderen Tabelle zu einer gesamten Spendenliste zusammen. Ich bin mir nicht sicher, ob die Embedded-Datenbanken diese Funktionen ebenfalls unterstützen. In jedem Fall würden jedoch meine verwendeten MySQL-Administrationswerkzeuge dafür nicht mehr funktionieren.
Viel wichtiger ist jedoch, dass ich, ähnlich wie in dem von dir verlinkten Beitrag, mehreren Benutzern direkten Lesezugriff auf die Daten ermöglichen möchte. Zudem wird zur Vorbeugung vor Datenverlust eine existierende MySQL-Infrastruktur mit täglichen Backups verwendet.

Beste Grüße,

Colin

Re: Performanceprobleme bei der Nutzung von MySQL

Verfasst: Sonntag 12. Dezember 2010, 17:24
von Colin Finck
Hallo nochmal,

mit den letzten Jameica- und JVerein-Releases hat sich die Wartezeit bereits drastisch verringert und liegt nun in meinen Tests bei rund 50 Sekunden.
Die Funktion checkConnection ist jedoch weiterhin für einen Großteil der unnötigen Wartezeit verantwortlich. Da ein komplettes Entfernen dieser Funktion wahrscheinlich nicht infrage kommt, schlage ich folgenden Patch vor:

Code: Alles auswählen

Index: DBSupportMySqlImpl.java
===================================================================
RCS file: /cvsroot/jverein/jverein/src/de/jost_net/JVerein/server/DBSupportMySqlImpl.java,v
retrieving revision 1.10
diff -u -r1.10 DBSupportMySqlImpl.java
--- DBSupportMySqlImpl.java	15 Oct 2010 09:58:27 -0000	1.10
+++ DBSupportMySqlImpl.java	12 Dec 2010 16:16:51 -0000
@@ -158,17 +158,24 @@
     return false;
   }
 
+  private long lastCheck = 0;
+  
   /**
    * @see de.willuhn.jameica.hbci.rmi.DBSupport#checkConnection(java.sql.Connection)
    */
   public void checkConnection(Connection conn) throws RemoteException
   {
+	long newCheck = System.currentTimeMillis();
+	if ((newCheck - lastCheck) < (10 * 1000L))
+	  return; // Wir checken hoechstens aller 10 Sekunden
+	    
     Statement s = null;
     ResultSet rs = null;
     try
     {
       s = conn.createStatement();
       rs = s.executeQuery("select 1");
+      lastCheck = newCheck;
     }
     catch (SQLException e)
     {
Damit verringert sich die Wartezeit in meinen Tests auf ca. 18 Sekunden. Der Code stammt übrigens nicht von mir, sondern ist exakt so bereits vor einem Jahr in Hibiscus' checkConnection-Funktion der Klasse AbstractDBSupportImpl eingeflossen.
Die restliche Wartezeit kommt daher, dass die Jameica-Plattform für jeden Fremdschlüssel jedes Datensatzes eine zusätzliche Einzelabfrage durchführt (AbstractDBObject::load-Funktion). Dieses Problem lässt sich wohl ohne große interne Änderungen nicht lösen.

Beste Grüße,

Colin

Re: Performanceprobleme bei der Nutzung von MySQL

Verfasst: Sonntag 12. Dezember 2010, 20:33
von heiner
Hallo Colin,

stellst du mir den Patch im Eclipse-Format zur Verfügung?

Heiner

Re: Performanceprobleme bei der Nutzung von MySQL

Verfasst: Montag 13. Dezember 2010, 17:24
von Colin Finck
heiner hat geschrieben:stellst du mir den Patch im Eclipse-Format zur Verfügung?
Ok, hier der nächste Versuch:

Code: Alles auswählen

### Eclipse Workspace Patch 1.0
#P jverein
Index: src/de/jost_net/JVerein/server/DBSupportMySqlImpl.java
===================================================================
RCS file: /cvsroot/jverein/jverein/src/de/jost_net/JVerein/server/DBSupportMySqlImpl.java,v
retrieving revision 1.10
diff -u -r1.10 DBSupportMySqlImpl.java
--- src/de/jost_net/JVerein/server/DBSupportMySqlImpl.java	15 Oct 2010 09:58:27 -0000	1.10
+++ src/de/jost_net/JVerein/server/DBSupportMySqlImpl.java	13 Dec 2010 16:11:39 -0000
@@ -158,17 +158,24 @@
     return false;
   }
 
+  private long lastCheck = 0;
+ 
   /**
    * @see de.willuhn.jameica.hbci.rmi.DBSupport#checkConnection(java.sql.Connection)
    */
   public void checkConnection(Connection conn) throws RemoteException
   {
+   long newCheck = System.currentTimeMillis();
+   if ((newCheck - lastCheck) < (10 * 1000L))
+     return; // Wir checken hoechstens aller 10 Sekunden
+      
     Statement s = null;
     ResultSet rs = null;
     try
     {
       s = conn.createStatement();
       rs = s.executeQuery("select 1");
+      lastCheck = newCheck;
     }
     catch (SQLException e)
     {
Ich habe übrigens soeben von Olaf Willuhn erfahren, dass in Hibiscus die restlichen Performanceprobleme im Zusammenspiel mit MySQL durch Caching einiger Tabellenwerte gelöst wurden.
Werde mir das demnächst mal anschauen und dann hoffentlich noch einen Patch veröffentlichen.

Grüße,

Colin

Re: Performanceprobleme bei der Nutzung von MySQL

Verfasst: Montag 13. Dezember 2010, 20:59
von heiner
Hallo Colin,

ich habe deinen Patch eingecheckt. Weitere Patches kannst du mir gerne schicken.

Heiner

Re: Performanceprobleme bei der Nutzung von MySQL

Verfasst: Montag 13. Dezember 2010, 21:57
von Colin Finck
heiner hat geschrieben:ich habe deinen Patch eingecheckt. Weitere Patches kannst du mir gerne schicken.
Vielen Dank!

Der nächste Patch kommt auch sofort, denn ich habe schon wieder Code aus Hibiscus geklaut und damit alle meine Performanceprobleme im Zusammenhang mit der Buchungsliste behoben :-)
Genau wie Hibiscus wird jetzt in JVerein ein Cache zur Speicherung wiederkehrender Werte (in diesem Fall der Buchungsarten und Konten) verwendet. Der Aufruf meiner Liste über eine externe MySQL-Verbindung dauert jetzt nur noch 2 Sekunden.

Code: Alles auswählen

Index: src/de/jost_net/JVerein/server/BuchungImpl.java
===================================================================
RCS file: /cvsroot/jverein/jverein/src/de/jost_net/JVerein/server/BuchungImpl.java,v
retrieving revision 1.14
diff -u -r1.14 BuchungImpl.java
--- src/de/jost_net/JVerein/server/BuchungImpl.java	15 Oct 2010 09:58:27 -0000	1.14
+++ src/de/jost_net/JVerein/server/BuchungImpl.java	13 Dec 2010 20:31:39 -0000
@@ -140,15 +140,7 @@
   @Override
   protected Class getForeignObject(String field)
   {
-    if ("buchungsart".equals(field))
-    {
-      return Buchungsart.class;
-    }
-    else if ("konto".equals(field))
-    {
-      return Konto.class;
-    }
-    else if ("mitgliedskonto".equals(field))
+    if ("mitgliedskonto".equals(field))
     {
       return Mitgliedskonto.class;
     }
@@ -172,7 +164,12 @@
 
   public Konto getKonto() throws RemoteException
   {
-    return (Konto) getAttribute("konto");
+    Integer i = (Integer) super.getAttribute("konto");
+    if (i == null)
+      return null; // Keine Buchungsart zugeordnet
+
+    Cache cache = Cache.get(Konto.class, true);
+    return (Konto) cache.get(i);
   }
 
   public void setKonto(Konto konto) throws RemoteException
@@ -280,7 +277,12 @@
 
   public Buchungsart getBuchungsart() throws RemoteException
   {
-    return (Buchungsart) getAttribute("buchungsart");
+    Integer i = (Integer) super.getAttribute("buchungsart");
+    if (i == null)
+      return null; // Keine Buchungsart zugeordnet
+    
+    Cache cache = Cache.get(Buchungsart.class, true);
+    return (Buchungsart) cache.get(i);
   }
 
   public int getBuchungsartId() throws RemoteException
@@ -360,6 +362,12 @@
       }
     }
 
+    if ("buchungsart".equals(fieldName))
+      return getBuchungsart();
+    
+    if ("konto".equals(fieldName))
+      return getKonto();
+    
     return super.getAttribute(fieldName);
   }
 
Index: src/de/jost_net/JVerein/server/BuchungsartImpl.java
===================================================================
RCS file: /cvsroot/jverein/jverein/src/de/jost_net/JVerein/server/BuchungsartImpl.java,v
retrieving revision 1.10
diff -u -r1.10 BuchungsartImpl.java
--- src/de/jost_net/JVerein/server/BuchungsartImpl.java	15 Oct 2010 09:58:28 -0000	1.10
+++ src/de/jost_net/JVerein/server/BuchungsartImpl.java	13 Dec 2010 20:31:40 -0000
@@ -44,6 +44,7 @@
 import de.jost_net.JVerein.JVereinPlugin;
 import de.jost_net.JVerein.rmi.Buchungsart;
 import de.jost_net.JVerein.rmi.Buchungsklasse;
+import de.jost_net.JVerein.server.Cache;
 import de.willuhn.datasource.db.AbstractDBObject;
 import de.willuhn.logging.Logger;
 import de.willuhn.util.ApplicationException;
@@ -170,9 +171,15 @@
     setAttribute("buchungsklasse", buchungsklasse);
   }
 
-  @Override
-  public Object getAttribute(String fieldName) throws RemoteException
+  public void delete() throws RemoteException, ApplicationException
+  {
+    super.delete();
+    Cache.get(Buchungsart.class, false).remove(this); // Aus Cache loeschen
+  }
+  
+  public void store() throws RemoteException, ApplicationException
   {
-    return super.getAttribute(fieldName);
+    super.store();
+    Cache.get(Buchungsart.class, false).put(this); // Cache aktualisieren
   }
 }
Index: src/de/jost_net/JVerein/server/Cache.java
===================================================================
RCS file: src/de/jost_net/JVerein/server/Cache.java
diff -N src/de/jost_net/JVerein/server/Cache.java
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ src/de/jost_net/JVerein/server/Cache.java	1 Jan 1970 00:00:00 -0000
@@ -0,0 +1,167 @@
+package de.jost_net.JVerein.server;
+
+import java.rmi.RemoteException;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+import de.willuhn.datasource.rmi.DBIterator;
+import de.willuhn.datasource.rmi.DBObject;
+import de.willuhn.datasource.rmi.ObjectNotFoundException;
+import de.jost_net.JVerein.Einstellungen;
+
+/**
+ * Cache fuer oft geladene Fachobjekte.
+ */
+class Cache
+{
+  private final static de.willuhn.jameica.system.Settings settings = new de.willuhn.jameica.system.Settings(Cache.class);
+  private static int timeout = 0;
+  
+  // Enthaelt alle Caches.
+  private final static Map<Class,Cache> caches = new HashMap<Class,Cache>();
+  
+  // Der konkrete Cache
+  private Map<String,DBObject> data = new HashMap<String,DBObject>();
+  private Class<? extends DBObject> type = null;
+  private long validTo = 0;
+  
+  static
+  {
+    settings.setStoreWhenRead(false);
+    
+    // Das Timeout betraegt nur 10 Sekunden. Mehr brauchen wir nicht.
+    // Es geht ja nur darum, dass z.Bsp. beim Laden der Umsaetze die
+    // immer wieder gleichen zugeordneten Konten oder Umsatz-Kategorien
+    // nicht dauernd neu geladen sondern kurz zwischengespeichert werden
+    // Das Timeout generell wird benoetigt, wenn mehrere Hibiscus-Instanzen
+    // sich eine Datenbank teilen. Andernfalls wuerde Hibiscus die
+    // Aenderungen der anderen nicht mitkriegen
+    timeout = settings.getInt("timeout.seconds",10);
+  }
+
+  /**
+   * ct.
+   */
+  private Cache()
+  {
+    touch();
+  }
+  
+  /**
+   * Aktualisiert das Verfallsdatum des Caches.
+   */
+  private void touch()
+  {
+    this.validTo = System.currentTimeMillis() + (timeout * 1000);
+  }
+  
+  /**
+   * Liefert den Cache fuer den genannten Typ.
+   * @param type der Typ.
+   * @param init true, wenn der Cache bei der Erzeugung automatisch befuellt werden soll.
+   * @return der Cache.
+   * @throws RemoteException
+   */
+  static Cache get(Class<? extends DBObject> type, boolean init) throws RemoteException
+  {
+    Cache cache = caches.get(type);
+    
+    if (cache != null)
+    {
+      if (cache.validTo < System.currentTimeMillis())
+      {
+        caches.remove(type);
+        cache = null; // Cache wegwerfen
+      }
+      else
+      {
+        cache.touch(); // Verfallsdatum aktualisieren
+      }
+    }
+    
+    // Cache erzeugen und mit Daten fuellen
+    if (cache == null)
+    {
+      cache = new Cache();
+      cache.type = type;
+      
+      if (init)
+      {
+        // Daten in den Cache laden
+        DBIterator list = Einstellungen.getDBService().createList(type);
+        while (list.hasNext())
+        {
+          DBObject o = (DBObject) list.next();
+          cache.data.put(o.getID(),o);
+        }
+      }
+      caches.put(type,cache);
+    }
+    return cache;
+  }
+  
+  /**
+   * Liefert ein Objekt aus dem Cache.
+   * @param id die ID des Objektes.
+   * @return das Objekt oder NULL, wenn es nicht existiert.
+   * @throws RemoteException
+   */
+  DBObject get(Object id) throws RemoteException
+  {
+    if (id == null)
+      return null;
+    
+    String s = id.toString();
+    
+    DBObject value = data.get(s);
+    
+    if (value == null)
+    {
+      // Noch nicht im Cache. Vielleicht koennen wir es noch laden
+      try
+      {
+        value = (DBObject) Einstellungen.getDBService().createObject(type,s);
+        put(value); // tun wir gleich in den Cache
+      }
+      catch (ObjectNotFoundException one)
+      {
+        // Objekt existiert nicht mehr
+      }
+    }
+    return value;
+  }
+  
+  /**
+   * Speichert ein Objekt im Cache.
+   * @param object das zu speichernde Objekt.
+   * @throws RemoteException
+   */
+  void put(DBObject object) throws RemoteException
+  {
+    if (object == null)
+      return;
+    data.put(object.getID(),object);
+  }
+  
+  /**
+   * Entfernt ein Objekt aus dem Cache.
+   * @param object das zu entfernende Objekt.
+   * @throws RemoteException
+   */
+  void remove(DBObject object) throws RemoteException
+  {
+    if (object == null)
+      return;
+    data.remove(object.getID());
+  }
+  
+  /**
+   * Liefert alle Werte aus dem Cache.
+   * @return Liste der Werte aus dem Cache.
+   */
+  Collection<DBObject> values()
+  {
+    return data.values();
+  }
+}
Index: src/de/jost_net/JVerein/server/KontoImpl.java
===================================================================
RCS file: /cvsroot/jverein/jverein/src/de/jost_net/JVerein/server/KontoImpl.java,v
retrieving revision 1.8
diff -u -r1.8 KontoImpl.java
--- src/de/jost_net/JVerein/server/KontoImpl.java	15 Oct 2010 09:58:28 -0000	1.8
+++ src/de/jost_net/JVerein/server/KontoImpl.java	13 Dec 2010 20:31:40 -0000
@@ -187,12 +187,6 @@
     setAttribute("hibiscusid", id);
   }
 
-  @Override
-  public Object getAttribute(String fieldName) throws RemoteException
-  {
-    return super.getAttribute(fieldName);
-  }
-
   public DBIterator getKontenEinesJahres(Geschaeftsjahr gj)
       throws RemoteException
   {
@@ -206,4 +200,15 @@
     return konten;
   }
 
+  public void delete() throws RemoteException, ApplicationException
+  {
+    super.delete();
+    Cache.get(Konto.class, false).remove(this); // Aus Cache loeschen
+  }
+  
+  public void store() throws RemoteException, ApplicationException
+  {
+    super.store();
+    Cache.get(Konto.class, false).put(this); // Cache aktualisieren
+  }
 }
Folgendes sei jedoch noch angemerkt:
  • Der Patch berücksichtigt zurzeit tatsächlich nur die oben genannten Tabellen. Alle Tabellen, welche sich noch in getForeignObject befinden, werden weiterhin performancelastig einzeln abgefragt. Da ich die anderen Tabellen noch nicht genutzt habe, ist das für mich kein Problem, aber auf Dauer sollten diese sicherlich auch auf den Cache umgestellt werden. Gleiches gilt für Fremdschlüssel-Tabellen in anderen Fachobjekten.
  • Zum Hinzufügen einer Fremdschlüssel-Tabelle zum Cache ist diese aus getForeignObject zu entfernen und in getAttribute entsprechend einzufügen (siehe Patch). Weiterhin muss die entsprechende get-Funktion (z.B. getKonto) geändert werden, um den Cache zu nutzen. Dem Fachobjekt, welche diese Fremdschlüssel-Tabelle repräsentiert, sind überschriebene delete- und store-Funktionen hinzuzufügen, um das Objekt jedesmal auch im Cache zu speichern. Sollte alles beim Blick auf den Patch offensichtlich werden, aber ich erkläre es lieber noch mal :-)
  • Überschriebene getAttribute-Funktionen, welche ausschließlich die gleiche Funktion der Superklasse aufrufen, habe ich in diesem Zuge entfernt, da sie in meinen Augen unnötig sind.
Grüße,

Colin

Re: Performanceprobleme bei der Nutzung von MySQL

Verfasst: Dienstag 14. Dezember 2010, 20:33
von heiner
Hallo Colin,

sobald ich deinen 2. Patch im Eclipse-Format habe, werde ich ihn einchecken.

Heiner

Re: Performanceprobleme bei der Nutzung von MySQL

Verfasst: Dienstag 14. Dezember 2010, 20:49
von Colin Finck
heiner hat geschrieben:sobald ich deinen 2. Patch im Eclipse-Format habe, werde ich ihn einchecken.
Das "Eclipse-Format" ist auch nur das normale Unified-Format, welches ich bisher bei allen Patches genutzt habe. In diesem Fall hab ich lediglich angegeben, dass der Patch nur von den JVerein-Änderungen erstellt werden soll und nicht von allen offenen Projekten. Daher fehlt die "Eclipse Workspace Patch"-Zeile.
Wenn du aber das JVerein-Projekt rechtsklickst und dann Team --> Apply Patch wählst, sollte er sich ohne Probleme anwenden lassen.

Aber warte besser noch ein wenig mit dem Einchecken.
Hab den heute noch ein bisschen mehr durchgetestet und folgenden Fehler festgestellt: Wenn ich eine neue Buchung erstelle und dort ein Konto angebe, werden alle angezeigten Buchungseinträge ab dem nächsten Klick auf "Buchungen" über dieses Konto gefiltert. Ich muss erst wieder bei Konto "kein Konto" anklicken, damit mir alle Buchungen angezeigt werden.
Kann noch nicht mit Sicherheit sagen, dass dies durch den Patch verursacht wurde, aber zumindest weisen alte JVerein-Versionen dieses Problem nicht auf.

Grüße,

Colin