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. 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
  2. 6.

    Fette Objekte id draft name teu build_year legal_country company call_sign

    imo imo_certificate speech_of_sponsor grt machine
  3. 7.

    Schattenobjekte ContainerVessel. select('id, name') order('name') • Read-Only • Nur angegebene

    Attribute • Exception falls unbekannt • ID wirft keine Exception ActiveRecord::MissingAttributeError ActiveRecord::ReadOnlyRecord
  4. 8.

    Rosinen picken • Nur eine Spalte • Objekt unwichtig •

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

    Outsourcing • Gewicht aller Container • Berechnung kann die DB

    durchführen • Rails sieht die einzelnen Container nicht @vessel.containers.inject{...} @vessel.containers.sum('weight')
  6. 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)
  7. 14.

    includes() • Jede Abfrage liefert einen Objekttyp • Rails behält

    Kontrolle • Schachtelung möglich • Feintuning schwierig .includes(:legal_country => :tax_rates) .select('country.image????')
  8. 17.

    Rails joins • Keine flaggenlose Schiffe • Keine Staaten @container_vessels

    = @company.container_vessels. order(:name). joins(:legal_country)
  9. 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
  10. 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
  11. 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')
  12. 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 %>
  13. 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()
  14. 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...")
  15. 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
  16. 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
  17. 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