Upload
aaron-patterson
View
5.353
Download
1
Embed Size (px)
DESCRIPTION
My talk at red dirt ruby conf
Citation preview
Testing This
TESTING TESTING
TESTING TESTING
Thursday, April 21, 2011
ZOMG!
Thursday, April 21, 2011
GOOD MORNING!
Thursday, April 21, 2011
Aaron Patterson
Thursday, April 21, 2011
@tenderlove
Thursday, April 21, 2011
WWFMD?Thursday, April 21, 2011
ITWFMWD?Thursday, April 21, 2011
Thursday, April 21, 2011
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
Miami Vice Principal Señor Facebook
Integration Engineering Manager
Thursday, April 21, 2011
Elect
Thursday, April 21, 2011
12%
61%
24%
2%
Inspirational Entertaining Informative Wasted
Our Time Together
Thursday, April 21, 2011
Thursday, April 21, 2011
KEYNOTE
Thursday, April 21, 2011
10%
50%
20%
20%
Inspirational Entertaining Informative Wasted
Our Time Together
Thursday, April 21, 2011
Thursday, April 21, 2011
20% More Transitions
Thursday, April 21, 2011
Living Behindthe Curtain 6 subjects, less
than 10minutes on each of them
I enjoy dealing with behinds the scenes information
Thursday, April 21, 2011
DATABASE
APPLICATION
CLIENT
Thursday, April 21, 2011
DATABASE
APPLICATION
CLIENT
Thursday, April 21, 2011
DATABASE
APPLICATION
CLIENT
Thursday, April 21, 2011
DATABASE
APPLICATION
CLIENT
Thursday, April 21, 2011
Code as Data
Thursday, April 21, 2011
Politics
Thursday, April 21, 2011
"Open Sourceis a Democracy"
We don't vote on commits. It would be too inefficient.
Thursday, April 21, 2011
OSS is an Oligarchy
Thursday, April 21, 2011
Benevolent Dictator
Thursday, April 21, 2011
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
Thursday, April 21, 2011
Care and Feeding of Your Tyrant
Thursday, April 21, 2011
Cheetos
Thursday, April 21, 2011
Feed them Quality Bugs
Good reproduction steps
Thursday, April 21, 2011
Feed them Patches with Tests
Thursday, April 21, 2011
leave build systems
Gemspec,JewlerHoe,etc
Thursday, April 21, 2011
leave test frameworks
Thursday, April 21, 2011
Use +1's Carefully
Thursday, April 21, 2011
Thursday, April 21, 2011
Thursday, April 21, 2011
320 comments
Thursday, April 21, 2011
210 plus one
Thursday, April 21, 2011
Thursday, April 21, 2011
FFFFFFFUUUUUUUUUUUU
Thursday, April 21, 2011
Thursday, April 21, 2011
Superficial
Thursday, April 21, 2011
Merit
Thursday, April 21, 2011
Quality
Thursday, April 21, 2011
How to fix?
We'll discuss this in closing.
Thursday, April 21, 2011
Encodingsthe silent killer
Thursday, April 21, 2011
Never a miscommunication
Data goes in, Data goes out
Thursday, April 21, 2011
Until there is
Thursday, April 21, 2011
`encode': "\xE9" from ASCII-8BIT to UTF-8(Encoding::UndefinedConversionError)
Thursday, April 21, 2011
You can't explain that
Encoding Error?
Thursday, April 21, 2011
String Tagging
Thursday, April 21, 2011
"hello! <3".encoding # => #<Encoding:UTF-8>
Strings with unknown encoding are marked as binary.
Thursday, April 21, 2011
socket.read(4).encoding # => #<Encoding:ASCII-8BIT>
Thursday, April 21, 2011
Boundaries
To understand these bugs, we must find boundaries
Thursday, April 21, 2011
App Code
Thursday, April 21, 2011
App Code
Web Server
Database
NoSQL File System
SadnessBelt
Thursday, April 21, 2011
#<Encoding:ASCII-8BIT>is usually a bug!
Databases know the stored encoding
Post contains encoding / spec declares default
Thursday, April 21, 2011
# encoding: utf-8
name = 'たこ焼き仮面'
user = User.create!(:name => name)user.reloaduser.name + name
Thursday, April 21, 2011
# 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
>> 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
>> 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
name = 'aaron patterson'user = User.create!(:name => name)user.reloaduser.name + name
Ruby attempts to convert the binary for you
Thursday, April 21, 2011
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
ASCII-8BIT + othershould raise
Thursday, April 21, 2011
ASCII-8BIT + othershould raise
Aaron'sOpinionCorner!
Thursday, April 21, 2011
Resources Know Encoding
Thursday, April 21, 2011
Prepared Statements
Thursday, April 21, 2011
SELECT * FROM "users" WHERE id = 1
Thursday, April 21, 2011
♥Parse the Query♥Formulate an Execution Plan♥Return results
Normal Queries
Thursday, April 21, 2011
Application
Database
Thursday, April 21, 2011
Application
Database
Result Set
Thursday, April 21, 2011
Application
Database
Thursday, April 21, 2011
Application
Database
query: 1234
Thursday, April 21, 2011
Application
Database
Thursday, April 21, 2011
Application
Database
Result Set
Thursday, April 21, 2011
♥Parse the Query♥Formulate an Execution Plan
Preparation
Thursday, April 21, 2011
Execution
♥Return Results
Thursday, April 21, 2011
Data transfer decreases
Thursday, April 21, 2011
Query Planner Improves
Thursday, April 21, 2011
Security
Thursday, April 21, 2011
SQLite3 Issues :'(
Talk about problem inserting rows with session data
Could insert session data, but could not find it
Thursday, April 21, 2011
INSERT INTO "sessions"("data", "session_id")VALUES (?, ?)
['data', 'BAh7BjoIZm9vSSIIYmF6BjoGRUY=']['session_id', 'dae0fb8a9c34e6980c9d0fae33a8fff6']
Thursday, April 21, 2011
db = SQLite3::Database.new ':memory:'db.trace { |sql| puts sql }
Thursday, April 21, 2011
INSERT INTO "sessions"("data", "session_id")
VALUES (
'BAh7BjoIZm9vSSIIYmF6BjoGRUY=',
x'3633303337623066376536613130343132343765623763626631616439303631')
Thursday, April 21, 2011
INSERT INTO "sessions"("data", "session_id")
VALUES (
'BAh7BjoIZm9vSSIIYmF6BjoGRUY=',
x'3633303337623066376536613130343132343765623763626631616439303631')
Thursday, April 21, 2011
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
INSERT INTO "sessions"(session_id) VALUES ('fuu')
Resulting Query
Thursday, April 21, 2011
stmt.bind_param 1, 'fuu'.encode('BINARY')stmt.execute
Thursday, April 21, 2011
INSERT INTO "sessions" (session_id)VALUES (x'667575')
Thursday, April 21, 2011
INSERT INTO "sessions" (session_id)VALUES (x'667575')
Why did sqlite3 do this?
Talk about column affinity
Thursday, April 21, 2011
Why is it stored as Binary?
Thursday, April 21, 2011
Why is the session id tagged binary?
Thursday, April 21, 2011
>> x = OpenSSL::Random.random_bytes(10)=> "G\x93\xFC\xB2\xCE\xC0\xEC\xBB\xA7W">> x.encoding=> #<Encoding:ASCII-8BIT>
Thursday, April 21, 2011
>> y = x.unpack('H*')=> ["4793fcb2cec0ecbba757"]>> y.first.encoding=> #<Encoding:ASCII-8BIT>
Thursday, April 21, 2011
def generate_sid ActiveSupport::SecureRandom.hex(16)end
Thursday, April 21, 2011
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
Find the Source
Thursday, April 21, 2011
ARel and ActiveRecord
Thursday, April 21, 2011
Fear of SQL
Thursday, April 21, 2011
Why?
Thursday, April 21, 2011
We must learn SQL
But not specifically SQL
Working with sets
Can fetch correct data.
Thursday, April 21, 2011
We must learn to work with Sets
Thursday, April 21, 2011
Avoid SQL for:
Thursday, April 21, 2011
code reuse
Thursday, April 21, 2011
security
Thursday, April 21, 2011
independence
from databases
Thursday, April 21, 2011
What is ARel?
Thursday, April 21, 2011
AST
Represents SQL as a Tree
Represents the IDEA of a SQL statement
Thursday, April 21, 2011
SQL Compiler
Thursday, April 21, 2011
Representsan IDEA
Only until we invoke the compiler, does the AST become a query
Thursday, April 21, 2011
Relationship to ActiveRecord
Thursday, April 21, 2011
User.where('something')
Thursday, April 21, 2011
class User scope :heart where(:name => '<3')end
User.heart.select('name')
Thursday, April 21, 2011
ActiveRecord
Our Application
ARel Database
Thursday, April 21, 2011
ActiveRecord
Our Application
.to_a
ARel Database
Thursday, April 21, 2011
ActiveRecord
Our Application
ARel Database
to_sql
Thursday, April 21, 2011
ActiveRecord
Our Application
ARel Database
SELECT * ...
Thursday, April 21, 2011
ActiveRecord
Our Application
ARel Database
[{: name => '<3'}]
Thursday, April 21, 2011
ActiveRecord::Relation
Thursday, April 21, 2011
shortens to...
Thursday, April 21, 2011
ARel
Thursday, April 21, 2011
ARel != ARel
Thursday, April 21, 2011
:'(
Thursday, April 21, 2011
SQL Compiler
Future we can have optimizers, compiler cache, etc
Thursday, April 21, 2011
Interpreter?
Thursday, April 21, 2011
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
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
Thursday, April 21, 2011
gist.github.com/845782
Thursday, April 21, 2011
Custom DSL
Thursday, April 21, 2011
class Select < Struct.new(:columns) def self.* other other.select = new(Arel.sql('*')) other endend
Responds to *
Thursday, April 21, 2011
Responds to WHERE
class From < Struct.new(:table, :conditions) def WHERE conditions self.conditions = conditions Where.new(self) endend
Thursday, April 21, 2011
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
Bootstrap Methods
SELECT = Selectdef FROM table From.new tableend
Thursday, April 21, 2011
x = SELECT * FROM("users") .WHERE(:id => 10)puts x
Thursday, April 21, 2011
Thursday, April 21, 2011
Write SQL Rubyto avoid SQL
Thursday, April 21, 2011
Streaming
Talked about data from the back end, talk a bit about data to the front end
Thursday, April 21, 2011
Twitter Streaming API
Thursday, April 21, 2011
Chunked Responses
Needed for persistent connections
Thursday, April 21, 2011
Normal TimelineBuffers data, then sends a response
Thursday, April 21, 2011
Processing ERb
Normal TimelineBuffers data, then sends a response
Thursday, April 21, 2011
Processing ERb
Download Asset
Download Asset
Normal TimelineBuffers data, then sends a response
Thursday, April 21, 2011
Chunked TimelineSend data as soon as it is available
Thursday, April 21, 2011
Processing ERb
Chunked TimelineSend data as soon as it is available
Thursday, April 21, 2011
Processing ERb
Download Asset
Download Asset
Chunked TimelineSend data as soon as it is available
Thursday, April 21, 2011
Implementation
Thursday, April 21, 2011
Rack API
Thursday, April 21, 2011
status, headers, body = app.call(env)
Thursday, April 21, 2011
status.to_i
Thursday, April 21, 2011
headers.is_a?(Hash)
Thursday, April 21, 2011
body.responds_to?(:each)
Thursday, April 21, 2011
Inside Rack
body.each do |chunk| output(chunk)endbody.close rescue nil
Calls each,
possibly calls close
Thursday, April 21, 2011
Sample Applicationclass MyApp def call(env) # some computation body = 'hello' # more computation body << ' world'
[200, { 'X-Hello' => 'World' }, [body]] endend
Thursday, April 21, 2011
Delay work until eachclass FooBody def each yield "hello " sleep(10) # simulate work yield "world!" endend
Thursday, April 21, 2011
Problems ☹
Database connections were lost
Thursday, April 21, 2011
Middleware
Database connections are managed in middleware
Thursday, April 21, 2011
"There are two hard problems in CS: cache invalidation
and naming things"-- Phil Karlton
Thursday, April 21, 2011
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
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
Update Middleware, Database Works! ☺
Thursday, April 21, 2011
But Query Cache was broken. ☹
Same problem in query cache as the database handle cache.
Thursday, April 21, 2011
Middleware is coupled to the callstack
Thursday, April 21, 2011
~ 24 middleware
Thursday, April 21, 2011
~ 5 required a proxy
Thursday, April 21, 2011
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
Solutions!
Thursday, April 21, 2011
Embrace The Differences!
Thursday, April 21, 2011
class Listener def created(event) end
def destroyed(event) endend
Lifecycle Listeners
Thursday, April 21, 2011
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
Filters
class Filter def filter(request, response, chain) chain.filter(request, response) endend
Thursday, April 21, 2011
♥Synchronous
♥Asynchronous
♥Freedom from the Callstack
♥Thread Safety
Thursday, April 21, 2011
I stole this API.
Thursday, April 21, 2011
TEE HEE!!!!!
Thursday, April 21, 2011
fixing our tyrants
Thursday, April 21, 2011
Facebook + Github
Thursday, April 21, 2011
Get Involved
Thursday, April 21, 2011
Gain Trust
Only fix the immediate problem
Thursday, April 21, 2011
Then Persuade
Thursday, April 21, 2011
Thursday, April 21, 2011
Thursday, April 21, 2011
Questions?
Thursday, April 21, 2011