There's a lot of open issues
No release in over a year
Simple Postgres replication helper
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
 Dependencies

Development

~> 0.9

Runtime

~> 1.1
~> 2.0, >= 2.0.0
~> 7.0
~> 13.0
 Project Readme

Koyo::Postgres::Replication

例 rei - Japanese for example

効用 koyo - Japanese for utility

What is this?

This gem tries to simplify dealing with a replication slot in Postgres. It gives you a simple way to capture events in your Rails app when your Postgres DB has a create, update, delete event. You can manage this from one class, or manage this on a table-by-table basis within ActiveRecord Models.

Example of catch all:

class KoyoReplHandlerService < Koyo::Repl::EventHandlerService
  class << self
    # This is called whenever a create/update/delete action happens
    # @param (Koyo::Repl::DataRow) row is docuemented in the wiki at
    # https://github.com/wiseleyb/koyo-postgres-replication/wiki/Koyo::Repl::DataRow-data-spec
    def koyo_handle_all_replication(row)
      case row.kind
      when 'insert'
        case row.table
        when 'users'
          # Do something with data... like update some api
          # It's important to do this async (active-job/sidekiq) so you 
          # don't back up the replication slot
          UpdateSomeApi.performn_async(row.id)
          # This job would do something like:
          # User.find(row.id); Call some API with data
      when 'delete'
      when 'update'
    end
  end
end

Example of model callback:

class User < ApplicationRecord
  include Koyo::Repl::Mod
  koyo_repl_handler :handle_replication

  # This is called when a row is created/updated/deleted for the users table
  # @param (Koyo::Repl::DataRow) row is docuemented in the wiki at
  # https://github.com/wiseleyb/koyo-postgres-replication/wiki/Koyo::Repl::DataRow-data-spec
  def self.handle_replication(row)
    case row.kind
    when 'insert'
      # Do something with data... like update some api
      # It's important to do this async (active-job/sidekiq) so you 
      # don't back up the replication slot
      UpdateSomeApi.performn_async(row.id)
      # This job would do something like:
      # User.find(row.id); Call some API with data
    when 'delete'
    when 'update'
  end
end

What is a replication slot?

Please see the wiki page that discusses replication slots

Why would you use this?

Example: You have a no-sql store (like Elastic-Search) that needs to be in-sync with your database. You could do this "the Rails way" with after-save type patterns. You could do this with a service type architecture that updates things. But if you have non-rails teams updating data, or ever update the database via SQL, this becomes more complex, duplicates delicate/error-prone work between teams, or just isn't possible. Going the microservice approach also isn't ideal for performance reasons (you never want to updating a really busy DB via API).

Please see the wiki for more discussion on this.

Quick Start

You need to configure Postgres for this first. This isn't enabled by default. See Configuring Postgres for Replication in the wiki

Add to Gemfile:

Versions

The main branch is always the latest code. Branches contain versions that are specific to Rails.

Rails Branch Gemfile
7 verions/7.0.0 gem 'koyo-postgres-replication', '~> 7.0', require: 'koyo'
6 coming soon n/a
5 coming soon n/a
4 coming soon n/a
3 probably not coming soon n/a
gem 'koyo-postgres-replication', require: 'koyo'

Then:

bundle install
bundle exec rake koyo:repl:install

This adds files:

  • config/initializers/koyo_postgres_replication_config.rb: which is commented on various ways to override various settings.
  • app/models/koyo_repl_handler_service.rb: which has call backs for every replication event, log events, and errors. This file is also heavily commented.
  • app/models/koyo_repl_model_example.rb: is an example of how to add replication monitoring on a model level. You can delete this file - it's just there for a simple example.

Run Diagnostics

You need to create a replication slot for each database. This requires admin access. In config you can setup a separate config/database.yml connection to limit admin level postgres access if your company prefers that.

# runs basic diagnostics - look for Error in this list and fix any issues
bundle exec rake koyo:repl:diagnostics

Which will output something like:

--------------------------------------------------------------------------------
Koyo::Repl::Diagnostic
source=KoyoReplication logid=f3a8f68d3e level=info message=Init: Finding models that support koyo_repl_handler
source=KoyoReplication logid=410b6fd0d0 level=info message=Init: ignoring model SchemaMigration
source=KoyoReplication logid=b429e47251 level=info message=Init: ignoring model ArInternalMetadatum
source=KoyoReplication logid=6532a8ec76 level=info message=Init: registering handler ["users", "User"]
Config settings:
  auto_create_replication_slot: true
  config_prefix: KOYO_REPL
  db_conn:
  slot: koyo_repl_rei_postgres_replication_development_development
  sql_delay: 1
  test_mode: false
Replication slot exists: true
Registered tables:
  users: User
Can connect to db: true
Connection adapter: PostgreSQL
Wal Level (should be 'logical'): logical
Can access replication slot: true
Replication slot count: 0
--------------------------------------------------------------------------------

If there are errors you'll need to fix those first before running the server.

Run the server

bundle exec rake koyo:repl:run_server

Now - when you create/update/delete data in the configured database you should be getting callbacks in app/models/koyo_repl_handler_service.rb#koyo_handle_all_replication(row) and in any model app/models/{some-model}#handle_replication(row) that implements callbacks.

Row data in callbacks

See wiki page on DataRow for raw data examples of what you get from Postgres

Processing callbacks

Both koyo_repl_handler_service#koyo_handle_all_replication(row) and {some-model}#handle_replication(row) need to be REALLY fast. You shouldn't do database updates from this code (or risk infinite loops) and, if you're doing something like updating an API you should async that via Sidekiq, ActiveJob, etc. Keep in mind that, if you're running multiple Sidekiq servers that things might not be processed in the same order. Please see the wiki page on processing callbacks for more on this.

Testing

The following assumes you're using rspec (open to PRs supporting other test frameworks though - I just don't use those)

By default (and definitely the fastest way) specs run inside transactions that are rolled back after each spec runs. You need to use Database Cleaner truncation approach to test this stuff. Please see the wiki on testing for what's needed if you want to run replication slot tests.

Sample apps

These are simple Rails version-specific demo apps with Docker files

  • Rails 7 Example
  • Rails 6 Example: coming soon
  • Rails 5 Example: coming soon
  • Rails 4 Example: coming soon
  • Rails 3 Example: probably won't do

Technical

See wiki page on creating this gem if you're interested how to do gems like these.

Working with the gem

See wiki page on working with this gem for basics of debugging and working with.

Working with replication slots

See koyo::repl::database for sql examples on how to interact with replication slots.

Yard Doc

Yard docs are up on RubyDoc.info

Cheat sheets:

Build yard docs: yard View docs: yard server then open https://localhost:8808

Contributing

TODO: update

  • follow github guide
  • run/add specs
  • run rubocop
  • add/run yard

Working with gem

Run specs

rspec spec

Docker

See Docker wiki page

Gem Build

rm koyo-postgres-replication-{current version}.gem
git add .
gem build

TODO