Ylan Segal

The REPL: Issue 54 - February 2019

Move fast and migrate things: how we automated migrations in Postgres

Vineet Gopal from Benchling writes an interesting post about their approach to running migrations on highly-contested databases in production. A new concept for me was that they automatically retry migrations that fail due to lock timeouts. This reduces the number of failed deployments and manual intervention steps.

Rescue from errors with a grace

In this post Paweł Dąbrowski shows how to leverage Ruby’s value equality (===) method, and overriding the default functionality in custom exceptions. The results is cleaner exception handling code.

Distributed Phoenix Chat with PubSub PG2 adapter

Alvise Susmel writes in detail how to use Phoenix Chat PubSub implementation using the pg2 library. The result a distributed, multi-node chat service that does not have an external dependency to a separate system (like Redis).

Dragons in Benchmark-ips

My go-to tool for analyzing ruby performance is benchmark-ips. It’s an enhancement to Ruby’s own stdlib tool Benchmark. It’s easy to use, and reports meaningful information by default.

Recently, while running a benchmark, I was getting very odd results. One of the alternatives reported thousand of times slower. That was not in line with my expectations. As a sanity test, I had both blocks run the same code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
require 'benchmark/ips'

Benchmark.ips do |x|
  x.report("option A") do
    Object.new
  end

  x.report("option B") do |times|
    Object.new
  end

  x.compare!
end

# >> Warming up --------------------------------------
# >>             option A   213.307k i/100ms
# >>             option B   257.225k i/100ms
# >> Calculating -------------------------------------
# >>             option A      5.958M (± 6.5%) i/s -     29.650M in   5.001835s
# >>             option B    254.000B (± 9.7%) i/s -    709.200B in   3.014349s
# >>
# >> Comparison:
# >>             option B: 254000471514.5 i/s
# >>             option A:  5958499.2 i/s - 42628.26x  slower
# >>

It took a few minutes, before I spotted the difference: The second block is making times available to the block, while the first is not. After setting up both blocks in the same way:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
require 'benchmark/ips'

Benchmark.ips do |x|
  x.report("option A") do
    Object.new
  end

  x.report("option B") do
    Object.new
  end

  x.compare!
end

# >> Warming up --------------------------------------
# >>             option A   227.941k i/100ms
# >>             option B   229.945k i/100ms
# >> Calculating -------------------------------------
# >>             option A      6.299M (± 2.3%) i/s -     31.684M in   5.032681s
# >>             option B      6.279M (± 3.3%) i/s -     31.502M in   5.023744s
# >>
# >> Comparison:
# >>             option A:  6299217.3 i/s
# >>             option B:  6278600.3 i/s - same-ish: difference falls within error
# >>

That is more like it: Identical code performs the same.

The REPL: Issue 53 - January 2019

Detecting Agile BS

I don’t know what I like more about this guide: The fact that it calls BS a lot of what is gospel for many in the software industry, the fact that it’s published by the Department of Defense, or the power-point-y graphics.

Distributed Transactions: The Icebergs of Microservices

In this article, Graham Lea explains many potential pitfalls with distributed transactions, and general advice on how to avoid them in the first place, or deal with them effectively when must.

The solution to distributed transactions in microservices is simply to avoid them like the plague.

Our Software Dependency Problem

Russ Cox writes about software dependencies, and goes into great detail of what dependencies are and what risks they bring into software projects. I found myself nodding in agreement throughout the post. The need to have a good policy towards updating project dependencies has been a pet-peeve of mine for a while.

Don't Rescue RuntimeError

I came across some code recently that attempted a long series of steps, and on failure issued a notification. Something functionally similar to:

1
2
3
4
5
def complicated_method
  # Several calls to things that can raise
rescue RuntimeError => ex
  notify_failure(ex)
end

At first instance, this seems appropriate. After all, Ruby defaults to raising a RuntimeError when not using a more specific exception:

1
2
3
4
5
6
7
def boom!
  raise "Hell"
rescue => ex
  ex
end

boom! # => #<RuntimeError: Hell>

There is a subtlety here: Ruby default to raising a RuntimeError, but defaults to rescuing a StandardError. A RuntimeError has a StandardError as an ancestor, which means that any RuntimeError will be rescued from a “naked” raise. However, when we specify RuntimeError as the rescue clause, we might miss a lot of exceptions that we thought we were rescuing, because they don’t have RuntimeError as an ancestor.

1
2
3
4
RuntimeError.ancestors # => [RuntimeError, StandardError, Exception, Object, Kernel, BasicObject]
ArgumentError.is_a?(RuntimeError) # => false
IOError.is_a?(RuntimeError) # => false
TypeError.is_a?(RuntimeError) # => false

Conclusion

When possible, always rescue specific exceptions, to avoid supressing exception. Failing that, rescue StandardError, not RuntimeError. Oh, and don’t rescue Exception.

The REPL: Issue 53 - December 2018

Scaling engineering organizations

Raylene Yung at Stripe writes a detailed post about how to scale engineering organizations. Although in my current role I am not a hiring manager, I find these types of posts very useful for future reference and to see what hiring looks like from the other side. Understanding the system allows you to use it for your benefit.

PostgresSQL: Implicit vs. explicit joins

Hans-Jürgen Schönig writes an excellent technical explanation of implicit vs. explicit joins in Postgres. It mostly jives with my experience: For the most part, the query planner will ensure that the performance is the same. When writing SQL directly, I prefer to use explicit joins. I believe they are more readable.