Tag Archive for 'elixir'

python elixir query optimierung

Ich bin vor ein paar Tagen bei der Programmierung von PyCRMan fast verzweifelt. Wie ich schon in einem früheren Eintrag beschrieb, verwende ich Elixir als Wrapper für die Datenbank und mir ist aufgefallen, das Elixir bei meinen Model sehr viele DB-Queries generiert. Die Anzahl der Queries ist so hoch das die Netzwerk Latenzzeiten eine sehr große Rolle spielen :( Nach ewig langen erfolglosem Studieren der etwas dürftigen Elixir Dokumentation bin ich nachts um ca. 1 Uhr in der SQLAlchemy Dokumentation fündig geworden. Die “geheimen” Zauberwörter heißen “lazyload” und “eagerload”, wenn ich das nur vorher schon gewußt hätte ;)

Hier ein kleines Beispiel:

Als erstes mal ein kleines Model (ja das ist das Beispiel aus der Elixir Doku :) )

from elixir import *
metadata.bind = "sqlite:///:memory:"
metadata.bind.echo = True
 
class Movie(Entity):
  title = Field(String(30))
  director = ManyToOne('Director')
 
  def __repr__(self):
    return '<Movie "%s">' % (self.title)
 
class Director(Entity):
  name = Field(String(60))
  movies = OneToMany('Movie')
 
  def __repr__(self):
    return '<Director "%s">' % self.name
 
setup_all()
create_all()

Jetzt ein paar Werte einfügen:

rscott = Director(name="Ridley Scott")
glucas = Director(name="George Lucas")
unknown = Director(name="Nobody Unknown")
alien = Movie(title="Alien", director=rscott)
swars = Movie(title="Star Wars", director=glucas)
brunner = Movie(title="No Name", director=unknown)
session.flush()
session.clear()
 
for m in Movie.query.all():
  print "Movie: name=%s director=%s" % (m.title, m.director.name)

Wenn man nun auf die letzten sql queries schaut, sieht man das erst alle movies aus der DB geladen werden und dann für jeden einzelnen Film wird der Director einzeln geholt. Ha nun ist klar warum das alles so langsam geht :)

SELECT __main___movie.id AS __main___movie_id, __main___movie.title AS __main___movie_title, __main___movie.director_id AS __main___movie_director_id 
FROM __main___movie ORDER BY __main___movie.oid
[]
SELECT __main___director.id AS __main___director_id, __main___director.name AS __main___director_name 
FROM __main___director 
WHERE __main___director.id = ?
[1]
SELECT __main___director.id AS __main___director_id, __main___director.name AS __main___director_name 
FROM __main___director 
WHERE __main___director.id = ?
[2]
SELECT __main___director.id AS __main___director_id, __main___director.name AS __main___director_name 
FROM __main___director 
WHERE __main___director.id = ?
[3]

Damit aus diesen beispielhaften 4 Queries EINE wird kann man an so einer Stelle eagerload verwenden.

# import eagerload from sqlalchemy
from sqlalchemy.orm import eagerload
 
for m in Movie.query.options(eagerload("director")).all():
  print "Movie: name=%s director=%s" % (m.title, m.director.name)

Und schon ist es eine Query:

SELECT __main___movie.id AS __main___movie_id, __main___movie.title AS __main___movie_title, __main___movie.director_id AS __main___movie_director_id, __main___director_1.id AS __main___director_1_id, __main___director_1.name AS __main___director_1_name 
FROM __main___movie LEFT OUTER JOIN __main___director AS __main___director_1 ON __main___movie.director_id = __main___director_1.id ORDER BY __main___movie.oid, __main___director_1.oid

Alternativ zum “eagerload” kann man auch im Model das lazyload abschalten.

class Movie(Entity):
  title = Field(String(30))
  director = ManyToOne('Director', lazy=False) # <- lazyload now False
 
  def __repr__(self):
    return '<Movie "%s">' % (self.title)

Und wenn man mal das ganze Gegenteil braucht, kann man lazyload verwenden und im Model heißt die Option “deferred=True”.


posted with Vim

python elixir session

Zur Zeit mache ich beim ZID mein Praxissemester, ich programmiere für uns den Python Computer Room Manager (pycrman). Dieses Projekt ist in Python geschrieben und benutzt verschiedene Python Erweiterung. Zur einer Erweiterung will ich hiermit ein bissel was schreiben :)

Elixir

Elixir ist “dünner” Wrapper für die SQLAlchemy Library. Beide zusammen vereinfachen den Umgang mit einer Datenbank in Python um ein vielfaches.

Hier ein kleines Beispiel mit 2 Tabellen:

# import elixir stuff
from elixir import *
 
# setup db connection
metadata.bind = "sqlite:///movies.sqlite"
metadata.bind.echo = True
 
# first table
class Device(Entity):
  """representation of a device
  """
  using_options(tablename='device', inheritance='multi')
 
  id = Field(Integer, primary_key=True)
  name = Field(String(127), required=True, unique=True)
  domain = Field(String(255), required=True)
  interfaces = OneToMany('Interface')
 
  def __repr__(self):
    return 'Device %s' % (self.name)
 
# second table
class Interface(Entity):
  """network interfaces
  """
  using_options(tablename='interface')
 
  id = Field(Integer, primary_key=True)
  name = Field(String(16), required=True)
  ip = Field(String(38), required=True, unique=True)
  mac = Field(String(17), unique=True)
 
  device = ManyToOne('Device')
 
  def __repr__(self):
    return '<Interface: %s ip: %s mac: %s>' % (
        self.name, self.ip, self.mac)
 
setup_all()
drop_all()
create_all()

und schon kann mal auf einfache und sehr elegante Weise diese 2 Tabellen benutzen:

# create new device object
device = Device(name="foobar")
device.domain = "test.localdomain"
 
# create new interface object
interface = Interface(name="eth0", ip="1.1.1.1")
 
# add interface object to device object
device.interfaces = [interface]
 
# store objects in db
session.flush([device, interface])
 
# query
device = Device.get_by(name="foobar")
print device

Ein schönes Beispiel gibt es auch im Elixir Tutorial.

Noch ein kleiner Beitrag zu den elixir/sqlalchemy sessions. In der Standard Einstellung sind die Elixir sessions sogenannte “scoped_session”, d.h. das jeder Thread in einer threaded Umgebung seine eigene Elixir session hat. Das hat zur folge, das wenn ein Thread ein Objekt anlegt bzw. verändert, ein anderer Thread das nicht unbedingt mit bekommt, da Elixir/SQLAlchemy die Query Resultate cached. Am besten geht man damit um das man den Cache ausschaltet:

import elixir
elixir.options_defaults['mapper_options'] = dict(always_refresh=True)

oder bei einer Query populate_existing() benutzt.


posted with Vim