cPickle Datenbank + threading

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Brainsucker
User
Beiträge: 68
Registriert: Mittwoch 16. November 2011, 23:20

BlackJack hat geschrieben:Das kann ich mir eigentlich nur vorstellen wenn ständig alle Werte für eine Berechnung benötigt werden und dabei auch immer alle Werte geändert werden müssen.

So ist es auch. Ich will eine statistik erstellen, die aus den gesamte Daten der Datenbank errechnet wird.

Wie schon gesagt, besteht die Datenbank im wesentlichen aus einem datetime.datetime objekt für jeden Spieler der auf dem Gameserver ist/war.

Eventuell kommen später noch ein paar andere Keys hinzu. Aber ich würde mal sagen 95% des Scripts besteht im wesentlichen aus einem Key, der den Spieler eindeutig identifiziert und einem *subkey*, mit dem datetime objekt als wert.

Edit:

Um den grundsätzlichen *aufbau* der "Datenbank" als Pythoncode ein kleinwenig zu verdeutlichen:

Code: Alles auswählen

visitors = {}
today = datetime.datetime.today()
if not visitors.has_key('creationdate'):
	# Wann wurde die Datenbank erstellt?
	visitors['creationdate'] = today
	
if not visitors.has_key('players'):
	# In diesem Key befinden sich die Daten der Spieler
	visitors['players'] = {}
	
if not visitors['players'].has_key('unique_ip'):
	# Dieser Key enthält die IPs der Spieler
	visitors['players']['unique_ip'] = {}
	
if not visitors['players'].has_key('unique_id'):
	# Dieser Key enthält einen Wert für jeden Spieler, der den Spieler exakt identifiziert.
	visitors['players']['unique_id'] = {}
BlackJack

@Brainsucker: Ich weiss es ist nur Pseudocode, aber `dict.has_key()` ist „deprecated“. In Python 3 gibt es die Methode nicht mehr, aber auch bei Python 2 macht es Sinn stattdessen den ``in``- beziehungsweise den ``not in``-Operator zu verwenden. Es ist kürzer, mindestens im Falle der Negierung (IMHO) auch leichter lesbar, und last but not least (in CPython) geringfügig schneller, da statt eines dynamischen Methodenaufrufs ein einzelner Bytecode für den Operator tritt, der eine von mehreren „magischen“ Methoden aufruft (z.B. `__contains__()`), die schneller erreichbar und aufrufbar sind als die `has_key()`-Methode. Also statt ``if not a_dict.has_key(key):`` besser ``if key not in a_dict:``.

Aus der Beschreibung wird mir nicht ersichtlich wo die Daten alle geändert werden? Denn nur dann macht es Sinn immer alle wieder zu schreiben. Es sieht eher so aus, als wenn da immer neue Daten hinzu kommen. Dann sollte man auch nur diese in eine DB wegschreiben müssen.

Der Aufbau ist mir auch noch nicht ganz klar. Was ist in den innersten Wörterbuchern enthalten?

Code: Alles auswählen

{
    'creationdate': datetime.datetime(2011, 12, 31, 10, 5, 38, 833585),
    'players': {
        'unique_ip': {}, # ?
        'unique_id': {}, # ?
    }
}
Das sieht bis jetzt so aus, als wenn man den grossteil der Daten in eine relationale Datenbank packen könnte. Oder in ein `shelve`. Oder eine Objektdatenbank.

Kannst Du die Berechnung der Statistiken nicht vielleicht so formulieren, dass sie nicht immer komplett neu berechnet werden müssen, sondern dass man ein altes Ergebnis mit neuen Daten aktualisieren kann? Dann bräuchtest Du sowohl beim Lesen als auch beim Schreiben nicht mehr alle Daten, sondern nur noch die aktuellen noch nicht verarbeiteten beziehungsweise gespeicherten Daten.

Bei einer „richtigen“ Datenbank mit Transaktionen könnte man die Statistikberechnung vielleicht sogar komplett aus dem Gameserver heraus nehmen und nebenher regelmässig machen. Das im Spiel eingebundene Skript muss dann nur neue „Rohdaten“ in die DB schreiben und von dort die fertige(n) Statistik(en) auslesen.
Dav1d
User
Beiträge: 1437
Registriert: Donnerstag 30. Juli 2009, 12:03
Kontaktdaten:

@Brainsucker, das habe ich gelesen:

Code: Alles auswählen

from datetime import datetime
from cPickle import dump, load
from time import time

stimes = 800*360 # 800 = mittel aus 600 und 1000, 360 für ein Jahr

example = {'foo' : datetime.now(), # ein etwas größeres als dein dict
           'bar' : {'dunno' : dict((i,i) for i in range(15)),
                    'dunno2' : dict((i,i) for i in range(10))},
            'baz' : 123}

example_big = [example for _ in range(stimes)] 
            
dump_start = time()
with open('/tmp/data.pkl', 'w') as f:
    dump(example_big, f)

dump_end = time()
    
with open('/tmp/data.pkl', 'r') as f:
    foo = load(f)
  
load_end = time()

print 'Dump:', dump_end - dump_start
print 'Load:', load_end - dump_end
print 'Data:', len(foo)

Code: Alles auswählen

─[ArchBox][/tmp]╼ time python2 setst.py             
Dump: 0.178967952728
Load: 0.127691984177
Data: 288000
python2 setst.py  0,37s user 0,02s system 99% cpu 0,387 total
Nicht gerade lange…
the more they change the more they stay the same
Brainsucker
User
Beiträge: 68
Registriert: Mittwoch 16. November 2011, 23:20

Dav1d hat geschrieben:

Code: Alles auswählen

─[ArchBox][/tmp]╼ time python2 setst.py             
Dump: 0.178967952728
Load: 0.127691984177
Data: 288000
python2 setst.py  0,37s user 0,02s system 99% cpu 0,387 total
Nicht gerade lange…

Danke für deine bemühungen, aber dein beispiel ist keinesfalls wirklich praxisnahe, da der Gameserver während dem Betrieb noch mit vielen anderen Berechnungen u.s.w. beschäftigt ist und außerdem in der Regel noch weitere Scripts auf dem Server laufen, kann der Gameserver der cPickle.dump funktion nur einen Teil der CPU zuweisen. Somit vordoppelt oder verdreifacht sich die von dir ausgerechnete Zeit ganz schnell mal. Und das würde ohne Threading ein lagg von 1-1,5 Sekunden verursachen, was nicht gerade schön ist auf einem Gameserver.
Brainsucker
User
Beiträge: 68
Registriert: Mittwoch 16. November 2011, 23:20

@BlackJack:

Hey, danke das mit dem ´in´ werd ich mir merken, wusste nicht dass das schneller ist als die ´has_key´-Funktion.


So nun Back2Topic:
Die vollständige Datenbank sieht in etwa so aus:

Code: Alles auswählen

{
    'creationdate': datetime.datetime(2011, 12, 31, 10, 5, 38, 833585),
    'players': {
        'unique_id': {'spieler1': {'lastonline': datetime.datetime.today()
                                    }
                    'spieler2': 'lastonline': datetime.datetime.today()}, # !
        'unique_ip': {'IP1': {'lastonline': datetime.datetime.today()}
                    'IP2': {'lastonline': datetime.datetime.today()}}, # !
    }
}
Heißt also, dass jede IP und jeder Spieler, die auf dem Server war eine eigenes datetime objekt bekommt, wann diese(r) zuletzt auf dem Server gesehen wurde.

Das stimmt durchaus, dass es eher sinnvoll wäre ausschließlich nur die Daten zu schreiben, die auch tatsächlich geändert wurden. Allerdings ist da cPickle in diesem Falle sehr begrenzt.

Also wäre wohl tatsächlich eine andere Datenbankart eher geeignet, die nur die Daten schreibt, die die auch wirklich geändert wurden.

Hmm, dann ist wohl cPickle nicht wirklich die Optimale lösung.
Dav1d
User
Beiträge: 1437
Registriert: Donnerstag 30. Juli 2009, 12:03
Kontaktdaten:

Mir ging es darum, dir zu zeigen, dass du versuchst zu optimieren, wenn es nicht sicher ist, ob es überhaupt nötig ist. Außerdem wie BlackJack meinte, mit den falschen Mitteln, da threading dir in Verbindung mit pickle überhaupt nichts nützen könnte.
the more they change the more they stay the same
Brainsucker
User
Beiträge: 68
Registriert: Mittwoch 16. November 2011, 23:20

Ich weiß aus der Praxis, dass threading zum gewünschten ergebniss führt.

Einmal hatte ich selbes problem, dass beim speichern/laden der Daten der Server gelaggt hat. Kaum waren die threads drin, hats nicht mehr gelaggt.

Allerdings hatte dies andere nachteile, da ich damals noch kein Lock() verwendet hatte.

Und nun wollte ich die sache richtig angehen und eben die sache so schreiben, dass die Nachteile nicht mehr auftreten. Desshalb hab ich hier um hilfe angefragt.
Brainsucker
User
Beiträge: 68
Registriert: Mittwoch 16. November 2011, 23:20

Also Leute, ich hab mich nun umentschieden eine richtige sqlite3 Datenbank anstatt dem cPickle Serialisierungsmurks zu benutzen. Somit kann das Topic hier dicht gemacht werden und ich mach dann im passenden Unterforum ein neues Topic auf wenn ich fragen zu sqlite habe.


Danke euch allen für die hilfe :)
Benutzeravatar
noisefloor
User
Beiträge: 3858
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,

gut. ;-)

Wenn du wirklich durchweg "nur" Schlüssel-Werte Paare schreibst / liest, dann sollte für dich ein Key-Value Store (wie z.B. Redis) auch interessant sein.

Zum einen sind die ziemlich schnell, zum anderen kann zumindest Redis die Daten asynchron im Hintergrund auf die HD schreiben. Und es gibt auch einen Object-Mapper für Redis.

Gruß, noisefloor
Brainsucker
User
Beiträge: 68
Registriert: Mittwoch 16. November 2011, 23:20

Hey,

ja der größteil sind natürlich schon schlüssel/werte paare in der Datenbank.

Allerdings das was mich bisher am aller meißten über sqlite 'verärgert', ist die umständliche art abzufragen ob für einen bestimmten spieler bereits eine Reihe in der Datenbank existiert und man dann je nach dem die Reihe Updaten oder Inserten muss. Etwas recht umständlich wie ich finde.

Code: Alles auswählen

# Wir gehen davon aus, dass "cur" eine Variable ist, die eine sqlite3.Connect instanz beinhaltet.
cur.execute("SELECT * FROM datenbanke WHERE spielerid=?", (spielerid,))

Bei den dicts war das deutlich einfach mit has_key bzw. wie im verlauf des topics schon beschrieben den ´in´ operator zu benutzen.
Benutzeravatar
noisefloor
User
Beiträge: 3858
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,

na ja, was heißt umständlich. Wenn du wissen willst, ob etwas in einer (relationalen) Datenbank abfragen willst, du brauchst du einen Query. Gibt der "None" zurück, ist nichts drin.

Vom Prinzip ist das nicht anderes als das, was du mit dem Dict auch machst. :-)

Gruß, noisefloor
BlackJack

@Brainsucker: Statt ``*`` könntest Du vielleicht besser ``count(*)`` oder einfach nur ``1`` selektieren. Denn die Daten möchtest Du ja gar nicht haben.

Ausserdem könntest Du statt abzufragen auch einfach das machen was erwartungsgemäss häufiger klappen wird und testen ob es wirklich funkioniert hat, und falls nicht das andere machen.
Brainsucker
User
Beiträge: 68
Registriert: Mittwoch 16. November 2011, 23:20

BlackJack hat geschrieben:@Brainsucker: Statt ``*`` könntest Du vielleicht besser ``count(*)`` oder einfach nur ``1`` selektieren. Denn die Daten möchtest Du ja gar nicht haben.
Hey, danke wusste gar nicht dass man auch irgendetwas x-beliebiges selektieren kann. Das macht die sache schon etwas einfacher um den datenfluss so gering wie möglich zu halten.
Antworten