Slide 1

Slide 1 text

WHO’S AFRAID OF DATABASE VIEWS? Robbie Clutton

Slide 2

Slide 2 text

@robb1e Who am I and why am I so interested in this?

Slide 3

Slide 3 text

@robb1e What are your go-to tools for solving performance issues?

Slide 4

Slide 4 text

@robb1e caching? de-normalised data? document database? client side rendering?

Slide 5

Slide 5 text

@robb1e Let’s look at an example of a manufacturing process

Slide 6

Slide 6 text

@robb1e Product Component Part A p ro d u c t i s m a d e u p o f m a n y components which in turn is made up of many parts. Each part has a cost associated to is, and each component and each product has a manufacturing cost. How much does it take to produce a given product?

Slide 7

Slide 7 text

@robb1e 10,000 parts 1,000 components 10 products ! each component has 100 parts each product has 100 components

Slide 8

Slide 8 text

@robb1e Product.each do |product| // add product costs product.components.each do |component| // add component costs component.parts.each do |part| // add part costs end end // print product price end Product.each do |product| // add product costs product.components.each do |component| // add component costs component.parts.each do |part| // add part costs end end // print product price end Product.each do |product| // add product costs product.components.each do |component| // add component costs component.parts.each do |part| // add part costs end end // print product price end

Slide 9

Slide 9 text

@robb1e O(n ) 3

Slide 10

Slide 10 text

@robb1e http://rob-bell.net/2009/06/a-beginners-guide-to-big-o-notation/ Big O notation is used in Computer Science to describe the performance or complexity of an algorithm. Big O specifically describes the worst-case scenario, and can be used to describe the execution time required or the space used (e.g. in memory or on disk) by an algorithm.

Slide 11

Slide 11 text

@robb1e

Slide 12

Slide 12 text

@robb1e Product.each do |product| // add product costs product.components.each do |component| // add component costs component.parts.each do |part| // add part costs end end // print product price end

Slide 13

Slide 13 text

@robb1e O(n ) 3

Slide 14

Slide 14 text

@robb1e 15.8 seconds

Slide 15

Slide 15 text

@robb1e Product. includes(components: [:parts]). each do |product| // add product costs product.components.each do |component| // add component costs component.parts.each do |part| // add part costs end end // print product price end

Slide 16

Slide 16 text

@robb1e 8x faster 2 seconds

Slide 17

Slide 17 text

@robb1e O(n ) 3

Slide 18

Slide 18 text

@robb1e Product. includes(components: [:parts]). each do |product| // add product costs product.components.each do |component| // add component costs component.parts.each do |part| // add part costs end end // print product price end

Slide 19

Slide 19 text

@robb1e class AddProductCosts < ActiveRecord::Migration ! def up execute <<-SQL CREATE OR REPLACE VIEW product_costs AS … SQL end ! def down execute “DROP VIEW product_costs” end ! end

Slide 20

Slide 20 text

@robb1e CREATE OR REPLACE VIEW product_costs AS SELECT products.id AS product_id, products.name AS product_name, products.manufacture_cost + sum(component_costs.cost)) AS product_manufacture_cost FROM products INNER JOIN product_components ON product_components.product_id = products.id INNER JOIN ( SELECT components.id AS component_id, (components.manufacture_cost + sum(cost)) AS cost FROM components INNER JOIN component_parts ON components.id = component_parts.component_id INNER JOIN parts ON parts.id = component_parts.part_id GROUP BY components.id ) AS component_costs ON component_costs.component_id = product_components.component_id GROUP BY products.id, products.name ORDER BY products.id

Slide 21

Slide 21 text

@robb1e SQL can be scary

Slide 22

Slide 22 text

@robb1e SELECT components.id AS component_id, (components.manufacture_cost + sum(cost)) AS cost FROM components INNER JOIN component_parts ON components.id = component_parts.component_id INNER JOIN parts ON parts.id = component_parts.part_id GROUP BY components.id

Slide 23

Slide 23 text

@robb1e Component Part Component Part component_id cost (component cost + sum(parts cost) 123 1234 456 2345

Slide 24

Slide 24 text

@robb1e SELECT products.id AS product_id, products.name AS product_name, products.manufacture_cost + sum(component_costs.cost)) AS product_manufacture_cost FROM products INNER JOIN product_components ON product_components.product_id = products.id INNER JOIN ( … ) AS component_costs ON component_costs.component_id = product_components.component_id GROUP BY products.id, products.name ORDER BY products.id

Slide 25

Slide 25 text

@robb1e product_id product_name cost 1 prod_1 12345 2 prod_2 23456 Product Component Product Component Cost

Slide 26

Slide 26 text

@robb1e class ProductCost < ActiveRecord::Base end

Slide 27

Slide 27 text

@robb1e ProductCost.each do |product_cost| // print product cost end

Slide 28

Slide 28 text

@robb1e O(n)

Slide 29

Slide 29 text

@robb1e 5x faster than includes 42x faster than memory 0.38 seconds

Slide 30

Slide 30 text

@robb1e But now I have business logic in two places

Slide 31

Slide 31 text

@robb1e maybe… but…

Slide 32

Slide 32 text

@robb1e before do // create Parts // create Components // create ComponentParts // create Products // create ProductComponents end

Slide 33

Slide 33 text

@robb1e it 'calculates the cost' do product_cost = ProductCost.where(product_name: 'product_1').first ! expected_cost = 1000 + 100 + 200 + 10 + 20 expect(product_cost.product_manufacture_cost).to eq(expected_cost) end

Slide 34

Slide 34 text

@robb1e Sacrifice local optimisations for system wide improvements …and you can keep TDDing

Slide 35

Slide 35 text

WHO’S AFRAID OF DATABASE VIEWS? (still) ^ github.com/robb1e/rails_db_views_demo ! @robb1e