fly-ruby 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 82d6cde56b619737984124f61e7fc526129eebb493e1954f9b9b171202d8eed0
4
- data.tar.gz: 2a14fee50d07739be04a19476128671878fb448fbe1cf23654df254beba4507a
3
+ metadata.gz: 59f0a5a6d3bbfc27e9440a2a96f230b662d42628c0d520df2cfb8269ada04a33
4
+ data.tar.gz: b21db455e7927c6c10219db01d2ba38fe24c258b3bfe454fd5f25212b95e78c5
5
5
  SHA512:
6
- metadata.gz: 87c976601cddc300a435f503983f5de87e54c7965b07fd36232526c8e3da26e4ac2f40b729e6f701924d9371872adeaafd780937eb9a750af95c8f6cb59b9012
7
- data.tar.gz: 0c528dbb8a6d4035a754f8b580562c21194a37e999c64f82831aa2c70e0cb53b3687d9e94bbcaf67fe7ca1526f96232b87bb77a4dac1d017b30dcca080c9315d
6
+ metadata.gz: 0a1a404ac38a7ee8a257bcd97f9e32c25d990b7ba61e9fb4226fe49862934aa7bfbf00e947b39981903a42e54ce232dad508faaadac1b9724ccb7ce01236bdca
7
+ data.tar.gz: 7cb20aec2cc044cca21d3010cf80da5f3ce902b269b72ffa6739bb61f4abb5fac32e95d57faef5d18c98fc27603704420ae1deb9250cdbf52ba8d5d937c75851
data/Gemfile CHANGED
@@ -1,5 +1,11 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
+ gemspec
4
+
3
5
  gem 'rack-test'
4
6
  gem 'minitest'
5
- gemspec
7
+ gem "rails"
8
+ gem "pg"
9
+ gem "climate_control"
10
+ gem "minitest-around"
11
+ gem "m"
data/README.md CHANGED
@@ -1,23 +1,25 @@
1
1
  [![Test](https://github.com/superfly/fly-ruby/actions/workflows/test.yml/badge.svg)](https://github.com/superfly/fly-ruby/actions/workflows/test.yml)
2
2
 
3
- # Augment Ruby web apps on Fly.io
3
+ This gem contains helper code and Rack middleware for deploying Ruby web apps on [Fly.io](https://fly.io). Supported features:
4
4
 
5
- [Fly.io](https://fly.io) offers a number of native features that can improve the perceived speed and observability of web applications with minimal configuration. This gem automates some of the work required to take advantage of these features.
5
+ * Speed up apps by using region-local Postgresql replicas for database reads
6
6
 
7
- ## Regional replicas
7
+ ## Speed up apps using region-local database replicas
8
8
 
9
- Running database replicas alongside your apps in multiple regions [is quick and easy with Fly's Postgresql cluster](https://fly.io/docs/getting-started/multi-region-databases/). This can increase the perceived speed of read-heavy applications.
9
+ Fly's cross-region private networking makes it easy to run database replicas [alongside your app instances in multiple regions](https://fly.io/docs/getting-started/multi-region-databases/). These replicas can be used for faster reads, leading to faster application performance.
10
10
 
11
- The catch: in most primary/replica setups, you have one writeable primary located in a specific region. Fly solves this by allowing requests to be *replyed*, at the routing layer, in another region.
11
+ Writes, however, will be slow if performed across regions. Fly allows web apps to specify that a request be *replayed*, at the routing layer, in another region.
12
12
 
13
- This repository includes the `fly-ruby` gem which will utomatcally route requests that write to the database to the primary region. It should work
14
- with any Rack-compatible Ruby framework.
13
+ This gem includes Rack middleware to automatically route such requests to the primary region. It's designed should work with any Rack-compatible Ruby framework.
15
14
 
16
15
  Currently, it does this by:
17
16
 
18
17
  * modifying the `DATABASE_URL` to point apps to their local regional replica
19
18
  * replaying non-idempotent (post/put/patch/delete) requests in the primary region
20
- * catching Postgresql exceptions caused by writes to a read-only replica, and replaying these requests in the primary region
19
+ * catching Postgresql exceptions caused by writes to a read-only replica, and asking for
20
+ these requests to be replayed in the primary region
21
+ * replaying all requests within a time threshold after a write, to avoid users seeing
22
+ their own stale data due to replication lag
21
23
 
22
24
  ## Requirements
23
25
 
@@ -35,7 +37,7 @@ Add to your Gemfile and `bundle install`:
35
37
 
36
38
  `gem "fly-ruby"`
37
39
 
38
- If you're on Rails, the middleware will insert itself automatically at the top of the Rack middleware stack.
40
+ If you're on Rails, the middleware will insert itself automatically, and attempt to reconnect the database.
39
41
 
40
42
  ## Configuration
41
43
 
@@ -52,7 +54,7 @@ See [the source code](https://github.com/soupedup/fly-rails/blob/main/lib/fly-ra
52
54
 
53
55
  This middleware send all requests to the primary if you do something like update a user's database session on every GET request.
54
56
 
55
- If your replica becomes writeable for some reason, your custer may get out of sync.
57
+ If your replica becomes writeable for some reason, your cluster may get out of sync.
56
58
 
57
59
  ## TODO
58
60
 
data/Rakefile CHANGED
@@ -1,5 +1,6 @@
1
1
  require "bundler/gem_tasks"
2
2
  require "rake/testtask"
3
+ require_relative "lib/fly-ruby/version"
3
4
 
4
5
  Rake::TestTask.new do |t|
5
6
  t.libs << "test"
@@ -10,7 +11,15 @@ end
10
11
  desc "Run tests"
11
12
  task default: :test
12
13
 
13
-
14
14
  task :top do
15
15
  puts Rake.application.top_level_tasks
16
16
  end
17
+
18
+ task :publish do
19
+ version = Fly::VERSION
20
+ puts "Publishing fly-ruby #{version}..."
21
+ sh "git tag -f v#{version}"
22
+ sh "gem build"
23
+ sh "gem push fly-ruby-#{version}.gem"
24
+ sh "git push --tags"
25
+ end
@@ -24,6 +24,8 @@ module Fly
24
24
  # primary region after a successful write replay
25
25
  attr_accessor :replay_threshold_in_seconds
26
26
 
27
+ attr_accessor :database_url
28
+
27
29
  def initialize
28
30
  self.primary_region = ENV["PRIMARY_REGION"]
29
31
  self.current_region = ENV["FLY_REGION"]
@@ -33,14 +35,39 @@ module Fly
33
35
  self.database_port_env_var = "DATABASE_PORT"
34
36
  self.replay_threshold_cookie = "fly-replay-threshold"
35
37
  self.replay_threshold_in_seconds = 5
38
+ self.database_url = ENV[database_url_env_var]
39
+ end
40
+
41
+ def regional_database_uri
42
+ @uri ||= URI.parse(database_url)
43
+ @uri
36
44
  end
37
45
 
38
- def database_url
39
- ENV[database_url_env_var]
46
+ # Rails-compatible database configuration
47
+ def regional_database_config
48
+ {
49
+ "host" => "#{current_region}.#{regional_database_uri.hostname}",
50
+ "port" => 5433,
51
+ "adapter" => "postgresql"
52
+ }
40
53
  end
41
54
 
42
55
  def eligible_for_activation?
43
- database_url && primary_region && current_region
56
+ database_url && primary_region && current_region && web?
57
+ end
58
+
59
+ # Is the current process a Rails console?
60
+ def console?
61
+ defined?(::Rails::Console) && $stdout.isatty && $stdin.isatty
62
+ end
63
+
64
+ # Is the current process a rake task?
65
+ def rake_task?
66
+ defined?(::Rake) && !Rake.application.top_level_tasks.empty?
67
+ end
68
+
69
+ def web?
70
+ !console? && !rake_task?
44
71
  end
45
72
  end
46
73
  end
@@ -1,8 +1,28 @@
1
+ require_relative '../fly-ruby'
2
+
1
3
  class Fly::Railtie < Rails::Railtie
2
4
  initializer("fly.regional_database") do |app|
3
5
  if Fly.configuration.eligible_for_activation?
6
+ # Run the middleware high in the stack, but after static file delivery
4
7
  app.config.middleware.insert_after ActionDispatch::Executor, Fly::RegionalDatabase
5
- elsif !ENV["TESTING"]
8
+
9
+ ActiveSupport::Reloader.to_prepare do
10
+ # If we already have a database connection when this initializer runs,
11
+ # we should reconnect to the region-local database. This may need some additional
12
+ # hooks for forking servers to work correctly.
13
+ if defined?(ActiveRecord) && ActiveRecord::Base.connected?
14
+ config = ActiveRecord::Base.connection_db_config.configuration_hash
15
+ ActiveRecord::Base.establish_connection(config.merge(Fly.configuration.regional_database_config))
16
+ end
17
+
18
+ # Set useful headers for debugging
19
+ ::ApplicationController.send(:after_action) do
20
+ response.headers['Fly-Region'] = ENV['FLY_REGION']
21
+ response.headers['Fly-Database-Host'] = Fly.configuration.regional_database_config["host"]
22
+ end
23
+ end
24
+
25
+ elsif Fly.configuration.web?
6
26
  puts "Warning: DATABASE_URL, PRIMARY_REGION and FLY_REGION must be set to activate the fly-ruby middleware. Middleware not loaded."
7
27
  end
8
28
  end
@@ -8,33 +8,23 @@ module Fly
8
8
  class RegionalDatabase
9
9
  def initialize(app)
10
10
  @app = app
11
- prefer_regional_database! unless in_primary_region?
12
11
  end
13
12
 
14
- def console?
15
- defined?(::Rails::Console) && $stdout.isatty && $stdin.isatty
16
- end
17
-
18
- def rake_task?
19
- defined?(::Rake) && !Rake.application.top_level_tasks.empty?
20
- end
13
+ # Overwrite the database connection string environment variable
14
+ # to prefer connections to the regional replica.
15
+ #
16
+ # For Rails apps, this process will be repeated at middleware insertion time,
17
+ # to support situations where the database is already accessed by other
18
+ # initialization code. See Fly::Railtie.
21
19
 
22
- # Overwrite the primary database URL with that of the regional replica
23
20
  def prefer_regional_database!
24
- # Don't override the database if migrations are running
25
- return if console? || rake_task?
26
-
27
- uri = URI.parse(Fly.configuration.database_url)
28
- hostname = "#{Fly.configuration.current_region}.#{uri.hostname}"
29
- port = 5433
21
+ return if Fly.configuration.web?
30
22
 
31
- uri.hostname = hostname
32
- uri.port = port
33
- uri.to_s
23
+ uri = Fly.configuration.database_uri
34
24
 
35
25
  ENV[Fly.configuration.database_url_env_var] = uri.to_s
36
- ENV[Fly.configuration.database_host_env_var] = hostname
37
- ENV[Fly.configuration.database_port_env_var] = port.to_s
26
+ ENV[Fly.configuration.database_host_env_var] = uri.hostname
27
+ ENV[Fly.configuration.database_port_env_var] = uri.port.to_s
38
28
  end
39
29
 
40
30
  def in_primary_region?
@@ -1,3 +1,3 @@
1
- class Fly
2
- VERSION = "0.1.1"
1
+ module Fly
2
+ VERSION = "0.2.0"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fly-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joshua Sierles
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-07-08 00:00:00.000000000 Z
11
+ date: 2021-07-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rack