Many To Many Fields

Django, Flask, Bottle, WSGI, CGI…
Antworten
Freumel
User
Beiträge: 69
Registriert: Donnerstag 25. Januar 2018, 13:47

Hallo - ich wieder :geek:

Habe mal eine Frage zur Datenbankstruktur.

Mein Setup:
Django + PostgreSQL

Soweit, so gut.
Habe einen großen Datendurchlauf und versuche nun zu optimieren.
Unser Server muss pro Minute einige Datensätze aktualisieren.

Jetzt sind verschiedene Models untereinander verbunden. Und ich habe so das Gefühl einen üblen und teuren Fehler zu machen:
Im Grundeintrag (Model 1) habe ich ein Many To Many Field.
Dabei bediene ich mich aus den Daten einer anderen App (Model 2).
In diesem Datensatz ist ein weiteres Many To Many Field - innerhalb der gleichen App (Model 3).

Als dict würde ich das so darstellen:

Code: Alles auswählen

allData = [
	{Model 1 : [{Model 2 : [Model 3, Model 3, Model 3, .... ]}, {Model 2 : [Model 3, Model 3, Model 3, .... ]}, {Model 2 : [Model 3, Model 3, Model 3, .... ]}, ... ]},
	{Model 1 : [{Model 2 : [Model 3, Model 3, Model 3, .... ]}, {Model 2 : [Model 3, Model 3, Model 3, .... ]}, {Model 2 : [Model 3, Model 3, Model 3, .... ]}, ... ]},
	{Model 1 : [{Model 2 : [Model 3, Model 3, Model 3, .... ]}, {Model 2 : [Model 3, Model 3, Model 3, .... ]}, {Model 2 : [Model 3, Model 3, Model 3, .... ]}, ... ]},
	...
]
Model 1 wird einmalig angelegt und anschließend dauerhaft aktualisiert.
Auf den Eintrag kommen etwa 5 Einträge aus Model 2. Und auf jeden Eintrag von Model 2 kommen bei der Erstellung jeweils 1 Eintrag.
Das bedeutet: Sobald Model 1 eingetragen wird, werden insgesamt im Schnitt 11 Einträge quer durch die Datenbank gemacht.

Die Aktualisierung bedeutet, dass geschaut wird, ob ein weiteres Model 2 angelegt wurde. Ist dem so, so kommt ein weiterer Eintrag aus Model 3 hinzu.
Zusätzlich bekommen alle Verknüpfungen von Model 2 pro Aktualisierung jeweils ein Update.
Also entstehen pro Aktualisierung mindestens 5 neue Einträge in der Datenbank.

Der Datensatz für sich soll alle 2 Minuten aktualisiert werden.
Zusätzlich kommen immer wieder neue hinzu - es sollen rund 1000 Datensätze aktuell gehalten werden.
Auf 2 Minuten kommen also 5000 neue Einträge - pro Minute 2500.

Nun stelle ich die Struktur der Many To Many Feldern in Frage.
Wenn ich ein Objekt im Adminbereich bearbeiten möchte ist die Ladezeit ziemlich lange.
Auch ist der Server bereits zu 33% bei weitaus weniger Datensätzen ausgelastet. Bevor ich die Leistung skaliere, würde ich die Datenbank gerne optimieren.

Die Zuweisung der Daten ist von vorn herein klar und nicht willkürlich.
Ich überlege für Model 2 eine "for_Model_1" ID zu erstellen und das Many To Many Field an der Stelle aufzulösen.
Ähnliches mit Model 3. So kann ich die Einträge im Admin Bereich viel schneller erstellen. Die Filterarbeit beim Abfragen der Daten ist am Ende dann von den Codezeilen her gefühlt aufwändiger. Allerdings wird wesentlich (!) öfter aktualisiert als die Daten manuell abzufragen.
Würde das Performancetechnisch Sinn machen?

Ist mein erstes Projekt dieser Art ... am besten ist es wenn es die Datenbank ein paar Monate macht :lol:

Ich hoffe man konnte halbwegs folgen.
Sirius3
User
Beiträge: 17738
Registriert: Sonntag 21. Oktober 2012, 17:20

Als erstes würde man wohl analysieren, wie lange welche Operation auf der Datenbank braucht und ob man alle nötigen Indizes dafür erzeugt hat.
Dann könnte man schauen, ob die Datenbankstruktur wirklich so geschickt für die Aufgabe ist. Ohne da aber den genauen Anwendungsfall zu kennen, kann man nichts sagen.
Was soll denn da aktualisiert werden und warum hat das Einfluß auf die Many-to-many-Verknüpfungen?

Wahrscheinlich ist die Tabellenstruktur nicht richtig, wahrscheinlich ist das Konzept der Aktualisierung hier falsch und möglicherweise sind relationale Datenbanken der falsche Typ. Entweder Du probierst selber rum, bis die eine für Dich befriedigende Lösung gefunden hast, oder Du wirst hier konkreter.
Benutzeravatar
noisefloor
User
Beiträge: 3854
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,

mal unabhängig von der Optimierung:

2500 Datensätze pro Minute sind 150.000 Datensätze pro Stunde sind 3,6 Mio Datensätze pro Tag sind 1,31 Mrd Datensätze pro Jahr.
Da bist du IMHO relativ schnell an dem Punkt, wo die Datenbank auf mehrer Server verteilen solltest bzw., wie Siruis3 schon sagte, geprüft werden sollte, ob ein RDBMS noch die richtige Wahl ist.

Die Beschreibung finde ich auch extrem abstrakt und für aussenstehend (wie uns) ziemlich schlecht nachvollziehbar.

Gruß, noisefloor
DasIch
User
Beiträge: 2718
Registriert: Montag 19. Mai 2008, 04:21
Wohnort: Berlin

Die Datenmenge bekommt man problemlos mit einem RDBMS bewältigt, setzt nur voraus dass man entsprechende Hardware hat und das RDBMS richtig bedienen kann. Mangelt es bei letzterem hilft einem eine andere Datenbank auch nicht. Im Gegenteil NoSQL kommt i.d.R. mit Tradeoffs die es einem sehr leicht machen in noch viel größere Probleme zu rutschen falls man die Datenbank und deren Tradeoffs nicht versteht.

Wichtig ist hier zu verstehen wie auf die Daten zugegriffen wird, wie wird geschrieben? Ist es timeseries data die sich gut partitionieren lässt? Gibt es Spielraum zu denormalisieren? Kannst du Daten nach einer Weile löschen oder in "cold storage" schieben (S3 vllt. Athena nutzen falls ab und zu Queries erwünscht sind). Ist es möglich die Daten redundant zu halten, z.B. aggregiert für schnellen Zugriff?
Freumel
User
Beiträge: 69
Registriert: Donnerstag 25. Januar 2018, 13:47

noisefloor hat geschrieben: Mittwoch 28. August 2019, 09:37 [...]
2500 Datensätze pro Minute sind 150.000 Datensätze pro Stunde sind 3,6 Mio Datensätze pro Tag sind 1,31 Mrd Datensätze pro Jahr.
Da bist du IMHO relativ schnell an dem Punkt, wo die Datenbank auf mehrer Server verteilen solltest bzw., wie Siruis3 schon sagte, geprüft werden sollte, ob ein RDBMS noch die richtige Wahl ist.
[...]
Die Datenbank räumt sich selber auf.
Die meisten Daten sind 7 Tage lang interessant. Dann wird der Datensatz auf eine Backup-Datenbank geschoben. 3,6 Mio Datensätze pro Tag sind schonmal ein Wort. Es wird maximal an die 4 Mio reichen und diesen Gesamtwert mit Sicherheit niemals überschreiten.
Ich möchte die Datenbank natürlich so "klein" wie möglich halten.

Im Prinzip geht es darum bestimmte Kurse zu beobachten, ähnlich wie Aktienkurse (der Anwendungsfall weicht arg von dem Beispiel ab, aber kommt in der Funktionalität dran)
Es existiert eine Firma und verschiedene Börsen, anschließend noch ein Kurs, dieser teilt sich allerdings in mehrere Floats auf und bekommt zu jedem analysierten Zeitpunkt einen Eintrag in Model 3.

Code: Alles auswählen

class Courses(models.Model):
	value_1 = models.FloatField()
	value_2 = models.FloatField()
	value_3 = models.FloatField()
	date = models.DateTimeField()

class Boerse(models.Model):
	ID = models.IntegerField(primary_key = True)
	name = models.CharField(max_length = 50)
	courses = models.ManyToManyField(Courses)

class Firma(models.Model):
	ID = models.CharField(max_length = 10, primary_key = True)
	name = models.CharField(max_length = 50)
	date = models.DateTimeField()
	boersen = models.ManyToManyField(Boerse)
Wie gesagt - der Anwendungsfall ist etwas abstrakter, daher nehme ich davon Abstand. Aber der Aufbau der Datenbank folgt im wesentlichen der angegebenen Struktur.
Gefühlt würde ich folgende Umsetzung bevorzugen:

Code: Alles auswählen

class Firma(models.Model):
	ID = models.CharField(max_length = 10, primary_key = True)
	name = models.CharField(max_length = 50)
	date = models.DateTimeField()

class Boerse(models.Model):
	ID = models.IntegerField(primary_key = True)
	name = models.CharField(max_length = 50)

class Courses(models.Model):
	value_1 = models.FloatField()
	value_2 = models.FloatField()
	value_3 = models.FloatField()
	date = models.DateTimeField()

	boerse = ForeignKey(Boerse)
	firma = ForeignKey(Firma)

	class Meta:
		unique_together = [("firma", "boerse", "date"),]
Die Frage ist, ob beides Mist ist - oder eines in die richtige Richtung geht.
Das ist mein erstes Projekt in Richtung Datenbanken überhaupt...
Benutzeravatar
__blackjack__
User
Beiträge: 13071
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Freumel: Also mit dieser Bedeutung sehe ich da keine ManyToMany-Beziehungen. Denn ein `Courses`-Exemplar gehört ja nicht zu gleichzeitig zu mehreren Firmen und/oder Börsen.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Freumel
User
Beiträge: 69
Registriert: Donnerstag 25. Januar 2018, 13:47

__blackjack__ hat geschrieben: Mittwoch 28. August 2019, 17:08 @Freumel: Also mit dieser Bedeutung sehe ich da keine ManyToMany-Beziehungen. Denn ein `Courses`-Exemplar gehört ja nicht zu gleichzeitig zu mehreren Firmen und/oder Börsen.
Genau.
1) Ein Kurs wird einer Börse zugeordnet.
2) Eine Börse hat über die Zeit viele Kursschwankungen.

Ich habe eher von 2) ausgehend gedacht.
Benutzeravatar
__blackjack__
User
Beiträge: 13071
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

In welche Richtung man eine Beziehung bei relationalen Datenbanken betrachtet, macht keinen Unterschied. Die sind ungerichtet. Wenn ein Kurs nur einer Börse zugeordnet wird, dann ist das keine „many to many“-Beziehung.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Sirius3
User
Beiträge: 17738
Registriert: Sonntag 21. Oktober 2012, 17:20

Firma.ID sollte auch eine Zahl sein. Und wie sehen nun die Operationen aus, die Du auf dem Datenbankmodell ausführen willst?
Freumel
User
Beiträge: 69
Registriert: Donnerstag 25. Januar 2018, 13:47

Sirius3 hat geschrieben: Mittwoch 28. August 2019, 17:43 Firma.ID sollte auch eine Zahl sein. Und wie sehen nun die Operationen aus, die Du auf dem Datenbankmodell ausführen willst?
Warum eine Zahl?
Mit Groß- und Kleinbuchstaben inklusive Zahlen habe ich viel mehr Möglichkeiten eine ID darzustellen. Während die Zahl immer weiter wächst komme ich mit einer Länge von 5 Zeichen wesentlich weiter, verbrauche also weniger Speicherplatz.
Und bei Django kann ich einem Int Feld nicht so einfach eine maximale Größe zuweisen.
Oder sehe ich in der Denkweise was falsch?
__deets__
User
Beiträge: 14523
Registriert: Mittwoch 14. Oktober 2015, 14:29

das Argument ist Unfug. Nur weil du als ASCII Darstellung vermeintlich mehr Zeichen brauchst, hat die Datenbank natürlich eine kompakte binäre Darstellung, von wahrscheinlich mindesten 64 Bit. Also 8 Byte. Hast du wirklich mehr als 2^64 Firmen? Hat jede davon eine individuelle Adresse auf einem Quadratzentimeter der Erdoberfläche? Und deine Wahl reduziert trotz mehr Byte die Anzahl der nutzbaren Zeichen wenn du nur Buchstaben & zahlen benutz: 2**64 > 36*10

Und die Zeiten, wo man an einzelnen Zeichen in der DB rumoptimiert hat sind seit Jahrzehnten vorbei. Wenn du ein Problem hast, wo es auf ein paar Bytes ankommt, hast du ein Problem bei dem du auf ganze Farmen von Rechnern skalieren musst.
Sirius3
User
Beiträge: 17738
Registriert: Sonntag 21. Oktober 2012, 17:20

@Freumel: IDs in Datenbanken sind üblicherweise Zahlen, weil sich mit Zahlen einfacher und schneller arbeiten läßt. Um die Vergabe kümmert sich auch die Datenbank, so dass Du Dir hier auch keine Gedanken machen mußt.
Freumel
User
Beiträge: 69
Registriert: Donnerstag 25. Januar 2018, 13:47

Kurz eine andere Frage - ohne direkt einen neuen Beitrag zu öffnen:

Code: Alles auswählen

objs = Obj.objects.all()
objs = objs.filter(id__in = [obj.id for obj in objs if abs(obj.a - obj.b) > 2])
Dieser "Filter" ist extrem teuer.... geht das vielleicht eleganter?
Sirius3
User
Beiträge: 17738
Registriert: Sonntag 21. Oktober 2012, 17:20

Wenn Du die Objekte schon mit all abgefragt hast, warum schickst Du dann nochmal eine Query hinterher? Das wäre eine ganz normale Filterung einer Python-Liste.

Besser ist es, die Berechnung von der Datenbank machen zu lassen.
Arithmetische Operationen kann man mit `F`-Referenzen abbilden, ob das aber mit `abs` funktioniert weiß ich nicht:
https://docs.djangoproject.com/en/1.11/ ... -the-model

Bliebe als letzter Ausweg raw-SQL: https://docs.djangoproject.com/en/2.2/topics/db/sql/

SQL-Alchemy bietet da deutlich komfortablere Methoden; bisher habe ich noch nicht gesehen, warum Du die Datenbankanbindung von Django brauchst.
Antworten