mmofacts.com

Wie weit normalisieren und Redundanz sinnvoll?

gepostet vor 14 Jahre von NeoArmageddon

Aloha,

ich habe schon zwei Themen hier im Forum entdeckt, die sich um das Thema drehen, allerdings beantwortet keiner davon die Fragen, die sich mir im Laufe meiner Entwicklungsarbeit aufgetan haben zufriedenstellend und ich denke, die Fragen stellen sich nicht nur mir.

Okay, kommen wir mal zum ersten Punkt, bei dem sich meine Sorgen hauptsächlich um die Performance drehen:

Als Beispiel nehme ich mal eine Spielfigur die eine Waffe tragen kann. Wenn ich mich an alle NF halte, sollte ich mir 3 Tabellen basteln. Eine mit der Figur und eine mit den Eigenschaften der Waffe. Dann eine, die sowohl auf den Spieler und die Waffeneigenschaften verweist und eventuell noch Werte wie Haltbarkeit/Abnutzung enthält.

Wenn ich nun will, dass meine Figur eine andere Figur haut, brauche ich im besten Fall 3 Abfragen (die ich natürlich verknüpfen kann, aber dass lassen wir erstmal außen vor). Ich brauche die Figur an sich, dann die Waffe, die auf den Schlüssel der Figur zeigt (quasi gerade getragen wird) und ich brauche die Werte der Waffe, die in einer anderen Spalte stehen.

Wie sinnvoll wäre es in einem solchen Fall, auf die Normalisierung zu pfeifen und bzw die Schadenswerte der Waffe redundant in der Charaktertabelle zu speichern? Es reicht ja teoretisch vollkommen aus, beim Anlegen oder Ablegen der Waffe, den Schaden den der Charakter macht, in seiner Tabelle zu updaten. Es besteht natürlich die Gefahr, das bei Anpassungen, der Spieler auf einmal weniger oder mehr Schaden macht, als er sollte, aber natürlich kann man dafür fix ne Funktion basteln, die kurz die Relationen zwischen Spielerschaden, Waffe und Waffentyp checkt und Fehler ausbügelt. Den Vorteil sehe ich dann jedoch darin, dass bei bzw großen Kämpfen wo mehrere Charaktere Kämpfen, nicht bei jedem Schlag 3 Tabellen abgefragt werden müssen, sondern nur 1 pro Charakter, und das nur am Anfang des Kampfes. Man könnte so im besten Fall 2/3 der Querys einsparen.

Hat das einen Sinn? Übersehe ich was? Hat jemand Erfahrung damit, ob diese Einsparung was bringt oder letztlich in der Auslastung des Servers kaum eine Rolle spielt?

Nächste Frage zu dem Thema: Angenommen ich habe in meinem Spiel eine beliebige Anzahl an Ressourcen. BZW wenn ich nur welche für den Handel benötige ... (also für den Spieler Sinnlosen Rohstoff bei A kaufen und mit Profit bei B verkaufen).

Nun habe ich mir im groben 2 Systeme überlegt, wie man damit am besten verfahren könnte:

1.) Wäre eine der NF ensprechende: Es gibt 2 Tabellen für Rohstoffe. Einmal den Rohstofftyp mit Infos wie Name, Standartpreis, Gewicht etc und eine Tabelle mit "Rostoffpaketen". Also eine verlinkung auf einen Rohstofftyp, eine auf einen Spieler und ein Eintrag für die Menge. Bei Abfragen nach Rohstoffen werden nun halt nach Spalten mit der richtigen Spielerid und Rohstofftyp gesucht und es wird die Menge gecheckt.

2.) Möglichkeit: Ich spare mir die Pakettabelle und benutze in der Tabelle des Spielers einen formatierten String, der Infos zu den Rohstoffen im besitzt enthält:

Beispiel: 1:50;3:23;8:2

Bedeutet: Spieler hat vom Rohstofftyp 1. 50 Einheiten, vom 3. 23 und vom 8 genau 2. Den Rohstofftyp könnte man hier der Einfachhalt halber direkt als Namen nehmen oder aber auch als Schlüssel für die Oben vorgestellte Rohstofftypentabelle. Mit explode() würde sich so ein String wunderbar zerlegen und bearbeiten lassen. Hier könnte man wieder Querys sparen, jedoch weiß ich nicht genau wie es mit der Performance von explode und implode bei PHP (oder anderen Sprachen) aussieht. Allerdings könnte man auch so last von der Datenbank auf den CPU verlagern, soltle dieser sich zu sehr langweilen^^.

Grundfrage ist bei beiden halt: In wie weit lohnt sich diese Verringerung der Querys? Spielt es in diesem kleinen Bereich überhaupt eine Rolle, ob ich normalisiere oder nicht?

So, genug zu meinen Fragen und Überlegungen. Ich denke für das meiste gibt es eh 100 mal bessere Lösungen.

gepostet vor 14 Jahre von knalli

Original von NeoArmageddon

Als Beispiel nehme ich mal eine Spielfigur die eine Waffe tragen kann. Wenn ich mich an alle NF halte, sollte ich mir 3 Tabellen basteln. Eine mit der Figur und eine mit den Eigenschaften der Waffe. Dann eine, die sowohl auf den Spieler und die Waffeneigenschaften verweist und eventuell noch Werte wie Haltbarkeit/Abnutzung enthält.

Wenn ich nun will, dass meine Figur eine andere Figur haut, brauche ich im besten Fall 3 Abfragen (die ich natürlich verknüpfen kann, aber dass lassen wir erstmal außen vor). Ich brauche die Figur an sich, dann die Waffe, die auf den Schlüssel der Figur zeigt (quasi gerade getragen wird) und ich brauche die Werte der Waffe, die in einer anderen Spalte stehen.

Wenn deine Anwendung weiß, dass es nur eine aktive Waffe gibt, kannst du das auch in einer Abfrage machen, Stichwort Joins. Und das kann man auch als View ablegen.

Wie sinnvoll wäre es in einem solchen Fall, auf die Normalisierung zu pfeifen und bzw die Schadenswerte der Waffe redundant in der Charaktertabelle zu speichern? Es reicht ja teoretisch vollkommen aus, beim Anlegen oder Ablegen der Waffe, den Schaden den der Charakter macht, in seiner Tabelle zu updaten. Es besteht natürlich die Gefahr, das bei Anpassungen, der Spieler auf einmal weniger oder mehr Schaden macht, als er sollte, aber natürlich kann man dafür fix ne Funktion basteln, die kurz die Relationen zwischen Spielerschaden, Waffe und Waffentyp checkt und Fehler ausbügelt. Den Vorteil sehe ich dann jedoch darin, dass bei bzw großen Kämpfen wo mehrere Charaktere Kämpfen, nicht bei jedem Schlag 3 Tabellen abgefragt werden müssen, sondern nur 1 pro Charakter, und das nur am Anfang des Kampfes. Man könnte so im besten Fall 2/3 der Querys einsparen.

Rein aus Sicht der NF ist das nicht sinnvoll; aber natürlich kann es sein, dass bei stark frequentierten Tabellen Performanceprobleme(!) auftreten können. Allerdings würde ich da mal stark einen Cache empfehlen, das kann entweder eine materialisierte View sein, oder ein entsprechendes Caching in der Zwischenlogik, etwa im OR-Layer oder sonstiges, wie memcache.

Performance und NF gehen nicht unbedingt einher; genauso gut ist ein ASM-Programm auch effektiver als jedes C-Programm. Das war es aber auch mit den Vorteilen..

Beispiel: 1:50;3:23;8:2

Bedeutet: Spieler hat vom Rohstofftyp 1. 50 Einheiten, vom 3. 23 und vom 8 genau 2. Den Rohstofftyp könnte man hier der Einfachhalt halber direkt als Namen nehmen oder aber auch als Schlüssel für die Oben vorgestellte Rohstofftypentabelle. Mit explode() würde sich so ein String wunderbar zerlegen und bearbeiten lassen. Hier könnte man wieder Querys sparen, jedoch weiß ich nicht genau wie es mit der Performance von explode und implode bei PHP (oder anderen Sprachen) aussieht. Allerdings könnte man auch so last von der Datenbank auf den CPU verlagern, soltle dieser sich zu sehr langweilen^^.

Klar verringert das irgendwo der Aufwand - für die Datenbank; dennoch: die erste NF sollte man nicht brechen. Echt nicht. 1. NF und 2. NF sollten im Tabellendesign eigentlich immer drin sein, bei der 3. kann man sich uU streiten.

gepostet vor 14 Jahre von RaydenDD

Also zum ersten Teil sag ich dir wie ich es gelöst hab, bei mir ist alles schön getrennt. Da ein Kampf jetzt allerdings nicht gerade jede Sekunde stattfindet, lade ich mir vor dem Kampf alle benötigten Werte aus den verschiedenen Tabellen heraus verknüpfe diese und speichere sie für die gesamte Kampfdauer (und nicht nur immer für einen Schuss :) ). Das Verknüpfen der Werte wird ja schnell durchgeführt und ist auch nur einmal nötig im Kampf.

Da ich allerdings nicht genau weiss, wie dein Kampf im Detail abläuft, musst du wissen ob diese Methode bei dir funktionieren würde. Die Datenbank würde ich jedenfalls so sauber wie möglich halten, weil bei entsprechenden Änderungen ist es immer von Vorteil, wenn man nicht immer neue Tabellenfelder hinzufügen muss, sondern nur neue Einträge einpflegen, die dann automatisch richtig zugeordnet werden können.

gepostet vor 14 Jahre von Kampfhoernchen

1. Normalform: Auf jeden!

2. Normalform: Auf jeden!

3. NF: Gibt sinnvolle Anwendungen dagegen zu verstoßen. Ich würds aber über Materialisierte Views lösen.

B-C-NF: Naja, is irgendwie Unsinnig find ich und bläst das ganze bis zur Unkenntlichkeit auf.

4. NF: Bin noch nie auf die Idee gekommen dagegen zu verletzten

5. NF: Versteh ich nicht, wahrscheinlich mach ichs implizit richtig.

gepostet vor 14 Jahre von knalli

Auch wieder wahr, so kann man es auch formulieren ;)

gepostet vor 14 Jahre von NeoArmageddon

Wenn deine Anwendung weiß, dass es nur eine aktive Waffe gibt, kannst du das auch in einer Abfrage machen, Stichwort Joins. Und das kann man auch als View ablegen.

Es gibt ja nicht unebdingt nur eine Waffe... man kann sicher auch noch 2 Händig 2 Waffen tragen, nen Bogen, Rüstungsteile... da ist man fix bei 15 verschiedenen Items die der Charakter trägt. 

Hab ich vllt vergessen zu erwähnen... hab das Beispiel halt einfach gehalten... dabei springen einen ja Joins richtig an... aber bei 15 versch. items wird der Query auch mit JOINS etwas lang, zumindest für meinen geschmack.

Gibt es von euch vielleicht Erfahrungen, ab wann die ersten Performanceprobleme auftreten, wenn man komplett auf 1 und 2. NF setzt? Also vorallem bei solchen Sachen wie Charakterverwaltung mit Items... oder meinet wegen auch Raumschiffe mit Upgrades und anbauten? 

4. NF: Bin noch nie auf die Idee gekommen dagegen zu verletzten

5. NF: Versteh ich nicht, wahrscheinlich mach ichs implizit richtig.

4  habe ich in der Tat bis jetzt auch intuitiv vermieden. Und ich denke 5 wird in unserem Bereich nie wichtig werden, wenn ich das richtig verstanden habe. Solange man Abhängige Tabellen gegeneinander auflösen kann, sind zusätzliche Tabellen für ALLE Abhängikeiten über andere Tabellen hinweg ja total überflüssig.

gepostet vor 14 Jahre von MrMaxx

Das was Kampfhörnchen da schreibt macht 100% Sinn...du rennst einfach unweigerlich in Probleme, wenn du anfängst zu viele Daten redundant in deinem Datenmodell zu halten.

Wenn du dir wirklich Sorgen machst, über die Performance deiner Anwendung, dann solltest du vor allem deine Datenbankperformance ansehen, denn da geht der Hauptteil der Rechenzeit hin.

Welche Querys werden häufig ausgeführt ?...überprüfbar durch Logging...

Welche Querys dauern besonders lange (und werden diese eventuell häufig ausgeführt) ?...überprüfbar durch slow-Query-Log und generellem Logging...

Identifizier diese Kandidaten...und nur bei diesen lohnt es sich überhaupt zu optimieren. Und die einfachste Möglichkeit dies zu tun ist es bestimmte Daten zu Cachen.

Ein guter Caching-Kanditat ist bestimmt deine Items-Tabelle, denn davon wird es nicht mehr als ein paar Hundert/Tausend geben. Gute Caching-Kandidaten sind leicht dadurch zu identifizieren, dass:

 - es von ihnen nicht sonderlich viele gibt

- sie häufig gebraucht werden

- sie wenig bis garnicht verändert werden

Dabei würde ich gerne einen schönen Satz aus einem Buch über Performaceoptimierung zitieren:

Rules of Optimization:
Rule 1: Don't do it.
Rule 2 (for experts only): Don't do it yet.

- M.A. Jackson

Mr.Maxx

gepostet vor 14 Jahre von Phoscur

Ich hab noch einen Satz zum zitieren:

Permature Optimization is the root of all evil.

Schreib dein Spiel erstmal ganz hübsch und nach Best-Practice. Wenn du dann am Ende nicht auf die Performance kommst, die du gerne hättest, kannst du beginnen zu optimieren. Dann kannst du auch profilen und wirst mit hoher Wahrscheinlichkeit feststellen, dass die meiste Zeit an ganz anderen Stellen drauf geht. Optimiert werden sollten eh nur die 10% Code die 90% der Arbeit tun.

gepostet vor 14 Jahre von NeoArmageddon

Original von MrMaxx

 - es von ihnen nicht sonderlich viele gibt

- sie häufig gebraucht werden

- sie wenig bis garnicht verändert werden

Dabei würde ich gerne einen schönen Satz aus einem Buch über Performaceoptimierung zitieren:

Rules of Optimization:
Rule 1: Don't do it.
Rule 2 (for experts only): Don't do it yet.

- M.A. Jackson

Hab mir gestern mal Zeit für Memcache genommen und dabei entdeckt, dass es recht simpel ist, das im Nachhinein in eine Datenbankklasse mit einzubauen, wenn ich eh meine Querys über ne eigene Klasse absende.

Von daher habt ihr natürlich vollkommen recht... 

Habt schonmal vielen Dank für eure guten Tips und Ratschläge.

gepostet vor 14 Jahre von knalli

Hab mir gestern mal Zeit für Memcache genommen und dabei entdeckt, dass es recht simpel ist, das im Nachhinein in eine Datenbankklasse mit einzubauen, wenn ich eh meine Querys über ne eigene Klasse absende.

Von daher habt ihr natürlich vollkommen recht... 

Habt schonmal vielen Dank für eure guten Tips und Ratschläge.

Wenn du das DB-Handling komplett selbst übernimmst, musst du das unweigerlich tun. Verwendest du ein Framework, würde ich dir empfehlen, zu erst nach *fertigen* Cache-Plugins zu suchen. 

gepostet vor 14 Jahre von NeoArmageddon

Muss sagen, dass ich nicht so der Framework-Fan bin. Ich sehe natürlich den Vorteil den man hat, wenn man Produktiv arbeiten will, aber da ich das alles eh nur als Hobby und aus Weiterbildungsgründen mache, ist es mir immer liever sowas selbst zu machen, auch wenns mehr Arbeit bedeutet...

gepostet vor 14 Jahre von RaydenDD

Darf ich mal Fragen in welcher Sprache du das Zeug schreibst?

gepostet vor 14 Jahre von NeoArmageddon

PHP aufm Server. MySQL als DB und JavaScript (AJAX) aufm Client.

Wobei ich mich beim letzteren nochn bissl reinfuchse^^

Auf diese Diskussion antworten