Coder Social home page Coder Social logo

flipper-activerecord's People

Contributors

bgentry avatar bkeepers avatar trinode-work avatar

Stargazers

 avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

flipper-activerecord's Issues

Get all features in one go

Every time when enabled? gets called, flipper makes multiple network round trips to the database.

Instead, we can get all features in one query and cache it during the same HTTP request-response cycle, so that following enabled? check will be done without hitting the database. It makes sense to most typical Rails apps.

This change has several implications and trade-offs between time and space at scale:

  • The benefit of performance increases as the average number of features checked in a request grows.
    • Network RTT is becoming one of the slowest components in the tech stack. It takes at least 0.1ms in a fast 10GbE network, and it adds up linearly.
  • The cost of memory increases as the total number of features grows.
    • 100 features shouldn't be a problem, but instantiating 1,000 AR objects might be debatable.
  • An added bonus would be that it eliminates verbose multiple logs that calls flipper tables in development, which is basically a noise for the debugging purpose.

Would this be something we're interested in?

Reversible Migration to flipper-active_record

Since I had a production application depending on this gem I wrote a large migration to move
from it to the new official flipper-active_record adapter gem. Posted here to help anyone who might face a similar struggle. This is a fully reversible migration.

I'm assuming you used the following original migration for flipper-activerecord:

class CreateFlipperTables < ActiveRecord::Migration
  def self.up
    create_table :flipper_features do |t|
      t.string :name, null: false
      t.timestamps null: false
    end
    add_index :flipper_features, :name, unique: true

    create_table :flipper_gates do |t|
      t.integer :flipper_feature_id, null: false
      t.string :name, null: false
      t.string :value
      t.timestamps null: false
    end
    add_foreign_key :flipper_gates, :flipper_features, on_delete: :cascade
    add_index :flipper_gates, [:flipper_feature_id, :name, :value], unique: true
  end

  def self.down
    remove_foreign_key :flipper_gates, :flipper_features
    drop_table :flipper_gates
    drop_table :flipper_features
  end
end

Again, I'm assuming you are moving to flipper-active_record which at the time of this writing uses the following migration:

class CreateFlipperTables < ActiveRecord::Migration
  def self.up
    create_table :flipper_features do |t|
      t.string :key, null: false
      t.timestamps null: false
    end
    add_index :flipper_features, :key, unique: true

    create_table :flipper_gates do |t|
      t.string :feature_key, null: false
      t.string :key, null: false
      t.string :value
      t.timestamps null: false
    end
    add_index :flipper_gates, [:feature_key, :key, :value], unique: true
  end

  def self.down
    drop_table :flipper_gates
    drop_table :flipper_features
  end
end

The two migrations are similar aside from the following changes necessary to be compatible with flipper-active_record:

  • flipper_features table uses key string over name string to identify the feature
  • flipper_gates table uses feature_key string over flipper_feature_id integer to reference the related flipper_features record
  • flipper_gates table uses key string over name string to identify the gate
  • there is no foreign key associating flipper_features and flipper_gates

Since I'm assuming that โ€” like me โ€” you had both existing flipper_features and flipper_gates records in production which you could not afford to wipe, here's the migration:

class MigrateToFlipperActiveRecord < ActiveRecord::Migration
  # Create Migration classes to avoid relying on models that may not
  # exist in the future. See:
  # http://blog.testdouble.com/posts/2014-11-04-healthy-migration-habits
  class MigrationFeature < ActiveRecord::Base
    self.table_name = "flipper_features"
  end
  class MigrationGate < ActiveRecord::Base
    self.table_name = "flipper_gates"
  end

  def up
    # Add new columns and indices
    add_column :flipper_features, :key, :string

    # Migrate existing data to avoid errors when enabling null: false
    MigrationFeature.all.find_each do |feature|
      feature.update_attribute(:key, feature.name)
    end

    change_column_null :flipper_features, :key, false

    add_column :flipper_gates, :key, :string
    add_column :flipper_gates, :feature_key, :string

    # Migrate existing data to avoid errors when enabling null: false
    MigrationGate.all.find_each do |gate|
      feature = MigrationFeature.find_by(id: gate.flipper_feature_id)
      gate.update(key: gate.name, feature_key: feature.key)
    end

    change_column_null :flipper_gates, :key, false
    change_column_null :flipper_gates, :feature_key, false

    add_index :flipper_features, :key, unique: true

    add_index :flipper_gates, [:feature_key, :key], unique: true

    # Renove old columns (and indices automatically)
    remove_column :flipper_features, :name

    change_table :flipper_gates do |t|
      t.remove :flipper_feature_id
      t.remove :name
    end
  end

  def down
    # Add old columns and indices
    add_column :flipper_features, :name, :string

    # Migrate existing data to avoid errors when enabling null: false
    MigrationFeature.all.find_each do |feature|
      feature.update_attribute(:name, feature.key)
    end

    change_column_null :flipper_features, :name, false

    add_index :flipper_features, :name, unique: true

    add_column :flipper_gates, :flipper_feature_id, :integer
    add_column :flipper_gates, :name, :string

    # Migrate existing data to avoid errors when enabling null: false
    MigrationGate.all.find_each do |gate|
      feature = MigrationFeature.find_by(key: gate.feature_key)
      gate.update(
        flipper_feature_id: feature.id,
        name: gate.key
      )
    end

    change_column_null :flipper_gates, :flipper_feature_id, false
    change_column_null :flipper_gates, :name, false

    add_foreign_key :flipper_gates, :flipper_features,
      on_delete: :cascade
    add_index :flipper_gates, [:flipper_feature_id, :name],
      unique: true

    # Remove new columns (and indices automatically)
    remove_column :flipper_features, :key

    change_table :flipper_gates do |t|
      t.remove :feature_key
      t.remove :key
    end
  end
end

I've run this migration in both directions and checked state so it should be safe but I highly recommend testing in your development and staging environments before running it in production.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.