Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Ruby on Rails Datenbankoptimierung (german)

Ruby on Rails Datenbankoptimierung (german)

Beispiel, wie man eine Rails-Webanwendung durch Optimierung der erzeugten Datenbankabfragen besser skalieren kann. Thema sind speziell die Zusammenarbeit von klassischer relationaler Datenbank und ActiveRecord.
Vorgestellte Techniken spannen einen Bogen von der Verminderung der Felder mit select und pluck, über die Reduzierung der Datenbankabfragen mit join bis hin zu handgeschrieben SQL, um beispielsweise verbundene Objekte schon in der Datenbank zu aktualisieren.

Vortrag bei der Hamburger Ruby-Usergroup am 8.August 2012.

Karsten Meier

August 08, 2012
Tweet

More Decks by Karsten Meier

Other Decks in Programming

Transcript

  1. Datenbankoptimierung
    Karsten Meier
    meier-online.com
    Beispiele für die
    Optimierung an der
    Ruby-on-Rails-Schnittstelle

    View Slide

  2. 2
    Mein Background

    1986: SQL im Studium

    1996: QuarkXpress -> HTML Converter

    1998-2001: WebObjects, MVC, ORM

    2004: Erster Kontakt mit Ruby (Pleac)

    Seit 2005: Handylearn Projects

    Seit 2009: Nutzung von Rails

    View Slide

  3. 3
    Fallbeispiel Cycosmos

    Community

    Webobjects

    ORM
    Enterprise Objects

    3 Appserver,
    1 DB Server

    View Slide

  4. Reduktion der Datenbankzugriffe

    Bessere
    Antwortzeiten

    Weniger
    Datenbanklast

    300% höherer
    Durchsatz

    Höhere Stabilität

    View Slide

  5. Die Schichten einer Anwendung

    View Slide

  6. Fette Objekte
    id
    draft
    name
    teu
    build_year
    legal_country
    company
    call_sign
    imo
    imo_certificate
    speech_of_sponsor
    grt
    machine

    View Slide

  7. Schattenobjekte
    ContainerVessel.
    select('id, name')
    order('name')

    Read-Only

    Nur angegebene Attribute

    Exception falls
    unbekannt

    ID wirft keine Exception
    ActiveRecord::MissingAttributeError
    ActiveRecord::ReadOnlyRecord

    View Slide

  8. Rosinen picken

    Nur eine Spalte

    Objekt unwichtig

    pluck(column)

    ab Rails 3.2
    ContainerVessel.pluck(:name)
    ['Australia', 'Brisbane', 'Busan',...]

    View Slide

  9. Darf das Schiff ablegen?

    View Slide

  10. Outsourcing

    Gewicht aller Container

    Berechnung kann die DB durchführen

    Rails sieht die einzelnen Container nicht
    @vessel.containers.inject{...}
    @vessel.containers.sum('weight')

    View Slide

  11. Verknüpfte Objekte

    Eine Reederei mit Liste ihrer Schiffe und
    Flaggenstaaten

    View Slide

  12. Ablaufdiagramm

    View Slide

  13. includes()
    @container_vessels =
    @company.container_vessels.
    order(:name).
    includes(:legal_country)
    SELECT "container_vessels".*
    FROM "container_vessels"
    WHERE "container_vessels"."company_id" = 2
    ORDER BY name
    SELECT "countries".*
    FROM "countries"
    WHERE "countries"."id" IN (8, 7, 4)

    View Slide

  14. includes()

    Jede Abfrage liefert
    einen Objekttyp

    Rails behält
    Kontrolle

    Schachtelung
    möglich

    Feintuning schwierig
    .includes(:legal_country => :tax_rates)
    .select('country.image????')

    View Slide

  15. Was war noch mal ein Join?

    View Slide

  16. Inner/Left/Outer/Right

    View Slide

  17. Rails joins

    Keine flaggenlose
    Schiffe

    Keine Staaten
    @container_vessels =
    @company.container_vessels.
    order(:name).
    joins(:legal_country)

    View Slide

  18. Filtern mit joins()

    Filtern anhand von verbundenen Daten

    Nur Zielobjekte werden geliefert

    Vorsicht vor Vervielfachung
    @companies = Company.order(:name).
    joins(:container_vessels).
    where(["container_vessels.build_year > ?", 2009])
    SELECT "companies".*
    FROM "companies"
    INNER JOIN "container_vessels"
    ON "container_vessels"."company_id" = "companies"."id"
    WHERE (container_vessels.build_year > 2009)
    ORDER BY name

    View Slide

  19. Automatischer Join
    in Associationen
    class Country < ActiveRecord::Base
    has_many :registering_companies,
    :through => :registered_vessels,
    :source => 'company',
    :class_name => 'Company',
    :uniq => true
    ...
    @companies = @country.registering_companies
    SELECT DISTINCT "companies".*
    FROM "companies"
    INNER JOIN "container_vessels"
    ON "companies"."id" = "container_vessels"."company_id"
    WHERE "container_vessels"."legal_country_id" = 10

    View Slide

  20. Join direkt verwenden?

    View Slide

  21. Echte Datenbankjoins aus Rails
    connection = Company.connection
    columns = "container_vessels.id, container_vessels.name,\
    container_vessels.imo, container_vessels.teu, \
    countries.name as legal_country_name"
    sql = 'SELECT ' + columns + ' FROM "container_vessels" \
    JOIN "countries" \
    ON "countries"."id" = "container_vessels"."legal_country_i
    WHERE "container_vessels"."company_id" = ' + @company.id.t
    ' ORDER BY "container_vessels".name'
    @vessel_data = connection.select_all(
    sql, 'ContainerVessel Overview Load')

    View Slide

  22. select_all Rückgabewerte

    select_all: array of hashes

    select_rows: array of arrays
    <% @vessel_data.each do |data| %>

    <%= data['name'] %>
    <%= data['imo'] %>
    <%= data['teu'] %>
    <%= data['legal_country_name'] %>
    ...
    <% end %>

    View Slide

  23. Parameter-Überprüfung

    SQL-Injection

    Methoden leider
    etwas versteckt

    Ab Rails 3.2:
    ActiveRecord::
    Sanitization

    Bei IDs: to_i.to_str
    Company.where(
    'name like '%?', input)
    record.sanitize_sql_array(..)
    replace_bind_variables()
    quote_bound_value()
    connection.quote_string()

    View Slide

  24. Schreiben
    Wenn es Performanceprobleme beim Schreiben
    gibt, dann sind sie meistens schwerwiegend.

    View Slide

  25. IDs

    ID-Vergabe kann
    zentraler
    Flaschenhals sein

    IDs schon existent?

    View Slide

  26. Transaktionen

    gewährleisten die Konsistenz (ACID)

    weniger Sperren, schnelleres Schreiben

    Ab 2 Schreiboperationen -> nutzen!

    View Slide

  27. Massenupdates
    UPDATE container_vessels
    SET company_id = 7
    WHERE company_id = 5

    Firma wird verkauft

    Alle Schiffen bekommen neuen Besitzer
    connection.update_sql(sql, "Updating vessel...")

    View Slide

  28. Verbundene Updates

    Beispiel Denormalisierung

    Name des Landes soll auch im
    Schiffsdatensatz gespeichert werden
    UPDATE container_vessels, country
    SET container_vessels.country_name = country.name
    WHERE container_vessels.legal_country_id = country.id

    View Slide

  29. Keine Angst for SQL
    "Many people treat the relational database
    like a crazy aunt who's shut up in an attic
    and whom nobody wants to talk about"
    Martin Fowler: OrmHate

    View Slide

  30. ... end
    meier-online.com
    Website von Karsten Meier:
    Bilder:
    Container ship by jogdragoon, openclipart.org
    Hammer5 by Krystof Jetmar, openclipart.org
    OOCL Montreal & Cosco Hope fotografiert von Karsten Meier im Hamburger Hafen 2012

    View Slide