184
Testing This TESTING TESTING TESTING TESTING Thursday, April 21, 2011

Behind the Curtain

Embed Size (px)

DESCRIPTION

My talk at red dirt ruby conf

Citation preview

Page 1: Behind the Curtain

Testing This

TESTING TESTING

TESTING TESTING

Thursday, April 21, 2011

Page 2: Behind the Curtain

ZOMG!

Thursday, April 21, 2011

Page 3: Behind the Curtain

GOOD MORNING!

Thursday, April 21, 2011

Page 4: Behind the Curtain

Aaron Patterson

Thursday, April 21, 2011

Page 5: Behind the Curtain

@tenderlove

Thursday, April 21, 2011

Page 6: Behind the Curtain

WWFMD?Thursday, April 21, 2011

Page 7: Behind the Curtain

ITWFMWD?Thursday, April 21, 2011

Page 8: Behind the Curtain

Thursday, April 21, 2011

Page 9: Behind the Curtain

AT&T, AT&T logo and all AT&T related marks are trademarks of AT&T Intellectual Property and/or AT&T affiliated companies.

Thursday, April 21, 2011

Page 10: Behind the Curtain

Miami Vice Principal Señor Facebook

Integration Engineering Manager

Thursday, April 21, 2011

Page 11: Behind the Curtain

Elect

Thursday, April 21, 2011

Page 12: Behind the Curtain

12%

61%

24%

2%

Inspirational Entertaining Informative Wasted

Our Time Together

Thursday, April 21, 2011

Page 13: Behind the Curtain

Thursday, April 21, 2011

Page 14: Behind the Curtain

KEYNOTE

Thursday, April 21, 2011

Page 15: Behind the Curtain

10%

50%

20%

20%

Inspirational Entertaining Informative Wasted

Our Time Together

Thursday, April 21, 2011

Page 16: Behind the Curtain

Thursday, April 21, 2011

Page 17: Behind the Curtain

20% More Transitions

Thursday, April 21, 2011

Page 18: Behind the Curtain

Living Behindthe Curtain 6 subjects, less

than 10minutes on each of them

I enjoy dealing with behinds the scenes information

Thursday, April 21, 2011

Page 19: Behind the Curtain

DATABASE

APPLICATION

CLIENT

Thursday, April 21, 2011

Page 20: Behind the Curtain

DATABASE

APPLICATION

CLIENT

Thursday, April 21, 2011

Page 21: Behind the Curtain

DATABASE

APPLICATION

CLIENT

Thursday, April 21, 2011

Page 22: Behind the Curtain

DATABASE

APPLICATION

CLIENT

Thursday, April 21, 2011

Page 23: Behind the Curtain

Code as Data

Thursday, April 21, 2011

Page 24: Behind the Curtain

Politics

Thursday, April 21, 2011

Page 25: Behind the Curtain

"Open Sourceis a Democracy"

We don't vote on commits. It would be too inefficient.

Thursday, April 21, 2011

Page 26: Behind the Curtain

OSS is an Oligarchy

Thursday, April 21, 2011

Page 27: Behind the Curtain

Benevolent Dictator

Thursday, April 21, 2011

Page 28: Behind the Curtain

Tyrannical Leader

Small projects have few people, so it is efficient

Large projects have too many people so it becomes inefficient to individually consider all feedback

Thursday, April 21, 2011

Page 29: Behind the Curtain

Thursday, April 21, 2011

Page 30: Behind the Curtain

Care and Feeding of Your Tyrant

Thursday, April 21, 2011

Page 31: Behind the Curtain

Cheetos

Thursday, April 21, 2011

Page 32: Behind the Curtain

Feed them Quality Bugs

Good reproduction steps

Thursday, April 21, 2011

Page 33: Behind the Curtain

Feed them Patches with Tests

Thursday, April 21, 2011

Page 34: Behind the Curtain

leave build systems

Gemspec,JewlerHoe,etc

Thursday, April 21, 2011

Page 35: Behind the Curtain

leave test frameworks

Thursday, April 21, 2011

Page 36: Behind the Curtain

Use +1's Carefully

Thursday, April 21, 2011

Page 37: Behind the Curtain

Thursday, April 21, 2011

Page 38: Behind the Curtain

Thursday, April 21, 2011

Page 39: Behind the Curtain

320 comments

Thursday, April 21, 2011

Page 40: Behind the Curtain

210 plus one

Thursday, April 21, 2011

Page 41: Behind the Curtain

Thursday, April 21, 2011

Page 42: Behind the Curtain

FFFFFFFUUUUUUUUUUUU

Thursday, April 21, 2011

Page 43: Behind the Curtain

Thursday, April 21, 2011

Page 44: Behind the Curtain

Superficial

Thursday, April 21, 2011

Page 45: Behind the Curtain

Merit

Thursday, April 21, 2011

Page 46: Behind the Curtain

Quality

Thursday, April 21, 2011

Page 47: Behind the Curtain

How to fix?

We'll discuss this in closing.

Thursday, April 21, 2011

Page 48: Behind the Curtain

Encodingsthe silent killer

Thursday, April 21, 2011

Page 49: Behind the Curtain

Never a miscommunication

Data goes in, Data goes out

Thursday, April 21, 2011

Page 50: Behind the Curtain

Until there is

Thursday, April 21, 2011

Page 51: Behind the Curtain

`encode': "\xE9" from ASCII-8BIT to UTF-8(Encoding::UndefinedConversionError)

Thursday, April 21, 2011

Page 52: Behind the Curtain

You can't explain that

Encoding Error?

Thursday, April 21, 2011

Page 53: Behind the Curtain

String Tagging

Thursday, April 21, 2011

Page 54: Behind the Curtain

"hello! <3".encoding # => #<Encoding:UTF-8>

Strings with unknown encoding are marked as binary.

Thursday, April 21, 2011

Page 55: Behind the Curtain

socket.read(4).encoding # => #<Encoding:ASCII-8BIT>

Thursday, April 21, 2011

Page 56: Behind the Curtain

Boundaries

To understand these bugs, we must find boundaries

Thursday, April 21, 2011

Page 57: Behind the Curtain

App Code

Thursday, April 21, 2011

Page 58: Behind the Curtain

App Code

Web Server

Database

NoSQL File System

SadnessBelt

Thursday, April 21, 2011

Page 59: Behind the Curtain

#<Encoding:ASCII-8BIT>is usually a bug!

Databases know the stored encoding

Post contains encoding / spec declares default

Thursday, April 21, 2011

Page 60: Behind the Curtain

# encoding: utf-8

name = 'たこ焼き仮面'

user = User.create!(:name => name)user.reloaduser.name + name

Thursday, April 21, 2011

Page 61: Behind the Curtain

# encoding: utf-8

name = 'たこ焼き仮面'

user = User.create!(:name => name)user.reloaduser.name + name

incompatible character encodings: ASCII-8BIT and UTF-8 (Encoding::CompatibilityError)

Thursday, April 21, 2011

Page 62: Behind the Curtain

>> name = "Aaron Patterson">> name.encoding=> #<Encoding:UTF-8>>> User.create!(:name => name)>> u = User.find :first>> u.name.encoding=> #<Encoding:ASCII-8BIT>

Thursday, April 21, 2011

Page 63: Behind the Curtain

>> name = "Aaron Patterson">> name.encoding=> #<Encoding:UTF-8>>> User.create!(:name => name)>> u = User.find :first>> u.name.encoding=> #<Encoding:ASCII-8BIT>

Thursday, April 21, 2011

Page 64: Behind the Curtain

name = 'aaron patterson'user = User.create!(:name => name)user.reloaduser.name + name

Ruby attempts to convert the binary for you

Thursday, April 21, 2011

Page 65: Behind the Curtain

name = 'aaron patterson'user = User.create!(:name => name)user.reloaduser.name + name

no error

Ruby attempts to convert the binary for you

Thursday, April 21, 2011

Page 66: Behind the Curtain

ASCII-8BIT + othershould raise

Thursday, April 21, 2011

Page 67: Behind the Curtain

ASCII-8BIT + othershould raise

Aaron'sOpinionCorner!

Thursday, April 21, 2011

Page 68: Behind the Curtain

Resources Know Encoding

Thursday, April 21, 2011

Page 69: Behind the Curtain

Prepared Statements

Thursday, April 21, 2011

Page 70: Behind the Curtain

SELECT * FROM "users" WHERE id = 1

Thursday, April 21, 2011

Page 71: Behind the Curtain

♥Parse the Query♥Formulate an Execution Plan♥Return results

Normal Queries

Thursday, April 21, 2011

Page 72: Behind the Curtain

Application

Database

Thursday, April 21, 2011

Page 73: Behind the Curtain

Application

Database

Result Set

Thursday, April 21, 2011

Page 74: Behind the Curtain

Application

Database

Thursday, April 21, 2011

Page 75: Behind the Curtain

Application

Database

query: 1234

Thursday, April 21, 2011

Page 76: Behind the Curtain

Application

Database

Thursday, April 21, 2011

Page 77: Behind the Curtain

Application

Database

Result Set

Thursday, April 21, 2011

Page 78: Behind the Curtain

♥Parse the Query♥Formulate an Execution Plan

Preparation

Thursday, April 21, 2011

Page 79: Behind the Curtain

Execution

♥Return Results

Thursday, April 21, 2011

Page 80: Behind the Curtain

Data transfer decreases

Thursday, April 21, 2011

Page 81: Behind the Curtain

Query Planner Improves

Thursday, April 21, 2011

Page 82: Behind the Curtain

Security

Thursday, April 21, 2011

Page 83: Behind the Curtain

SQLite3 Issues :'(

Talk about problem inserting rows with session data

Could insert session data, but could not find it

Thursday, April 21, 2011

Page 84: Behind the Curtain

INSERT INTO "sessions"("data", "session_id")VALUES (?, ?)

['data', 'BAh7BjoIZm9vSSIIYmF6BjoGRUY=']['session_id', 'dae0fb8a9c34e6980c9d0fae33a8fff6']

Thursday, April 21, 2011

Page 85: Behind the Curtain

db = SQLite3::Database.new ':memory:'db.trace { |sql| puts sql }

Thursday, April 21, 2011

Page 86: Behind the Curtain

INSERT INTO "sessions"("data", "session_id")

VALUES (

'BAh7BjoIZm9vSSIIYmF6BjoGRUY=',

x'3633303337623066376536613130343132343765623763626631616439303631')

Thursday, April 21, 2011

Page 87: Behind the Curtain

INSERT INTO "sessions"("data", "session_id")

VALUES (

'BAh7BjoIZm9vSSIIYmF6BjoGRUY=',

x'3633303337623066376536613130343132343765623763626631616439303631')

Thursday, April 21, 2011

Page 88: Behind the Curtain

db.trace { |sql| puts sql }

stmt = db.prepare('INSERT INTO "sessions" (session_id)VALUES (?)')

stmt.bind_param 1, 'fuu'stmt.execute

Made a sample program to reproduce the problem

Thursday, April 21, 2011

Page 89: Behind the Curtain

INSERT INTO "sessions"(session_id) VALUES ('fuu')

Resulting Query

Thursday, April 21, 2011

Page 90: Behind the Curtain

stmt.bind_param 1, 'fuu'.encode('BINARY')stmt.execute

Thursday, April 21, 2011

Page 91: Behind the Curtain

INSERT INTO "sessions" (session_id)VALUES (x'667575')

Thursday, April 21, 2011

Page 92: Behind the Curtain

INSERT INTO "sessions" (session_id)VALUES (x'667575')

Why did sqlite3 do this?

Talk about column affinity

Thursday, April 21, 2011

Page 93: Behind the Curtain

Why is it stored as Binary?

Thursday, April 21, 2011

Page 94: Behind the Curtain

Why is the session id tagged binary?

Thursday, April 21, 2011

Page 95: Behind the Curtain

>> x = OpenSSL::Random.random_bytes(10)=> "G\x93\xFC\xB2\xCE\xC0\xEC\xBB\xA7W">> x.encoding=> #<Encoding:ASCII-8BIT>

Thursday, April 21, 2011

Page 96: Behind the Curtain

>> y = x.unpack('H*')=> ["4793fcb2cec0ecbba757"]>> y.first.encoding=> #<Encoding:ASCII-8BIT>

Thursday, April 21, 2011

Page 97: Behind the Curtain

def generate_sid ActiveSupport::SecureRandom.hex(16)end

Thursday, April 21, 2011

Page 98: Behind the Curtain

def generate_sid sid = ActiveSupport::SecureRandom.hex(16) if sid.respond_to?(:encode!) sid.encode!('UTF-8') end sidend

Should hex have tagged it?

Should have generate_sid tagged it?

Thursday, April 21, 2011

Page 99: Behind the Curtain

Find the Source

Thursday, April 21, 2011

Page 100: Behind the Curtain

ARel and ActiveRecord

Thursday, April 21, 2011

Page 101: Behind the Curtain

Fear of SQL

Thursday, April 21, 2011

Page 102: Behind the Curtain

Why?

Thursday, April 21, 2011

Page 103: Behind the Curtain

We must learn SQL

But not specifically SQL

Working with sets

Can fetch correct data.

Thursday, April 21, 2011

Page 104: Behind the Curtain

We must learn to work with Sets

Thursday, April 21, 2011

Page 105: Behind the Curtain

Avoid SQL for:

Thursday, April 21, 2011

Page 106: Behind the Curtain

code reuse

Thursday, April 21, 2011

Page 107: Behind the Curtain

security

Thursday, April 21, 2011

Page 108: Behind the Curtain

independence

from databases

Thursday, April 21, 2011

Page 109: Behind the Curtain

What is ARel?

Thursday, April 21, 2011

Page 110: Behind the Curtain

AST

Represents SQL as a Tree

Represents the IDEA of a SQL statement

Thursday, April 21, 2011

Page 111: Behind the Curtain

SQL Compiler

Thursday, April 21, 2011

Page 112: Behind the Curtain

Representsan IDEA

Only until we invoke the compiler, does the AST become a query

Thursday, April 21, 2011

Page 113: Behind the Curtain

Relationship to ActiveRecord

Thursday, April 21, 2011

Page 114: Behind the Curtain

User.where('something')

Thursday, April 21, 2011

Page 115: Behind the Curtain

class User scope :heart where(:name => '<3')end

User.heart.select('name')

Thursday, April 21, 2011

Page 116: Behind the Curtain

ActiveRecord

Our Application

ARel Database

Thursday, April 21, 2011

Page 117: Behind the Curtain

ActiveRecord

Our Application

.to_a

ARel Database

Thursday, April 21, 2011

Page 118: Behind the Curtain

ActiveRecord

Our Application

ARel Database

to_sql

Thursday, April 21, 2011

Page 119: Behind the Curtain

ActiveRecord

Our Application

ARel Database

SELECT * ...

Thursday, April 21, 2011

Page 120: Behind the Curtain

ActiveRecord

Our Application

ARel Database

[{: name => '<3'}]

Thursday, April 21, 2011

Page 121: Behind the Curtain

ActiveRecord::Relation

Thursday, April 21, 2011

Page 122: Behind the Curtain

shortens to...

Thursday, April 21, 2011

Page 123: Behind the Curtain

ARel

Thursday, April 21, 2011

Page 124: Behind the Curtain

ARel != ARel

Thursday, April 21, 2011

Page 125: Behind the Curtain

:'(

Thursday, April 21, 2011

Page 126: Behind the Curtain

SQL Compiler

Future we can have optimizers, compiler cache, etc

Thursday, April 21, 2011

Page 127: Behind the Curtain

Interpreter?

Thursday, April 21, 2011

Page 128: Behind the Curtain

module Arel module Visitors class Mongo < Arel::Visitors::Visitor attr_reader :db

def initialize db; @db = db; end Query = Struct.new(:collection_name, :fields, :conditions)

private

def visit_Arel_Nodes_SelectStatement o o.cores.map { |c| visit_Arel_Nodes_SelectCore c }.map { |query| collection = db.collection query.collection_name fields = query.fields selector = Hash[query.conditions] opts = {} opts[:fields] = fields unless fields.empty? opts[:limit] = o.limit.expr.to_i if o.limit collection.find(selector, opts).to_a }.flatten end

def visit_Arel_Nodes_SelectCore o fields = o.projections.map { |proj| visit(proj) }.compact conditions = o.wheres.map { |condition| visit(condition) }.flatten(1) Query.new(visit(o.source), fields, conditions) end

def visit_Arel_Nodes_And o o.children.map { |child| visit child } end

def visit_Arel_Nodes_Equality o [visit(o.left), visit(o.right)] end

def visit_Arel_Attributes_Attribute o return if o.name == '*'; o.name end

def visit_Arel_Nodes_JoinSource o visit o.left end

def visit_Arel_Table o o.name end def literal o; o; end alias :visit_String :literal alias :visit_Fixnum :literal end endend

Thursday, April 21, 2011

Page 129: Behind the Curtain

module Arel module Visitors class Mongo < Arel::Visitors::Visitor attr_reader :db

def initialize db; @db = db; end Query = Struct.new(:collection_name, :fields, :conditions)

private

def visit_Arel_Nodes_SelectStatement o o.cores.map { |c| visit_Arel_Nodes_SelectCore c }.map { |query| collection = db.collection query.collection_name fields = query.fields selector = Hash[query.conditions] opts = {} opts[:fields] = fields unless fields.empty? opts[:limit] = o.limit.expr.to_i if o.limit collection.find(selector, opts).to_a }.flatten end

def visit_Arel_Nodes_SelectCore o fields = o.projections.map { |proj| visit(proj) }.compact conditions = o.wheres.map { |condition| visit(condition) }.flatten(1) Query.new(visit(o.source), fields, conditions) end

def visit_Arel_Nodes_And o o.children.map { |child| visit child } end

def visit_Arel_Nodes_Equality o [visit(o.left), visit(o.right)] end

def visit_Arel_Attributes_Attribute o return if o.name == '*'; o.name end

def visit_Arel_Nodes_JoinSource o visit o.left end

def visit_Arel_Table o o.name end def literal o; o; end alias :visit_String :literal alias :visit_Fixnum :literal end endend

Mongo Interpreter64 loc

Thursday, April 21, 2011

Page 130: Behind the Curtain

Thursday, April 21, 2011

Page 131: Behind the Curtain

gist.github.com/845782

Thursday, April 21, 2011

Page 132: Behind the Curtain

Custom DSL

Thursday, April 21, 2011

Page 133: Behind the Curtain

class Select < Struct.new(:columns) def self.* other other.select = new(Arel.sql('*')) other endend

Responds to *

Thursday, April 21, 2011

Page 134: Behind the Curtain

Responds to WHERE

class From < Struct.new(:table, :conditions) def WHERE conditions self.conditions = conditions Where.new(self) endend

Thursday, April 21, 2011

Page 135: Behind the Curtain

Responds to to_s

class Where < Struct.new(:from, :select) def to_s Arel::Table.engine = Arel::Sql::Engine.new( FakeRecord::Base.new) table = Arel::Table.new from.table table.project(select.columns).where( from.conditions.map { |k,v| table[k].eq v }).to_sql endend

Thursday, April 21, 2011

Page 136: Behind the Curtain

Bootstrap Methods

SELECT = Selectdef FROM table From.new tableend

Thursday, April 21, 2011

Page 137: Behind the Curtain

x = SELECT * FROM("users") .WHERE(:id => 10)puts x

Thursday, April 21, 2011

Page 138: Behind the Curtain

Thursday, April 21, 2011

Page 139: Behind the Curtain

Write SQL Rubyto avoid SQL

Thursday, April 21, 2011

Page 140: Behind the Curtain

Streaming

Talked about data from the back end, talk a bit about data to the front end

Thursday, April 21, 2011

Page 141: Behind the Curtain

Twitter Streaming API

Thursday, April 21, 2011

Page 142: Behind the Curtain

Chunked Responses

Needed for persistent connections

Thursday, April 21, 2011

Page 143: Behind the Curtain

Normal TimelineBuffers data, then sends a response

Thursday, April 21, 2011

Page 144: Behind the Curtain

Processing ERb

Normal TimelineBuffers data, then sends a response

Thursday, April 21, 2011

Page 145: Behind the Curtain

Processing ERb

Download Asset

Download Asset

Normal TimelineBuffers data, then sends a response

Thursday, April 21, 2011

Page 146: Behind the Curtain

Chunked TimelineSend data as soon as it is available

Thursday, April 21, 2011

Page 147: Behind the Curtain

Processing ERb

Chunked TimelineSend data as soon as it is available

Thursday, April 21, 2011

Page 148: Behind the Curtain

Processing ERb

Download Asset

Download Asset

Chunked TimelineSend data as soon as it is available

Thursday, April 21, 2011

Page 149: Behind the Curtain

Implementation

Thursday, April 21, 2011

Page 150: Behind the Curtain

Rack API

Thursday, April 21, 2011

Page 151: Behind the Curtain

status, headers, body = app.call(env)

Thursday, April 21, 2011

Page 152: Behind the Curtain

status.to_i

Thursday, April 21, 2011

Page 153: Behind the Curtain

headers.is_a?(Hash)

Thursday, April 21, 2011

Page 154: Behind the Curtain

body.responds_to?(:each)

Thursday, April 21, 2011

Page 155: Behind the Curtain

Inside Rack

body.each do |chunk| output(chunk)endbody.close rescue nil

Calls each,

possibly calls close

Thursday, April 21, 2011

Page 156: Behind the Curtain

Sample Applicationclass MyApp def call(env) # some computation body = 'hello' # more computation body << ' world'

[200, { 'X-Hello' => 'World' }, [body]] endend

Thursday, April 21, 2011

Page 157: Behind the Curtain

Delay work until eachclass FooBody def each yield "hello " sleep(10) # simulate work yield "world!" endend

Thursday, April 21, 2011

Page 158: Behind the Curtain

Problems ☹

Database connections were lost

Thursday, April 21, 2011

Page 159: Behind the Curtain

Middleware

Database connections are managed in middleware

Thursday, April 21, 2011

Page 160: Behind the Curtain

"There are two hard problems in CS: cache invalidation

and naming things"-- Phil Karlton

Thursday, April 21, 2011

Page 161: Behind the Curtain

class DbCache < Struct.new(:app) def call(env) # init cache status, headers, body = app.call(env) # clear cache [status, headers, body] endend

Thursday, April 21, 2011

Page 162: Behind the Curtain

class BodyProxy < Struct.new(:delegate) def each delegate.each { |x| yield x } end

def close delegate.close rescue nil # clear cache endend

Thursday, April 21, 2011

Page 163: Behind the Curtain

Update Middleware, Database Works! ☺

Thursday, April 21, 2011

Page 164: Behind the Curtain

But Query Cache was broken. ☹

Same problem in query cache as the database handle cache.

Thursday, April 21, 2011

Page 165: Behind the Curtain

Middleware is coupled to the callstack

Thursday, April 21, 2011

Page 166: Behind the Curtain

~ 24 middleware

Thursday, April 21, 2011

Page 167: Behind the Curtain

~ 5 required a proxy

Thursday, April 21, 2011

Page 168: Behind the Curtain

Types of Middleware

♥Generators♥Filters♥Lifecycle Handlers

Rack jams these to one API.

Approach seems naive when examining usage

Tied to callstack

Thursday, April 21, 2011

Page 169: Behind the Curtain

Solutions!

Thursday, April 21, 2011

Page 170: Behind the Curtain

Embrace The Differences!

Thursday, April 21, 2011

Page 171: Behind the Curtain

class Listener def created(event) end

def destroyed(event) endend

Lifecycle Listeners

Thursday, April 21, 2011

Page 172: Behind the Curtain

Generatorsclass Resource def service(request, response) 10.times { |i| response.body.write "hello #{i}" } response.body.close endend

Synchronous or Asynchronous

Return value is ignored

Thursday, April 21, 2011

Page 173: Behind the Curtain

Filters

class Filter def filter(request, response, chain) chain.filter(request, response) endend

Thursday, April 21, 2011

Page 174: Behind the Curtain

♥Synchronous

♥Asynchronous

♥Freedom from the Callstack

♥Thread Safety

Thursday, April 21, 2011

Page 175: Behind the Curtain

I stole this API.

Thursday, April 21, 2011

Page 176: Behind the Curtain

TEE HEE!!!!!

Thursday, April 21, 2011

Page 177: Behind the Curtain

fixing our tyrants

Thursday, April 21, 2011

Page 178: Behind the Curtain

Facebook + Github

Thursday, April 21, 2011

Page 179: Behind the Curtain

Get Involved

Thursday, April 21, 2011

Page 180: Behind the Curtain

Gain Trust

Only fix the immediate problem

Thursday, April 21, 2011

Page 181: Behind the Curtain

Then Persuade

Thursday, April 21, 2011

Page 182: Behind the Curtain

Thursday, April 21, 2011

Page 183: Behind the Curtain

Thursday, April 21, 2011

Page 184: Behind the Curtain

Questions?

Thursday, April 21, 2011