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

Postgresql on Rails

Postgresql on Rails

Postgresql on Rails talk. RubyConf Uruguay,

thiagopradi

March 22, 2013
Tweet

More Decks by thiagopradi

Other Decks in Technology

Transcript

  1. Thiago Pradi • Lead developer at MetaFiscal • Ruby developer

    for 5 years • Author / contributor of Octopus (Database sharding gem for ActiveRecord) • Bachelor of computer science from FURB - Brazil
  2. Rails default stack • ActiveRecord as ORM • Supports different

    databases, like SQLite, MySQL and PostgreSQL • ActiveRecord “abstracts” the SQL Language
  3. PostgreSQL • OpenSource database with more than 15 years •

    Considered “The world's most advanced open-source database” • Successfully used by thousand of companies around the world • SQL follows SQL standard from ISO
  4. HStore • Data type to store key-value fields • NoSQL

    fields with no extra complexity! • Allows advanced queries on the fields • Allows indexing • Native on Rails 4, plugin available for older versions
  5. class CreateBlogs < ActiveRecord::Migration def up execute 'CREATE EXTENSION hstore'

    create_table :blogs do |t| t.column :contact_infos, :hstore t.timestamps end end def down drop_table :blogs execute 'DROP EXTENSION hstore' end end
  6. class Blog < ActiveRecord::Base store_accessor :contact_infos, :email, :twitter end blog

    = Blog.create blog.contact_infos # => nil # Accessor syntax blog.email = '[email protected]' blog.twitter = 'twitter.com/thiagopradi' # Alternative syntax blog.contact_infos[:facebook] = 'facebook.com/ thiagopradi'
  7. # Save the object blog.save # UPDATE "blogs" SET "contact_infos"

    = $1, # "updated_at" = $2 WHERE "blogs"."id" = 1 # [["contact_infos", {"email"=>"[email protected]", # "twitter"=>"twitter.com/thiagopradi", # "facebook"=>"facebook.com/thiagopradi"}], +00:00]]
  8. # Returns all objects with key twitter > Blog.where("contact_infos ?

    'twitter'").first => #<Blog id: 1, ... # Returns all objects where the key ‘twitter’ has the value ‘twitter.com/thiagopradi’ > Blog.where("contact_infos -> 'twitter' = 'twitter.com/thiagopradi'").first => #<Blog id: 1, ...
  9. Full Text Search • Queries with ‘like %%’ are painfully

    slow • Full Text Search for the rescue! • Support out of box! No extra dependencies (Sphinx/Lucene) • Integrated to ActiveRecord with the gem texticles, by @tenderlove
  10. class CreatePosts < ActiveRecord::Migration def change create_table :posts do |t|

    t.integer :blog_id, null: false t.string :title, null: false t.string :body, null: false t.timestamps end end end
  11. > Post.create blog: Blog.first, title: "Sample Title", body: "Sample Body"

    => #<Post id: 1, ..> > Post.search(title: 'Sample').load => #<ActiveRecord::Relation [#<Post id: 1, ...>]>
  12. # Post Load (2.9ms) SELECT "posts".*, # ts_rank(to_tsvector('english', "posts"."title"), #

    to_tsquery('english', 'Sample')) AS "rank0.8922075761585498" # FROM "posts" WHERE (to_tsvector('english', "title") # @@ to_tsquery('english', 'Sample')) ORDER BY "rank0.8922075761585498" DESC
  13. Supports... • Indexes for faster queries • Partial matches •

    Trigram based search (similar words) • Support dictionaries in different languages (including portuguese / spanish)
  14. MySQL also supports! • But only with the MyISAM storage

    engine • Unfortunately, the tables are not transactioned with MyISAM :-(
  15. Partial Indexes • Allows the user to build indexes with

    conditions • Useful in different situations, from ensure uniqueness between rows or building indexes on the most active data • Integrated with Rails (Rails 4)
  16. class CreateAccounts < ActiveRecord::Migration def change create_table :accounts do |t|

    t.string :code, null: false t.boolean :active, null: false t.timestamps end add_index(:accounts, :code, unique: true, where: "active") end end
  17. > Account.create code: "10", active: true => #<Account id: 6,

    code: "10", active:true, ..> > Account.create code: "10", active: false => #<Account id: 7, code: "10", active:false, ..> > Account.create code: "10", active: false => #<Account id: 8, code: "10", active:false, ..> > Account.create code: "10", active: true ActiveRecord::RecordNotUnique: PG::Error: ERROR: duplicate key value violates unique constraint "index_accounts_on_code" DETAIL: Key (code)=(10) already exists.
  18. Rollback for DDL • Supports rollback for data definition commands

    (create table, alter table) • Helps the developer, not leaving “garbage” when a migration fails
  19. Prepared Statements • Cache of parse / execution plan of

    each query • Out of box on Rails 3.x • 1.5 to 10 times faster on complex queries • Unless you’re using MySQL, so no performance boost :-(
  20. Window Functions • Aggregation functions • Allow grouping / partitioning

    of data • It’s possible to do with MySQL, but without native support • Examples: row_number(), rank()
  21. Safer type handling • Raises error when the value doesn’t

    match the field size • Type comparison is safer than “others” databases
  22. class User < ActiveRecord::Base has_many :products before_create :generate_access_token private def

    generate_access_token self.auth_token = SecureRandom.hex end end class Product < ActiveRecord::Base belongs_to :user end
  23. class ProductsController < ApplicationController respond_to :xml, :json def create @user

    = User.where('auth_token = ?', params[:auth_token]).first if @user.present? @product = @user.products.create(params[:product]) respond_with(@product) else head :bad_request end end end
  24. > User.create name: "Thiago", email: "[email protected]" => #<User id: 1,

    name: "Thiago", ...> > User.create name: "Lucas", email: "[email protected]" => #<User id: 2, name: "Lucas", ...>
  25. PostgreSQL Started POST "/products" for 127.0.0.1 at 2013-03-08 02:43:43 -0300

    Processing by ProductsController#create as XML Parameters: {"auth_token"=>0, "product"=>{}} User Load (3.9ms) SELECT "users".* FROM "users" WHERE (auth_token = 0) ORDER BY "users"."id" ASC LIMIT 1 PG::Error: ERROR: operator does not exist: character varying = integer LINE 1: SELECT "users".* FROM "users" WHERE (auth_token = 0) ORDE...
  26. MySQL Started POST "/products" for 127.0.0.1 at 2013-03-08 02:40:11 -0300

    Processing by ProductsController#create as XML Parameters: {"auth_token"=>0, "product"=>{}} User Load (0.8ms) SELECT `users`.* FROM `users` WHERE (auth_token = 0) ORDER BY `users`.`id` ASC LIMIT 1 (0.3ms) BEGIN SQL (2.9ms) INSERT INTO `products` (`created_at`, `updated_at`, `user_id`) VALUES ('2013-03-08 05:40:11', '2013-03-08 05:40:11', 1) (0.7ms) COMMIT
  27. Results • Mysql has a weird string arithmetic • ???

    To Fix • Or, simply use PostgreSQL ;-)
  28. Other features... • Extension for Geospatial data (PostGIS) • Hot

    StandBy • Synchronous/Asynchronous Replication
  29. Transactions • Use to ensure that your operations are atomic!

    • Assume that your data will be consistent if something fails
  30. Constraints • Makes your database consistent (not null, field limits)

    • Use unique index to ensure uniqueness of fields
  31. create_table :accounts do |t| t.string :code, null: false t.boolean :active,

    null: false, default: true t.integer :max_value, null: false, limit: 8 t.timestamps end add_index(:accounts, :code, unique: true, where: "active")
  32. Concurrency Model • Know what your database is doing under

    the hood • Some locks / constraints can make your asynchronous application behave like serial processing. • Study about MVCC - Multi-Version Concurrency Control
  33. Don’t be afraid of writing custom SQL • Bulk insert

    • Custom joins (left join, right join, outer join)
  34. Database Availability • Daily backups, at least. • Replicated database

    instances in different geographic locations • Bad things happen, assume this.
  35. Knowing your database can improve... • Performance • Security •

    Maintainability • Developer Happiness! :-)