SQLite plattgemacht ;-)



  • Scheppertreiber schrieb:

    Der Schluss "XXX GB Daten/Jahr => brauchst du Oracle Cluster" ist vollkommener Quatsch.

    Klar. Es ging mir um die Anzahl Records, die Datenmenge selbst ist unkritisch
    (es sind Druckdaten in XML zusammen mit einem PDF das im Idealfall das gleiche
    anzeigt). Aus den einzelnen Positionen des Lieferscheins wird die fragliche
    Datenbank aufgebaut. Der Kunde kann die Daten dann übers Web abfragen (klar,
    Flaschenhals Web).

    Ja, egal, auch die Anzahl der Records alleine ist nicht ausschlaggebend.
    Wenn ich 100 Mio. Records in einer Tabelle habe, und über nen Index 100 davon raushole, dann geht das relativ fix. Mit jedem System, sei's nun MSSQL, Oracle oder SQLite.
    Wenn der Herr Datenbank aus 100 Mio. Datensätzen dann 100.000 lesen muss, dann wird die Sache schon interessanter. Wenn es bei sowas nicht möglich ist die Daten physikalisch so abzulegen, dass er für die 100.000 Records nur z.B. 1000 Pages braucht, dann wird man LANGE warten - auch wieder egal mit welchem System.
    Der grösste Unterschied zwischen SQLite und anderen Datenbanken ist da wohl, dass andere Datenbanken einem mehr Möglichkeiten geben die Daten physikalisch passend anzuordnen. (Stichwort Clustered Index)

    Die einzelnen Suchbegriffe haben immer konstante Längen in Byte. eigentlich
    müßte da ein DBMS wie zB Btrieve oder DBASE erheblich schneller wie eins mit
    flexiblen Satzlängen sein. dBASE ist tot und Btrieve heute zu teuer (die
    spinnen mit ihren Preisen).

    Ich wüsste keinen Vorteil den DBMS' mit fixen Zeilengrössen hier haben sollten.

    Die Daten selbst (PDF, XML) lege ich in Tagescontainern ab, die relevanten
    Suchbegriffe in einer Datenbank (also keine Blobs die mir alles noch mehr
    aufblähen würden). Dazu die Tabellen mit den Suchbegriffen, jeweils mit einem
    Pointer auf die Tagescontainer. Sortiert sind diese Tabellen nach dem
    jeweiligen Schbegriff, mit ein paar vergleichen habe ich den ersten Treffer
    (reine Lehre wäre natürlich ein Binärbaum, dauert aber zu lange, den zu
    erstellen. Ausßerdem sind es max 20 Vergleiche bis ich den ersten am
    Wickel habe. So steckt der "binary tree" in der Suche).

    Macht ne Datenbank nicht wesentlich anders, nur dass hier B-Bäume zum Einsatz kommen - die den Vorteil haben dass man sie billiger updaten kann als sortierte Listen. Und gegenüber binären Bäumen den Vorteil haben dass sie "Disk-IO-freundlich" aufgebaut sind.

    Wenn du die Tables als Clustered Index auf den Suchbegriff anlegst, dann wirst du sogar deutlich unter 20 IOs/Lookup kommen - ich würde eher mit <= 2 (physikalischen) IOs/Lookup rechnen.
    Und selbst wenn es nur ein normaler Index ist, wird es sich im Bereich <=5 IOs/Lookup abspielen.

    ----

    Scheppertreiber schrieb:

    Ich habe für den Import und die Bereitstellung ein Zeitfenster von ca 2h.
    Dann noch heftige Probleme, große Einzeldateien täglich zu sichern ...

    Hast du...
    ..."PRAGMA journal_mode = OFF" und "PRAGMA synchronous = OFF" zum Aufbauen der Datenbank gesetzt (wenn während dem Aufbauen was passiert muss man halt von vorne anfangen - d.h. da kann man auf ACID gut verzichten)?
    ...die Daten alle ohne Indexe importiert und die Indexe erst danach aufgebaut?
    ...die Cache-Grösse mit PRAGMA cache_size hochgedreht?



  • Hast du...

    Jepp.

    Macht ne Datenbank nicht wesentlich anders, nur dass hier B-Bäume zum Einsatz kommen - die den Vorteil haben dass man sie billiger updaten kann als sortierte Listen. Und gegenüber binären Bäumen den Vorteil haben dass sie "Disk-IO-freundlich" aufgebaut sind.

    Vorteil einer festen Satzlänge ist, ich kann die schneller lesen. Eine mit
    variablen Satzlängen ist da aufwendiger (Länge Record => Record lesen => alle
    Felder mit den Längen isolieren).

    Bei festen Satzlängen mache ich mir ein darauf passendes struct und lese den
    Kram mit einem read() hinein - fertig.

    Der sonst (bei einer mit flexibler Satzlänge) notwendige physical index
    ergibt sich einfach aus recno und sizeof( Struktur), muß also beim Einlesen
    auch nicht gepflegt werden.

    Die Sache mit einem binary tree habe ich mir auch überlegt, Problem ist
    nur: solange der nicht ausbalanciert ist bringt der wenig. Und das kann
    dauern.

    Bei größeren Indextabellen (in der Gegend mehrerer GB) kann ich das nicht
    mehr in einem Zug in den Speicher laden. Dann gehe ich mit meinem Suchbegriff
    in die Mitte, ist er größer oder kleiner ? Dann in dieser Richtung weiter.
    Auch recht flott. Sobald ich den ersten habe ist es nur noch satzweises
    Weiterlesen - easy.

    SQLite ist ein EWS-System, meins speziell für diese Anwendung und da (und
    auch nur da) natürlich wesentlich schneller ...

    Bei dem Dings was ich gerade baue komme ich auf max ~1 MB an Daten. Die
    lese ich mit einem Zug in den Speicher und filtere dann im Speicher.
    Halbwegs narrensicher (es kommen noch callbacks wegen Zugriffsrechten
    dazu) und recht flott.

    Flaschenhals ist die Ausgabe über den Webserver bzw das http. Da muß ich
    dann leider Kompromisse im HTML-Code machen um die klein zu halten.
    JSON muß ich mir da noch mal ansehen. Wenn nur der IE6 nicht wäre 😡



  • Sag ja zu NO-SQL 🙂



  • PRIEST schrieb:

    Sag ja zu NO-SQL 🙂

    SQL ist nicht das Maß der Dinge, klar ...

    Halt universell anwendbar, ideal für BWLer 😃



  • Scheppertreiber schrieb:

    Macht ne Datenbank nicht wesentlich anders, nur dass hier B-Bäume zum Einsatz kommen - die den Vorteil haben dass man sie billiger updaten kann als sortierte Listen. Und gegenüber binären Bäumen den Vorteil haben dass sie "Disk-IO-freundlich" aufgebaut sind.

    Vorteil einer festen Satzlänge ist, ich kann die schneller lesen. Eine mit
    variablen Satzlängen ist da aufwendiger (Länge Record => Record lesen => alle
    Felder mit den Längen isolieren).

    Bei festen Satzlängen mache ich mir ein darauf passendes struct und lese den
    Kram mit einem read() hinein - fertig.

    Der Overhead die Zeilen zu "parsen" geht normalerweise komplett in den IO-Kosten unter. Daher ... kein Vorteil bei DBs die nicht komplett ins RAM passen.

    Der sonst (bei einer mit flexibler Satzlänge) notwendige physical index
    ergibt sich einfach aus recno und sizeof( Struktur), muß also beim Einlesen
    auch nicht gepflegt werden.

    Das bringt dir nur was, wenn du die Record-Number bereits kennst.
    Wenn du Zeilen anhand des Inhalts suchst, bringt es dir dagegen nix. Weil du sowohl mit fixer Datensatz-Länge als auch mit variabler Länge einen Index brauchst. Ob dort dann die Record-Number oder die Page-Number drinnen steht, ist ziemlich egal.

    Die Sache mit einem binary tree habe ich mir auch überlegt, Problem ist
    nur: solange der nicht ausbalanciert ist bringt der wenig. Und das kann
    dauern.

    Bei größeren Indextabellen (in der Gegend mehrerer GB) kann ich das nicht
    mehr in einem Zug in den Speicher laden. Dann gehe ich mit meinem Suchbegriff
    in die Mitte, ist er größer oder kleiner ? Dann in dieser Richtung weiter.
    Auch recht flott. Sobald ich den ersten habe ist es nur noch satzweises
    Weiterlesen - easy.

    Wenn du immer grosse Teile des Tables "am Stück" lesen musst, dann wird das Vorteile haben.
    Wenn man immer nur einen oder ein paar Records braucht, und dann wieder wo anders hin springen muss, ist ein B-Baum (B-Tree != Binary Tree!) besser (schneller), weil man beim Lookup weniger IOs braucht als mit ner sortierten Liste.

    SQLite ist ein EWS-System, meins speziell für diese Anwendung und da (und auch nur da) natürlich wesentlich schneller ...

    Bei dem Dings was ich gerade baue komme ich auf max ~1 MB an Daten. Die
    lese ich mit einem Zug in den Speicher und filtere dann im Speicher.
    Halbwegs narrensicher (es kommen noch callbacks wegen Zugriffsrechten
    dazu) und recht flott.

    Ja, wenn du die Daten am Stück lesen kannst, dann bringt das natürlich enorme Vorteile. Eine "normale" Datenbank wird nie grosse Teile am Stück lesen können, sondern maximal eine Page. D.h. mit einer massgeschneiderten Lösung wirst du da immer schneller sein.

    @PRIEST
    Ich weiss nicht was eine "NO-SQL" Datenbank in diesem Fall für Vorteile haben sollte.



  • Das bringt dir nur was, wenn du die Record-Number bereits kennst.
    Wenn du Zeilen anhand des Inhalts suchst, bringt es dir dagegen nix. Weil du sowohl mit fixer Datensatz-Länge als auch mit variabler Länge einen Index brauchst. Ob dort dann die Record-Number oder die Page-Number drinnen steht, ist ziemlich egal.

    Nein. Ein "echtes" DBMS lädt eine Seite, sucht darin den Record und muß
    dann die einzelnen Felder suche und die Werte irgendwie laden (egal wie
    es den Record findet). Das dauert naturgemäß länger wie ein einzelnes
    lseek() mit nachfolgendem read() in eine Struktur wo die Felder ja bereits
    "fest" enthalten sind.

    In dieser struktur ist auch der Zeiger auf den Container mit dem PDF.

    Der Vorgang anhand einer Satznummer ist bei der Tabelle trivial, das DBMS
    muß sich durch den physical Index durchhangeln und die Recordnummer dort
    erstmal finden. Klar dauert das ...

    Die Suche nach dem Inhalt eines Feldes ist bei der Tabelle recht einfach.
    Die Tabelle sei nach A sortiert. Verfahren: Gehe in die Mitte (recmax /2)
    und vergleiche. Kleiner ? Ja -> in die Mitte der oberen Hälfte, nein->
    nach vorne. etcpp. Mit memcmp() direkt das Feld gegen den Suchbegriff testen
    ist trivial und schnell.

    Die Suche im Index eines DBMS funktioniert im Prinzip genauso, nur müssen
    komplette Pages geladen und deren Inhalt auseinandergebastelt werden. Die
    Laufzeit und den tree zu erstellen mal außen vor gelassen.

    Der Laufzeitunterschied ist heftig, schätze mal Faktor 100 bis 1000 (hab's
    noch nicht genau gemessen).



  • hustbaer schrieb:

    @PRIEST
    Ich weiss nicht was eine "NO-SQL" Datenbank in diesem Fall für Vorteile haben sollte.

    Ach ich bin ganz ehrlich, ich hab hier nicht alles gelesen :). Aber "Große Datenmenge" und dann hab ich noch etwas von JSON und "Blobb" gelesen. Ist mir sofort mongoDB (Mein derzeitiges Lieblingsspielzeug) in den Sinn gekommen.

    Es ist frei verfügbar, super skalierbar und extrem fix.

    Aber will hier keinen Glaubenskrieg anzetteln ob das eine oder andere jetzt die beste Wahl gewesen wäre 😉



  • Hmmmm ... eigentlich geht's ja eher um Performanceunterschiede zwischen
    einer EWS-Datenbank und einem speziell für genau einen Einsatzzweck konstruierten
    System. Und es gibt noch andere Welten wie SQL 😉



  • Was ist eine EWS-Datenbank?



  • EWS = Eierlegende-Wollmilch-Sau, so ein Mädchen für alles 😉



  • lach, alles klar - also auf die Wollmilch-Sau wär ich jetzt im leben nicht gekommen 😉

    Darf ich trotzdem mal fragen wieso du für ein Webprojekt sqlite benutzt?



  • PRIEST schrieb:

    lach, alles klar - also auf die Wollmilch-Sau wär ich jetzt im leben nicht gekommen 😉

    Muß nicht immer Denglish sein 😋

    PRIEST schrieb:

    Darf ich trotzdem mal fragen wieso du für ein Webprojekt sqlite benutzt?

    Tja, schwierig. Ich habe mal mit einem selbstgebastelten 8080 angefangen, dann
    C64, den nachgebaut, dann irgendwann einen "richtigen" PC, in einer Firma als
    Entwickler gelandet und C probiert. Hei war das auf einmal schnell gegenüber
    dem GWBASIC :p .

    Dann wilde Mischungen aus dBASE und C, dann irgendwann mit dBASE IV voll auf
    die Schnauze geflogen. Also nur noch C, in Verbndung mit Btrieve (genial).
    Dann Webanwendungen, irgendeine Datenbank braucht man dann mal für einfachere
    Anwendungen. Auf SQLite hat mich dieses Forum gebracht. Gut für's Web.

    Besser als die dauernde Patcherei wie mySQL etc.



  • Hm, interessant und die Webausgabe programmierst du dann in c?

    Finde es nur etwas unüblich 🤡

    Naja, wie dem auch sei 😃 - man schweift vom Thema ab ;)!



  • voll oldschool 🕶



  • Hm, interessant und die Webausgabe programmierst du dann in c?

    Klar. Ich hatte mich mal eine Woche in PHP eingewühlt und dann doch lieber
    das Original genommen. Ich finde, C ist für Webanwendungen besser geeignet
    wie Scriptsprachen. Schon der Compiler bringt eine Menge (so Fehler wie
    vergessen, eine Variable zu initialisieren, Tippfehler).

    Von PHP bis Ruby, Python kann jedes Scriptkiddie die Seite plattmachen -
    Lücken gibt es genug. Bei kompilierten Sachen dürfte das schwieriger sein.

    Dazu kommen absolut schrullige Benutzerrechte seitens der Kundschaft, wirre
    SAP-Aufrufe von außen und alles mögliche andere. Bei Benutzerrechten komme
    ich an Sachen wo es schon schwierig wird, das auf Datenbanken abzubilden.
    Teilweise muß ich die Scripts aus den Kundendaten generieren. Das kann PHP
    nicht mehr.

    Von der Technik her sind es scriptgesteuerte C-Programme (Watcom C), die
    HTML/CSS und JavaSscript dynamisch erzeugen.

    Es gibt auch Webstatistiken die behaupten (überprüfen kann ich das nicht),
    C sei die meistverwendete CGI-Sache. Keine Ahnung ob das wirklich stimmt,
    schnell, flexibel und einfacher wie zB PHP dürfte es sein.



  • Vorweg mal ein paar Fragen...
    Die Datenbank passt nichtmehr als ganzes ins RAM, oder? So hab ich das nämlich verstanden.
    Und du arbeitest vermutlich auch nicht mit SSDs, also Datenbank liegt auf drehenden Scheiben. Und auch nicht auf einem Array aus 100 solchen drehenden Scheiben, sondern irgendwas "normales" halt, also sagen wir bis max. 10 Platten in einem RAID 5 oder so.

    Wenn das stimmt, dann verstehe ich nämlich deine Argumentation nicht. Bzw. glaube dass du die falschen Gründe annimmst, warum deine massgeschneiderte Lösung schneller als die Eier-wollende Milchsau ist.

    Scheppertreiber schrieb:

    Das bringt dir nur was, wenn du die Record-Number bereits kennst.
    Wenn du Zeilen anhand des Inhalts suchst, bringt es dir dagegen nix. Weil du sowohl mit fixer Datensatz-Länge als auch mit variabler Länge einen Index brauchst. Ob dort dann die Record-Number oder die Page-Number drinnen steht, ist ziemlich egal.

    Nein. Ein "echtes" DBMS lädt eine Seite, sucht darin den Record und muß
    dann die einzelnen Felder suche und die Werte irgendwie laden (egal wie
    es den Record findet). Das dauert naturgemäß länger wie ein einzelnes
    lseek() mit nachfolgendem read() in eine Struktur wo die Felder ja bereits
    "fest" enthalten sind.

    Eine Seite laden oder 50 Byte laden spielt überhaupt keine Rolle. Die Daten direkt in eine struct Lesen oder erst durch die Page-Struktur durcharbeiten und die Daten der gesuchten Zeile rekonstruieren ist auch egal.
    Warum? Weil der Disk-Zugriff so langsam ist. Zwei Dinge:

    1. 50 Byte von der Platte lesen geht nicht schneller als 8 KB (=Page-Grösse von MSSQL) von der Platte lesen. Der Grossteil der Zeit wird gebraucht um die Spur anzufahren und zu warten bis der gesuchte Sektor unterm Lesekopf vorbeikommt. Ob man dann einen Sektor (typisch 512 Byte=Minimum was die Disk lesen kann) oder gleich 16 Sektoren liest ist so-gut-wie egal.

    2. Eine schnelle Platte schafft so an die 150 random IOs/Sekunde, 10 schnelle Platten in einem RAID Array also so 1500 IOs/Sekunde. Eine CPU macht in der Zeit ca. 2 Millionen Zyklen. Das sind 2 Millionen Zyklen in denen man die Records aus der Page raussuchen kann. Sollte sich leicht ausgehen.

    Das Lesen eines grösseren Stücks sowie das Zerklauben dieses Stücks in einzelne Datensätze spielt also - bei random IO - quasi keine Rolle.

    Die Suche nach dem Inhalt eines Feldes ist bei der Tabelle recht einfach.
    Die Tabelle sei nach A sortiert. Verfahren: Gehe in die Mitte (recmax /2)
    und vergleiche. Kleiner ? Ja -> in die Mitte der oberen Hälfte, nein->
    nach vorne. etcpp. Mit memcmp() direkt das Feld gegen den Suchbegriff testen
    ist trivial und schnell.

    Man nennt es binäre Suche 😉
    Braucht log_2(N) Schritte.
    Wenn wir Caching erstmal ignorieren, dann macht das log_2(N) physikalische random IOs.
    Sagen wir wir haben 130 Mio. Records, dann macht das etwa 27 IOs.

    Die Suche im Index eines DBMS funktioniert im Prinzip genauso, nur müssen
    komplette Pages geladen und deren Inhalt auseinandergebastelt werden. Die
    Laufzeit und den tree zu erstellen mal außen vor gelassen.

    Naja, funktioniert nicht ganz so.
    B-Baum eben. Also ein Baum mit ganz vielen Verzweigungen pro Ebene, d.h. ganz wenig Ebenen.
    Üblich ist eine Page bis zum Anschlag anzufüllen, so viel wie da reingeht so breit fächert der Baum.
    Nehmen wir mal ne Hausnummer an, sagen wir eine Zeile im Index braucht 256 Byte - was schon einigermassen sehr viel wäre.
    Dann passen in eine Page 8192/256 = 32 Zeilen.
    Dann braucht die Suche log_32(N) Schritte = log_32(N) random IOs.
    Bei unseren 130 Mio. Records wären das aufgerundet 6 IOs.

    So, nun zum Caching.
    Wir können davon ausgehen dass die am häufigsten gebrauchten Seiten im Cache stehen, und daher extrem billig zu "lesen" sind. Beim B-Baum interessiert eigentlich nur wie viele komplette Levels in den Cache passen. Bei der binären suche ist es im Prinzip das selbe, nur dass hier die "Levels" verstreut im Datenfile liegen - der mittlere Datensatz ist Level 1, die Datensätze an Position 1/4 und 3/4 bilden Level 2 usw.

    Unterschied 1: bei der binären Suche wächst die grösse eines Levels immer mit Faktor 2, beim B-Baum mit Faktor 32 (oder mehr).

    Unterschied 2: beim B-Baum wird die ganze Cacheseite genutzt, da innerhalb einer Seite immer Daten des gleichen Levels liegen. Bei der binären Suche, und vorausgesetzt man bekommt nicht die ganze Tabelle in den Speicher, verwendet man immer nur einen (!) Datensatz pro Cache-Seite.

    Effekt: beim B-Baum wirst du max. 2 (in Extremfällen vielleicht 3 oder 4) Levels haben die nicht aus dem Cache bedient werden können. Bei der binären Suche viel mehr, die Anzahl der Levels die du im Cache halten kannst ist log_2(sizeof(Cache)/sizeof(Cache-Page)). Sagen wir wir haben 4 GB Cache und eine Cacheseite ist 4 KB gross, dann ergibt das ca. 20 Levels. 27-20 ist 7, und das ist mehr als 2 🙂

    Also 7 physical IOs vs. 2 zum Auffinden eines Datensatzes.

    Der B-Baum müsste also wesentlich flotter sein.

    Was nun mit der schweinischen Milchwolle leider nicht mehr so gut funktioniert, ist das Lesen von vielen Datensätzen ab dieser Position. Weil eine solche Wollmilchdatenbank die Datensätze eben nicht in grossen Stücken sortiert auf der Platte ablegt.
    Und genau das wird vermutlich der Punkt sein, wo deine Speziallösung punktet. Weil die Daten eben sortiert sind, und das Lesen der folgenden Datensätze keine random IOs mehr sind, sondern hier linear gelesen wird.

    Der Laufzeitunterschied ist heftig, schätze mal Faktor 100 bis 1000 (hab's noch nicht genau gemessen).

    Dann miss es mal. Und vergleiche auch mal die Performance wenn du aus beiden Datenbanken jeweils nur einen Datensatz rausholst. Dann müsste die Datenwolle gut aufholen, wenn sie deine Speziallösung nicht sogar überholt.

    Wobei es auch relativ einfach sein sollte einen B-Baum-artigen Index mit deinem sortierten Datenfile zu verbinden.
    Die Lookups der ersten Zeile kannst du dann über den Index machen, und das lineare Lesen bleibt wie es ist.



  • Moin Hustbaer,

    Dann miss es mal.

    Ich hatte das nur sehr locker gemssen. Suchzeit bei meinem Systen (jeweils 3 Fundstellen) 0.14 sec,
    bei der gleichen Datenmenge mit SQLIte hatte der Apache nach einigen Minuten etwas von einem 500 gemunkelt ...
    (Feld-Wald+Wiesen-PC, Win XP prof, 4 GB RAM, Dualcore, RAID1, Apache 2).

    Interessante Ideen. Eigentlich logisch - ich muß das jetzt nur noch irgendwie mit der Praxis zusammenbringen.
    Nach Deinen Ausführungen dürfte SQLite aber nicht dermaßen dramatisch einknicken.

    Eigentlich sollten die Sektoren mit den SQLite-Pages auch hintereinanderliegen, die Datenbank wurde ja neu
    aufgebaut. Inwieweit da Ergebnisse des Caching eingehen weiß ich nicht. Nur, je weniger Daten umso mehr
    dürfte auch in den Cache passen. Die Chance, daß eine Datei mit 1 MB komplett im Cache liegt ist naturgemäß
    höher wie bei einer großen Datenbank.

    In der Firma läuft das auf schnellen PCs mit 10 Platten RAIDs, wir haben da auch ein 10 MBit Anbindung.

    Bei unseren 130 Mio. Records wären das aufgerundet 6 IOs.
    

    6 oder 27 I/Os dürften sich nicht so zwicken ...

    Bei dem diskutierten System (es funktioniert da leicht anders), kenne ich die Datenmengen der einzelnen
    vorkommenden Suchbegriffe - die habe ich mal in einzelne Dateien abgelegt (die größte wird 24 MB pro Jahr).
    Die kann ich komplett in den Speicher laden, durchsuchen und habe dann sofort die Trefferliste. Von da
    hole ich dann die Bestandsdaten.

    Wobei es auch relativ einfach sein sollte einen B-Baum-artigen Index mit deinem sortierten Datenfile zu verbinden.

    Beim Herumprobieren mit SQLite habe ich alle Daten (nicht nur die Suchbegriffe) in einer DB. NUR die
    Suchbegriffe dort speichern müßte ich nochmal probieren ...
    ("Bestandsdaten" bedeutet hier: Den Rest der Angaben aus den Rechnungszeilen, nicht die PDF/XML).

    Die Datenfiles sind "in order of einlesing", also unsortiert.

    --

    Suchbegriffe sind hier Kundennummer, Belegnummer, Artikelnummer (alle mit festen Längen).
    Eingrenzungen durch Datum und Niederlassung sind möglich.
    Bestandsdaten: Angaben wie Preise, Menge etc.


Anmelden zum Antworten