Quipper
● Joined in 2018-09
○ 7 months so far
■ 4 months at School Team (*)
■ 1 month at Unemployed (*)
■ 2 months at Data Restructuring Team
Slide 10
Slide 10 text
Almost microservices
ujihisa
9/92
Slide 11
Slide 11 text
"雑" Almost microservices / @ujihisa
● Release is important
○ Grind it to small pieces for scalability
● Human makes mistakes
● Feature toggles pattern is powerful
○ Quipper calls it Darklaunch
○ Provide strong support to Darklaunch
● Apply Circuit Breaker pattern and
Canary release if necessary
● Pragmatic Microservices' essence
Slide 12
Slide 12 text
Web app development
● Research & decide what to make
● rails new
● infrastructure setup
● git, code review, CI setup
● design models, views, and controllers
● development! of course unit tests too!
● refactor
● release & debug
Slide 13
Slide 13 text
Web app development
● Research & decide what to make
● rails new
● infrastructure setup
● git, code review, CI setup
● design models, views, and controllers
● development! of course unit tests too!
● refactor
● & debug
Release
Slide 14
Slide 14 text
Software
release
Release gives development meaning
Slide 15
Slide 15 text
What is software release?
俺 サーバ
Slide 16
Slide 16 text
What is software release?
俺 サーバ おまえら
Slide 17
Slide 17 text
Release
● Add a new feature and make it available to users
● Modify existing features
● Delete existing features
● Add/modify/delete internal implementation
(or Refactor)
Slide 18
Slide 18 text
"Release" typical properties
● Release = Code deploy && daemon restart
○ Not atomic
● Code deploy size = n commits
○ n depends on
■ num devs
■ deploy frequency
● Quipper:
○ 636 commits, 155 PRs, 42 devs, once a week
Slide 19
Slide 19 text
Release difficulties
● Edge case logical bugs
○ Works for most users
○ Breaks for certain users
○
● Unexpected untested changes
○ Somewhere far can get changed
○
● Runtime env specific issues
Slide 20
Slide 20 text
Release difficulties
● Edge case logical bugs
○ Works for most users
○ Breaks for certain users
○ More tests! ?
● Unexpected untested changes
○ Somewhere far can get changed
○ More tests! ?
● Runtime env specific issues
Slide 21
Slide 21 text
Testing is hard
I even make a mistake
exactly both at the implementation and the test code
Slide 22
Slide 22 text
from https://speakerdeck.com/sinamon129/sisutemuzhang-hai-tofalsexiang-kihe-ifang-at-sinamon129-number-tokyogirlsrb
TokyoGirls.rb Meetup
Slide 23
Slide 23 text
Tests cost
● Cost of a test case
○ Write
○ Run
○ Maintain
● Benefit of a test case
○ Can it prevent new bugs?
○ Can it explain the behaviour to us well?
● Good test = low cost * high benefit
Slide 24
Slide 24 text
Do I write tests?
● Yes!
● All model public methods
○ Sometimes even for private methods only if it
makes sense
○ If it's too trivial I sometimes skip
● All controller actions
○ Without very edgy cases
● Not really for others
Caution: This page can be
controversial
Slide 25
Slide 25 text
Safe Release
安心してリリースしたい
Slide 26
Slide 26 text
Solutions?
● Integration test
○ Headless browser
○ Browser
● Static code analysis
● Dynamic analysis
● Performance benchmarking/simulation
● Test manually
● Pay somebody to test manually
Slide 27
Slide 27 text
Solutions?
● Integration test
○ Headless browser
○ Browser
● Static code analysis
● Dynamic analysis
● Performance benchmarking/simulation
● Test manually
● Pay somebody to test manually
→ Weekly release
Slide 28
Slide 28 text
Quipper's solution (1): make manual test trivial
● Pull request code review
● CI deploys your branch into a dedicated cluster
○ Reviewer can actually try the web app
○ Masked production data
リ
Slide 29
Slide 29 text
Quipper's solution (2): Outsource human tests
● Test all the features every week
○ Product Managers make happy path scenario,
and pass it to QA company
Slide 30
Slide 30 text
Remaining problems
● Weekly release with giant changes
○ How to detect which commit broke?
● Long life branches
○ Merge conflicts
○ Hard to co-work
● Nontrivial to rollback specific one
○ Hotfix to master branch, merge back to develop
branch
27/92
Release gradually
● Big bang release (0% -> 100%)
● Split features smaller, and release each
(0% -> 20% -> 30% -> ... -> 90% -> 100%)
● Release to certain people first, and expose to
more people gradually
(0% -> 1% -> ... -> 99% -> 100%)
○ Can be randomized
○ Can be manually specified
Slide 34
Slide 34 text
Release gradually
● Big bang release (0% -> 100%)
● Split features smaller, and release each
(0% -> 20% -> 30% -> ... -> 90% -> 100%)
● Release to certain people first, and expose to more
people gradually
(0% -> 1% -> ... -> 99% -> 100%)
○ Can be randomized
○ Can be manually specified
MVP
Minimum Viable Product
Slide 35
Slide 35 text
Release gradually
● Big bang release (0% -> 100%)
● Split features smaller, and release each of them
(0% -> 20% -> 30% -> ... -> 90% -> 100%)
● Release to certain people first, and expose to more
people gradually
(0% -> 1% -> ... -> 99% -> 100%)
○ Can be randomized
○ Can be manually specified
MVP
Minimum Viable Product
https://blog.deming.org/2014/11/minimal-viable-product/
Slide 36
Slide 36 text
No content
Slide 37
Slide 37 text
No content
Slide 38
Slide 38 text
No content
Slide 39
Slide 39 text
No content
Slide 40
Slide 40 text
No content
Slide 41
Slide 41 text
in production
Iterate and learn
41/93
Slide 42
Slide 42 text
Iterate and
learn
in production
https://www.youtube.com/w
atch?v=75wa8Lx4yc4
Slide 43
Slide 43 text
Release gradually
● Big bang release (0% -> 100%)
● Split features smaller, and release each of them
(0% -> 20% -> 30% -> ... -> 90% -> 100%)
● Release to certain people first, and expose to more people
gradually
(0% -> 1% -> ... -> 99% -> 100%)
○ Can be randomized
○ Can be manually specified
43/93
Slide 44
Slide 44 text
Release to certain people
# dashboard_controller.rb
before_action :authenticate
def index
@user = current_user
render :index
end
Slide 45
Slide 45 text
Release to certain people
# dashboard_controller.rb
before_action :authenticate
def index
@user = current_user
if [234, 345, 456].include?(@user.id)
render :the_new_fancy_index
else
render :index
end
end
Slide 46
Slide 46 text
Release to certain people
# dashboard_controller.rb
before_action :authenticate
def index
@user = current_user
if ['[email protected]', '[email protected]'].include?(@user.email)
render :the_new_fancy_index
else
render :index
end
end
Slide 47
Slide 47 text
Release to certain people dynamically
# dashboard_controller.rb
before_action :authenticate
def index
@user = current_user
if UsersToShow.exists?(key: 'fancy-dashboard', email: @user.email)
render :the_new_fancy_index
else
render :index
end
end
Run this manually:
UsersToShow.create!(key: 'fancy-dashboard', email: '[email protected]')
Slide 48
Slide 48 text
Release to some people (percentage)
def index
@user = current_user
if @user.id % 100 < 31
render :the_new_fancy_index
else
render :index
end
end
Slide 49
Slide 49 text
Release to some people (percentage) dynamically
def index
@user = current_user
percentage = PercentageToShow.find(key: 'fancy-dashboard')&.value
if @user.id % 100 < (percentage || 0)
render :the_new_fancy_index
else
render :index
end
end
Run this manually
PercentageToShow.create!(key: 'fancy-dashboard', value: 31)
Slide 50
Slide 50 text
Dynamic tweaking
● More powerful than it sounds like
● No code change to release
● No code change to rollback
● Any time to release
○ Not at the same time to other commits
● No need to have perfect tests for
unreachable code
Slide 51
Slide 51 text
Digital (Big bang) release
Slide 52
Slide 52 text
Gradual release (feature)
Slide 53
Slide 53 text
Gradual release (user)
Slide 54
Slide 54 text
No content
Slide 55
Slide 55 text
Gradual release (user*feature)
Slide 56
Slide 56 text
No content
Slide 57
Slide 57 text
No content
Slide 58
Slide 58 text
New problems
● Gradual release adds complexity
○ Manage both old/new code
○ Behaviour depends on each users
○ More often to rollback
○ Tend to forget to release
● Hard to hold both old/new code for:
○ Application-global changes
○ Infrastructure-layer changes
58/93
Slide 59
Slide 59 text
P. Mange both old/new code
● S. Embrace it
● Make it pattern for ease to read
def index
@user = current_user
percentage = PercentageToShow.find(key: 'fancy-dashboard')&.value
if @user.id.hash % 100 < (percentage || 0)
render :the_new_fancy_index
else
render :index
end
end
Slide 60
Slide 60 text
P. Mange both old/new code
● S. Embrace it
● Make it pattern for ease to read
def index
@user = current_user
if FeatureToggles.variation('enable-fancy-dashboard', @user)
render :the_new_fancy_index
else
render :index
end
end
Slide 61
Slide 61 text
P. Mange both old/new code
def index
@user = current_user
if FeatureToggles.variation('enable-fancy-dashboard', @user)
render :the_new_fancy_index
else
render :index
end
end
● "Feature toggles" There's a name for it
○ https://martinfowler.com/articles/feature-toggles
.html
○ "modify system without changing code"
Slide 62
Slide 62 text
Darklaunch
Feature Toggles
Slide 63
Slide 63 text
P. Mange both old/new code
def index
@user = current_user
if Darklaunch.variation('enable-fancy-dashboard', @user)
render :the_new_fancy_index
else
render :index
end
end
● Quipper uses the word "Darklaunch"
○ potential confusion with "Feature flags"
we already have
Slide 64
Slide 64 text
P. Mange both old/new code
def index
@user = current_user
if Darklaunch.variation('enable-fancy-dashboard', @user)
render :the_new_fancy_index
else
render :index
end
end
● Quipper's Darklaunch stores into MongoDB
○ Most models are MongoMapper
○ (Darklaunch was on Redis at first)
Slide 65
Slide 65 text
No content
Slide 66
Slide 66 text
def index
@user = current_user
if Darklaunch.variation('enable-fancy-dashboard', {id: @user.id, …})
render :the_new_fancy_index
else
render :index
end
end
● returns true only for new experimental features
○ true = new (dangerous), false = old (safe)
Slide 67
Slide 67 text
def index
@user = current_user
if Darklaunch.variation('enable-fancy-dashboard', {id: @user.id, …})
render :the_new_fancy_index
else
render :index
end
end
● Pass user as a PORO
○ Let darklaunch not to know about business
logic
Slide 68
Slide 68 text
def index
@user = current_user
if Darklaunch.v1_variation('enable-fancy-dashboard', {id: @user.id})
render :the_new_fancy_index
else
render :index
end
end
● Darklaunch can't darklaunch itself
○ Never change the interface
Slide 69
Slide 69 text
P. More often to rollback
● Hypothesis:
more often to release = more often to rollback
● What's rollback
○ 0. an issue happens
○ 1. find where it happens
○ 2. change app behave as previous
○ 3. release the change
○ (4. investigate why and solve it)
Slide 70
Slide 70 text
● S. make it easy to darklaunch/undarklaunch
P. More often to rollback
Slide 71
Slide 71 text
P. More often to rollback
● Back then
○ revert commit
○ deploy (= release)
def index
@user = current_user
render :the_new_fancy_index # boom! RuntimeError!
end
↓ rollback
def index
@user = current_user
render :index
Slide 72
Slide 72 text
P. More often to rollback
def index
@user = current_user
if Darklaunch.v1_variation('enable-fancy-dashboard', {id: @user.id})
render :the_new_fancy_index # boom! RuntimeError!
else
render :index
end
end
● Typical task:
○ (User reports a bug)
○ An exception at the new code
○ Undarklaunch the key
Slide 73
Slide 73 text
P. More often to rollback
def index
@user = current_user
if Darklaunch.v1_variation('enable-fancy-dashboard', {id: @user.id})
begin
render :the_new_fancy_index # boom! RuntimeError!
rescue => e
...
Darklaunch.remove('enable-fancy-dashboard')
● Typical task:
○ (User reports a bug)
○ An exception at the new code
○ Undarklaunch the key
Circuit Breaker
A pattern to prevent catastrophic cascade
https://martinfowler.com/bliki/CircuitBreaker.html
Slide 76
Slide 76 text
Circuit Breaker (Martin Fowler)
● Very common
● Stateful gateway to
external resources
● Toggle between
"Open", "Half Open",
and "Closed"
● Requests timeout
immediately in "Closed"
Slide 77
Slide 77 text
Can be used also to
toggle behaviour
That's what I showed with Darklaunch
Slide 78
Slide 78 text
Special thanks to Hootsuite
● https://hootsuite.com
● PHP Monolith + Scala Microservices
● Darklaunch
○ Consul
● Circuit breaker
○ https://github.com/hootsuite/scala-circuit-breake
r
Slide 79
Slide 79 text
Re: New problems
● Gradual release adds complexity
○ Manage both old/new code
○ Behaviour depends on each users
○ More often to rollback
○ Tend to forget to release
● Hard to hold both old/new code for:
○ Application-global changes
○ Infrastructure-layer changes
Slide 80
Slide 80 text
Re: New problems
● Gradual release adds complexity
○ Manage both old/new code
○ Behaviour depends on each users
○ More often to rollback
○ Tend to forget to release
● Hard to hold both old/new code for:
○ Application-global changes
○ Infrastructure-layer changes
Solved!
Darklaunch module
Darklaunch dashboard (*)
Circuit breaker
Darklaunch stats (*)
Canary release
Canary release
Microservices
An application with collection of "services"
81/93
Slide 83
Slide 83 text
Microservices
Pros
● Team develops/maintains a subset of an app
○ ownership!
● Easy to make a big change
○ totally independent releases
■ frequent small releases =
trivial to debug production issues
● no need to know details of other services
Slide 84
Slide 84 text
Microservices
Cons
● Distributed
○ "Networks are unreliable in the worst possible
way" ― 7 deadly sin
● Requires well-defined service boundaries
○ is it possible?
● Hard to change interface
● Well, need to know details of both services
Slide 85
Slide 85 text
Distributed Monolith and Microservices
● Monolith
○ Not scalable with many devs
○ Different concerns in same code
● Distributed Monolith
○ Monolith + Network partitioning
● Microservices
○ Easier to deploy, and make runtime-wide changes
○ Hard to make microservices-wide changes
Slide 86
Slide 86 text
Distributed Monolith and Microservices
● Monolith
○ Not scalable with many devs
○ Different concerns in same code
● Distributed Monolith
○ Monolith + Partitioning
● Microservices
○ Easier to deploy, and make runtime-wide
changes
○ Hard to make microservices-wide changes
Slide 87
Slide 87 text
Monolith + Feature toggles
● Different concerns in same code
○ Modules/classes/methods
● Easier to deploy
● Manageable to make app-global changes
● No network partitioning
Slide 88
Slide 88 text
Monolith + Feature toggles
● Different concerns in same code
○ Modules/classes/methods
● Easier to deploy
● Manageable to make app-global changes
● No network partitioning
雑
"Almost microservices"
Tatsuhiro Ujihisa
RailsDM 2019
2019-03-22
ujihisa this year
● 2019-03
○ RailsDM 2019
"雑" Almost Microservices
● 2019-04
○ RubyKaigi 2019
"Play with local vars"
● 2019-05+
○ Move back to Vancouver, BC, Canada
90/93
"雑" Almost microservices / @ujihisa
● Release is important
○ Grind it to small pieces for scalability
● Human makes mistakes
● Feature toggles pattern is powerful
○ Quipper calls it Darklaunch
○ Provide strong support to Darklaunch
● Apply
(somewhat)
Circuit Breaker pattern and Canary
release if necessary
● See you at the RubyKaigi 2019! 93/93