Datenbanksystem für mind. 100k Inserts / Updates pro Sekunde

Hast du mal versucht, die IP-Strings mit regulären Ausdrücken zu checken?
 
IP Adressen sind ja eigentlich Integer mit 32bit (v4) oder 128bit (v6). Hier wäre es eine Maßnahme die IPs direkt als Int in die Logs zu schreiben oder aber spätestens beim Einlesen der Dateien umzuwandeln. Das Umwandeln frisst einmal Rechenzeit, spart perspektivisch jedoch Laufzeit und Speicher bei weiteren Aktionen.

Wenn du Logfiles rausschreibst die +100MB groß sind. Splitte die Logs sinnvoll auf bevor du sie einliest. Ein paar tausend Einträge je Vorgang sollten reichen (4MB Chunks nutze ich oft, wenn ich keine Muse habe genauer zu rechnen =) ). Damit hält sich dann auch dein Speicherbedarf in Grenzen, wenn jeder Thread nur 4MB Rohdaten bekommt.

Bei Bulk Inserts ist es mitunter nicht sinnvoll, immer sofort in die persistenten Tabellen zu schreiben. Deswegen gibt es bei vielen DBS die Möglichkeit temporäre Tabellen anzulegen. Da kannst du schreiben, sortieren, filtern, löschen und Indizes erstellen, ohne dass es deine Haupttabellen berührt. Zudem kannst du jedem Einlesethread deines Programms eine eigene temp. Table geben. Wenn die temp Tables dann passen, kannst du sie in deine Haupttabellen werfen.

Bei der Schreiblast wäre es auch ein Experiment wert, ob es evtl. sinnvoll ist kontinuirlich neute, persistente Tabellen anzulegen. Ein Reindex auf eine Tabelle, die 14 Tage Logs enthält wird auch dicker Hardware ordentlich Zeit fressen.
 
Drexel schrieb:
Zugegebenermaßen nur 111 Ergebniss, knapp über 1 Mio. Checks, Durchlaufzeit 0,3-0,4 Sekunden, Speicherverbrauch lt. Taskmanager 5 MB...

Das Ergebnis kann ich schlecht verifizieren da es ja random generierte IPs waren mit random generierten Netzen aber der Code sieht für mich korrekt aus. Ich hätte nicht erwartet, dass so schnell ist, du gehst ja bei jeder Zeile das Array von nets durch - d.h. mit steigender Anzahl von Subnetzen hast du hier auch mehr Iterationen und das nur um IPv4 von IPv6 zu unterscheiden. Das könnte man wahrscheinlich noch optimieren.

Und obwohl so viele Funktionen aufgerufen werden und jedes mal neue Arrays erstellt werden, scheint der Garbage Collector von C# deutlich besser zu sein.

Letztendlich sieht es so aus, als ob dein C# Code doppelt so schnell ist wie mein pypy Code. Hätte ich nicht gedacht. Aber für C# bräuchte ich wohl Mono auf den Linux Maschinen. Trotzdem cool, dass du es mal implementiert hast

@Piktogramm Die IPs kommen als Netflow rein. Theoretisch kann ich das raw öffnen und die Byte auslesen, praktisch dürfte dieser Parser aber noch ineffzienter sein. Auf die Netflows ("Logs") hab ich also keinen Einfluss.
 
Falc410 schrieb:
@Piktogramm Die IPs kommen als Netflow rein. Theoretisch kann ich das raw öffnen und die Byte auslesen, praktisch dürfte dieser Parser aber noch ineffzienter sein. Auf die Netflows ("Logs") hab ich also keinen Einfluss.
Wenn es ein Rohdatenformat gibt, indem das Meiste als Integer steht (Date als Unixtime, IP Adressen als Int32 bzw. Int128, ...), dann musst du an sich ja eben nichts parsen[1]. Das wäre ja gerade der Witz, dass mit (fixen) Offsets und Ranges gearbeitet werden kann. An der Stelle würdest du dir auch alle Casts von String zu Integer sparen. In den aller meisten Programmiersprachen ist das einiges Fixer als das Parsen von Strings.

Falls es nur menschenlesbare Datenformate gibt -> Schnellst möglich alles in die Datenbank kippen und das Parsing/Casting der Datenbankengine übergeben. Das ist normalerweise weniger Aufwand beim Implementieren und vergleichbar schnell wie handgeklöppelte Lösungen in PHP, Java, Python & Co.

[1] Je nachdem wie das Datenformat ausschaut evtl. die Unterscheidung der Protokollversion als IPv4/v6
 
Piktogramm schrieb:
Wenn es ein Rohdatenformat gibt, indem das Meiste als Integer steht (Date als Unixtime, IP Adressen als Int32 bzw. Int128, ...), dann musst du an sich ja eben nichts parsen[1].
Piktogramm hat recht. Der Code von Drexel wird die meiste Zeit für das Konvertieren der Strings zu Zahlen benötigen (IPAddress.Parse). Ich möchte nicht wissen wie viel Energie in der Welt verschwendet wird, weil immer wieder Zahlen aus textbasierten Datenformaten konvertiert werden müssen.

Falc410 schrieb:
Aber für C# bräuchte ich wohl Mono auf den Linux Maschinen.
In Java sieht der Code fast genauso aus und sollte auch etwa genauso schnell sein.
 
Falc410 schrieb:
d.h. mit steigender Anzahl von Subnetzen hast du hier auch mehr Iterationen und das nur um IPv4 von IPv6 zu unterscheiden. Das könnte man wahrscheinlich noch optimieren.

Ja mit steigender Anzahl von Netzen gibt es unter umständen mehr Schleifen, aber bei einem Treffer wird die Schleife abgebrochen. IPv4 vs IPv6 unterscheide ich eigentlich gar nicht großartig. Ich iterier einfach über alles drüber. Ich schau nur ob beide Adressen von der gleichen Version sind mit
if (nets[index].AddressFamily == compareAddress.AddressFamily),
wenn nicht spare ich mir weitere vergleiche und verwerfe das direkt.

Falc410 schrieb:
Und obwohl so viele Funktionen aufgerufen werden und jedes mal neue Arrays erstellt werden, scheint der Garbage Collector von C# deutlich besser zu sein.
Das einzige Array was immer wieder neu erstellt wird ist das Part Array mit
parts = line.Split(';');.
Die nets und maks Arrays bleiben erhalten.

Falc410 schrieb:
Letztendlich sieht es so aus, als ob dein C# Code doppelt so schnell ist wie mein pypy Code. Hätte ich nicht gedacht. Aber für C# bräuchte ich wohl Mono auf den Linux Maschinen. Trotzdem cool, dass du es mal implementiert hast

Kann morgen auch gerne mal nen Durchlauf mit 35 Netzen machen, ne größere Demo Datei mit mehr Random Werten wären auch cool, nicht dass da irgendwas gecached wird, wenn die Werte immer die gleichen sind...
 
Hab das spasseshalber mal nach Java übersetzt und dabei noch eine Verbesserung gefunden. Das Parsen der compareAddress kann bereits vor der for Schleife ausgeführt werden, was die Performance nochmal deutlich verbessert.
Java:
import java.io.BufferedReader;
import java.io.FileReader;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.List;

public class IpAddressChecker {

    public static void main(String[] args) throws Exception {
        long start = System.currentTimeMillis();

        InetAddress[] nets = new InetAddress[] {
                InetAddress.getByName("10.0.0.0"),
                InetAddress.getByName("192.168.0.0"),
                InetAddress.getByName("129.127.0.0"),
                InetAddress.getByName("2001:1000:0000::"),
                InetAddress.getByName("195.143.10.0"),
                InetAddress.getByName("195.143.17.0"),
                InetAddress.getByName("195.143.22.0"),
                InetAddress.getByName("195.143.43.0"),
                InetAddress.getByName("140.35.128.0"),
                InetAddress.getByName("151.60.48.0"),
        };

        InetAddress[] masks = new InetAddress[] {
                InetAddress.getByName("255.0.0.0"),
                InetAddress.getByName("255.255.0.0"),
                InetAddress.getByName("255.255.0.0"),
                InetAddress.getByName("ffff:ffff:f000::"),
                InetAddress.getByName("255.255.255.0"),
                InetAddress.getByName("255.255.255.0"),
                InetAddress.getByName("255.255.255.0"),
                InetAddress.getByName("255.255.255.0"),
                InetAddress.getByName("255.255.192.0"),
                InetAddress.getByName("255.255.254.0"),
        };

        List<String[]> result = new ArrayList<>();
        int checkCounter = 0;

        String line = null;
        String[] parts;
        BufferedReader file = new BufferedReader(new FileReader("d:/sample_data.txt"));
        while ((line = file.readLine()) != null) {
            parts = line.split(";");

            InetAddress compareAddress = InetAddress.getByName(parts[0]);
            for (int index = 0; index < nets.length; index++) {
                checkCounter++;
                if (nets[index].getAddress().length == compareAddress.getAddress().length) {
                    if (isInSameSubnet(nets[index], compareAddress, masks[index])) {
                        result.add(parts);
                        break;
                    }
                }
            }
        }

        System.out.println("Checks: " + checkCounter);
        System.out.println("Results: " + result.size());
        System.out.println("Duration: " + (System.currentTimeMillis() - start));
    }

    private final static boolean isInSameSubnet(InetAddress ip1, InetAddress ip2, InetAddress mask) {

        byte[] a1 = ip1.getAddress();
        byte[] a2 = ip2.getAddress();
        byte[] m = mask.getAddress();

        for (int i = 0; i < a1.length; i++)
            if ((a1[i] & m[i]) != (a2[i] & m[i]))
                return false;

        return true;
    }
}
 
  • Gefällt mir
Reaktionen: Drexel
Nolag schrieb:
Das Parsen der compareAddress kann bereits vor der for Schleife ausgeführt werden, was die Performance nochmal deutlich verbessert.
Jepp, da haste natürlich recht. ;)
Du hast aber das Skippen der ersten Zeile mit den Überschriften weggelassen, das führt zu einer Exception, wenn Du die in eine IPAdresse umwandeln willst. ;) Zumindest in C#...
 
Das .NET Framework ist out. C# kannst du mit .NET Core auch nativ unter Linux ausführen. Da brauchst du kein Mono mehr.
 
  • Gefällt mir
Reaktionen: Falc410
Der Java und C# Code hier arbeitet auch mit bitweisen Operatoren, hatte auch schon im Verdacht, dass da evtl. die bessere Performance herkommt, keine Ahnung wie die netaddr library arbeitet. Spricht natürlich generell nix dagegen, das auch in anderen Sprachen zu implementieren, aber ich denke C# und Java waren halt die Sprachen, die die jeweiligen Poster parat hatten...
 
Das C++ schneller ist, war mir schon bewusst. Aber ich hätte nicht erwartet, dass Python bzw. diese Library so langsam ist (gerade im Vergleich zu C# und Java die auch beide nicht gerade für Performance bekannt sind).

Und ursprünglich hatte ich ja auch eher Sorge um das DBS und nicht den Code vom Script der vorher läuft. Für einen PoC reicht mir das erstmal, wenn es denn mal Produktiv gehen sollte, dann muss ich mal schauen mit was es implementiert wird.

Auf jeden Fall wieder viel gelernt und die Erkenntnis, dass die 60.000 Datensätze die pro Sekunde reinkommen nur zu einem Bruchteil überhaupt in die Datenbank wandern müssen (konnte viel aussortieren).
 
Das C++ schneller ist, war mir schon bewusst. Aber ich hätte nicht erwartet, dass Python bzw. diese Library so langsam ist (gerade im Vergleich zu C# und Java die auch beide nicht gerade für Performance bekannt sind).

ich hab paarmal python zu c++ portiert und bin durchschnittlich ca um faktor 1000 schneller geworden damit

das kann aber nur gelingen wenn rechen- oder speicherintensive dinge in python gemacht werden
Eine operation in bibliotheken wie zb nen filter in opencv dauert immer etwa gleich lange- egal ob der aufruf von c++ oder von python aus gemacht wird
 
C# oder Java sind nicht per se langsam, die Übersetzung zur Runtime von Byte zu Maschinencode vermittelt oft einen langsamen Eindruck oder schlecht gemacht GUIs. Bei so wenig Code der dann oft durchlaufen wird, fällt die Übersetzung aber nicht ins Gewicht. Ebenso wie die Garbage Collection die den Thread anhält fällt eigentlich auch wenig ins Gewicht. Wenn der Code einmal voll übersetzt ist, sind sie auch recht fix.
 
Drexel schrieb:
C# oder Java sind nicht per se langsam
ich vermute, dass das im speziellen auf die "faktor 1000 schneller" aussage bezogen war; da stimme ich zu. und fuer C# kann ich mangels genug erfahrung nicht sprechen, aber C++ und java sind gerne mal um einen faktor 2 auseinander - das durfte ich bei manchen simulationen schon leidlicherweise erfahren.

gerade bei rechenintensiven problemen wie dem im OP traue ich maschinennaeheren implementierungen deshalb - sei das meinetwegen durch einbindung nativen codes - durchaus einen sehr starken performancegewinn zu.
 
Solche Aussagen sind in der Regel recht wertlos, es fehlen schlicht alle notwendige Angaben um den Spaß einordnen zu können. 100.000Inserts /s bekommt man auch mit sqlite hin. Die Frage ist eher, wie das Ganze ausschaut, wenn ein paar Constraints, Trigger und Indizes mit im Spiel sind.
An der Stelle wird es haarig. Genauso wie der TE ja eigentlich kein Problem hat, dass die DB nicht performt, sondern dass er enorm viel Rechenzeit auf das (unnötige) parsen der Daten wirft.
 
Zurück
Oben