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.

7c40307aec03fe08ddb87f354c79b81c?s=128

Karsten Meier

August 08, 2012
Tweet

Transcript

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

    Ruby-on-Rails-Schnittstelle
  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
  3. 3 Fallbeispiel Cycosmos • Community • Webobjects • ORM Enterprise

    Objects • 3 Appserver, 1 DB Server
  4. Reduktion der Datenbankzugriffe • Bessere Antwortzeiten • Weniger Datenbanklast •

    300% höherer Durchsatz • Höhere Stabilität
  5. Die Schichten einer Anwendung

  6. Fette Objekte id draft name teu build_year legal_country company call_sign

    imo imo_certificate speech_of_sponsor grt machine
  7. Schattenobjekte ContainerVessel. select('id, name') order('name') • Read-Only • Nur angegebene

    Attribute • Exception falls unbekannt • ID wirft keine Exception ActiveRecord::MissingAttributeError ActiveRecord::ReadOnlyRecord
  8. Rosinen picken • Nur eine Spalte • Objekt unwichtig •

    pluck(column) • ab Rails 3.2 ContainerVessel.pluck(:name) ['Australia', 'Brisbane', 'Busan',...]
  9. Darf das Schiff ablegen?

  10. Outsourcing • Gewicht aller Container • Berechnung kann die DB

    durchführen • Rails sieht die einzelnen Container nicht @vessel.containers.inject{...} @vessel.containers.sum('weight')
  11. Verknüpfte Objekte • Eine Reederei mit Liste ihrer Schiffe und

    Flaggenstaaten
  12. Ablaufdiagramm

  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)
  14. includes() • Jede Abfrage liefert einen Objekttyp • Rails behält

    Kontrolle • Schachtelung möglich • Feintuning schwierig .includes(:legal_country => :tax_rates) .select('country.image????')
  15. Was war noch mal ein Join?

  16. Inner/Left/Outer/Right

  17. Rails joins • Keine flaggenlose Schiffe • Keine Staaten @container_vessels

    = @company.container_vessels. order(:name). joins(:legal_country)
  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
  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
  20. Join direkt verwenden?

  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')
  22. select_all Rückgabewerte • select_all: array of hashes • select_rows: array

    of arrays <% @vessel_data.each do |data| %> <tr> <td><%= data['name'] %></td> <td><%= data['imo'] %></td> <td><%= data['teu'] %></td> <td><%= data['legal_country_name'] %></td> ... <% end %>
  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()
  24. Schreiben Wenn es Performanceprobleme beim Schreiben gibt, dann sind sie

    meistens schwerwiegend.
  25. IDs • ID-Vergabe kann zentraler Flaschenhals sein • IDs schon

    existent?
  26. Transaktionen • gewährleisten die Konsistenz (ACID) • weniger Sperren, schnelleres

    Schreiben • Ab 2 Schreiboperationen -> nutzen!
  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...")
  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
  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
  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