Slide 1

Slide 1 text

Circuit Breakers em Ruby

Slide 2

Slide 2 text

@lucasmazza http://afterhours.io/

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

No content

Slide 5

Slide 5 text

https://sp.femug.com https://github.com/femug/femug

Slide 6

Slide 6 text

Circuit Breakers em Ruby

Slide 7

Slide 7 text

No content

Slide 8

Slide 8 text

No content

Slide 9

Slide 9 text

No content

Slide 10

Slide 10 text

No content

Slide 11

Slide 11 text

Slide 12

Slide 12 text

No content

Slide 13

Slide 13 text

No content

Slide 14

Slide 14 text

rescue_from StandardError do render text: 'oops' end

Slide 15

Slide 15 text

rescue_from StandardError do render text: 'oops' end ❓❓❓❓❓❓❓❓❓ ❓❓❓❓❓❓❓❓❓

Slide 16

Slide 16 text

“You wrap a protected function call in a circuit breaker object, which monitors for failures.” http://martinfowler.com/bliki/CircuitBreaker.html

Slide 17

Slide 17 text

https://pragprog.com/book/mnee/release-it

Slide 18

Slide 18 text

No content

Slide 19

Slide 19 text

No content

Slide 20

Slide 20 text

No content

Slide 21

Slide 21 text

No content

Slide 22

Slide 22 text

No content

Slide 23

Slide 23 text

No content

Slide 24

Slide 24 text

No content

Slide 25

Slide 25 text

No content

Slide 26

Slide 26 text

Estado + transições

Slide 27

Slide 27 text

✓ Timeouts

Slide 28

Slide 28 text

✓ Timeouts ✓ Downtimes

Slide 29

Slide 29 text

✓ Timeouts ✓ Downtimes ✓ Picos de erros

Slide 30

Slide 30 text

✓ Timeouts ✓ Downtimes ✓ Picos de erros ✓ Má configuração

Slide 31

Slide 31 text

★ Limite de falhas

Slide 32

Slide 32 text

★ Limite de falhas ★ Tempo para reset

Slide 33

Slide 33 text

★ Limite de falhas ★ Tempo para reset ★ Erros a ignorar

Slide 34

Slide 34 text

No content

Slide 35

Slide 35 text

❓API pública ❓Gestão do estado ❓Concorrência

Slide 36

Slide 36 text

jnunemaker/resilient

Slide 37

Slide 37 text

require 'resilient/circuit_breaker' circuit_breaker = Resilient::CircuitBreaker.get('acme-co-api') if circuit_breaker.allow_request? begin # API Call circuit_breaker.success rescue => boom circuit_breaker.failure # do fallback end else # do fallback end

Slide 38

Slide 38 text

circuit_breaker = Resilient::CircuitBreaker.get('example', { # at what percentage of errors should we open the circuit error_threshold_percentage: 50, # do not try request again for 5 seconds sleep_window_seconds: 5, # do not open circuit until at least 5 requests have happened request_volume_threshold: 5, }) # etc etc etc

Slide 39

Slide 39 text

✅ Boa API pública ⚠ Não é distribuído ⚠ Não é concorrente

Slide 40

Slide 40 text

✅ Instrumentação & Métricas

Slide 41

Slide 41 text

shopify/semian

Slide 42

Slide 42 text

gem 'semian', require: %w(semian semian/redis) def fetch_user User.find(session[:user_id]) rescue Redis::CannotConnectError nil end

Slide 43

Slide 43 text

Semian.register(:mysql_shard0, timeout: 0.5, error_threshold: 3, error_timeout: 10) Semian[:mysql_shard0].acquire do # Perform a MySQL query here end

Slide 44

Slide 44 text

“Semian is not a trivial library to understand, introduces complexity and thus should be introduced with care.” https://github.com/shopify/semian#do-i-need-semian

Slide 45

Slide 45 text

“It is paramount that you understand Semian before including it in production as you may otherwise be surprised by its behaviour.” https://github.com/shopify/semian#do-i-need-semian

Slide 46

Slide 46 text

⚠ Monkeypatch ⚠ estado por host ✅ Concorrente

Slide 47

Slide 47 text

✅ Suporte a instrumentação ✅ Battle tested

Slide 48

Slide 48 text

orgsync/stoplight

Slide 49

Slide 49 text

light = Stoplight('acme-co-api') { do_api_call } .with_fallback { do_fallback } .with_threshold(3) # => Stoplight::Light light.color #=> 'green', 'yellow' ou 'red' light.run

Slide 50

Slide 50 text

require 'redis' redis = Redis.new data_store = Stoplight::DataStore::Redis.new(redis) Stoplight::Light.default_data_store = data_store slack = Slack::Notifier.new('http://www.example.com/webhook-url') notifier = Stoplight::Notifier::Slack.new(slack) Stoplight::Light.default_notifiers += [notifier] notifier = Stoplight::Notifier::Logger.new(Rails.logger) Stoplight::Light.default_notifiers += [notifier]

Slide 51

Slide 51 text

⚠ API não idiomática ✅ Distribuído ✅ Concorrente

Slide 52

Slide 52 text

✅ Implementação bem legível

Slide 53

Slide 53 text

netflix/hystrix

Slide 54

Slide 54 text

No content

Slide 55

Slide 55 text

orgsync/stoplight

Slide 56

Slide 56 text

class CircuitBreaker def initialize(client, name) @client = client @name = name end def method_missing(method, *args, &block) Stoplight(@name) { @client.public_send(method, *args, &block) }.run end end

Slide 57

Slide 57 text

gh_client = GitHub::Client.new('acme-co-oauth2-token') client = CircuitBreaker.new(gh_client, 'client-acme-co') # Requests feito dentro do Circuit Breaker client.repo('plataformatec/devise') client.repo('plataformatec/simple_form') client.repo('plataformatec/faraday-http-cache')

Slide 58

Slide 58 text

Pattern + Gem Você!

Slide 59

Slide 59 text

❓Monitoramento

Slide 60

Slide 60 text

Métricas

Slide 61

Slide 61 text

StatsD Librato NewRelic AppSignal …

Slide 62

Slide 62 text

Notificações

Slide 63

Slide 63 text

Notificações

Slide 64

Slide 64 text

Reset manual

Slide 65

Slide 65 text

❓Fallback vs raise

Slide 66

Slide 66 text

Escrita vs leitura sync vs async

Slide 67

Slide 67 text

Executar uma operação em background Retry + backoff

Slide 68

Slide 68 text

Ler dados de uma API Fallback e/ou cache

Slide 69

Slide 69 text

No content

Slide 70

Slide 70 text

Obrigado! @lucasmazza