Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Criando aplicações multi-tenant com bancos separados

Criando aplicações multi-tenant com bancos separados

Palestra feita no GuruSC 2011

Gabriel Sobrinho

October 02, 2011
Tweet

More Decks by Gabriel Sobrinho

Other Decks in Programming

Transcript

  1. MULTI-INSTANCE • Para cada novo cliente, uma nova instância da

    aplicação; • Alto custo de infra-estrutura; • Ambientes totalmente isolados;
  2. MULTI-TENANT • Apenas uma instância para atender todos os clientes;

    • Baixo custo de infra-estrutura; • Aumenta a complexidade para isolar os dados;
  3. PRINCIPAIS ABORDAGENS MULTI-TENANT • Separação lógica usando o cliente como

    referência; • Schemas separados para cada cliente; • Bancos totalmente isolados;
  4. SEPARAÇÃO LÓGICA • Responsabilidade da aplicação; • Banco de dados

    cresce conforme quantidade de clientes; • Podemos escalar usando master/slave ou sharding; • Compartilhamento de dados entre os clientes;
  5. SCHEMAS • Atualmente somente no PostgreSQL; • Migrations devem ser

    escritas em SQL; • Controle de versão do banco insiste em ficar no schema public; • Desconheço o uso desta abordagem em produção;
  6. BANCOS ISOLADOS • Dados totalmente isolados! • Bancos menores e

    possibilidade de backups incrementais; • Cada cliente pode definir qual banco de dados quer utilizar; • Bancos separados geograficamente;
  7. BANCOS ISOLADOS • Permite instalar o banco de dados no

    próprio cliente; • Permite uso de recursos específicos do banco como procedures; • Permite o acesso ao banco de dados pelo cliente;
  8. ACTIVERECORD CONNECTIONS • Projeto recente, ainda não está maduro; •

    Ainda não trabalhei na parte onde todos os clientes são migrados ao mesmo tempo; • Entra em produção no próximo mês (Novembro 2011);
  9. ACTIVERECORD CONNECTIONS • Pool de conexões para cada cliente; •

    Cuidado ao usar o mesmo servidor para todos os bancos! • Altera somente dois métodos do activerecord usando um proxy;
  10. def using_connection(connection_name, connection_spec) self.proxy_connection = ConnectionProxy.new(connection_name, connection_spec) def self.connection_pool connection_handler.retrieve_connection_pool(proxy_connection)

    end def self.retrieve_connection connection_handler.retrieve_connection(proxy_connection) end yield ensure self.proxy_connection = nil def self.connection_pool connection_handler.retrieve_connection_pool(self) end def self.retrieve_connection connection_handler.retrieve_connection(self) end end
  11. APLICANDO NA PRÁTICA class Customer < ActiveRecord::Base attr_accessible :name, :domain,

    :database serialize :database validates :name, :domain, :database, presence: true validates :name, :domain, uniqueness: { allow_blank: true } def using_connection(&block) ActiveRecord::Base.using_connection(id, database, &block) end end
  12. APLICANDO NA PRÁTICA class ApplicationController < ActionController::Base protect_from_forgery around_filter :handle_customer

    protected def handle_customer(&block) customer = Customer.find_by_domain!(request.host) customer.using_connection(&block) end end
  13. APLICANDO NA PRÁTICA namespace :db do desc "Migrate each customer's

    database" task :migrate => :environment do verbose = ENV["VERBOSE"] ? ENV["VERBOSE"] == "true" : true version = ENV["VERSION"] ? ENV["VERSION"].to_i : nil ActiveRecord::Migration.verbose = verbose Customer.find_each do |customer| puts "migrating customer #{customer.domain}" if verbose customer.using_connection do ActiveRecord::Migrator.migrate(ActiveRecord::Migrator.migrations_paths, version) end end end end