Um dos fatores de qualidade de SaaS é eliminar os SPOFs ganhando assim alta disponibilidade do serviço para o usuário final. A regra é simples, se tem algo no sistema que é único, tenha uma backup desse serviço, a.k.a redundância. Exemplo dois ou mais replicas de banco de dados, mais de um servidor http, mais de um worker e etc.
Na modalidade de negócios Saas isso significa credibilidade e deve ser mantida no mais alto nível quanto possível. Quando há uma falha crítica o cliente não quer saber se o correu falha humana, falha no banco, falha no hosting, no storage ou qualquer outro componente da solução, o cliente quer a informação no momento do acesso. Quaisquer tipo de transtorno causados no acesso dessa informação custa caro para empresa, é um chamado a mais para um atendente, é um ticket a mais para você solucionar.
E a sorte nunca esta do lado executivo-da-força, se tem algo pra dar problema… vai acontecer hoje ou outro dia. Isso não é pessimismo, isso é realidade do dia-a-dia em tentar deixar uma big aplicação Always on no cloud.
Falando em cloud e indo para um nível baixo, na comunidade Ruby on Rails existe muito barulho na parte de escalabilidade, aquela velha história do “O Rails não escala”, não continuarei esse briga aqui… o intuito desse post é compartilhar uma estrutura que estamos usando aqui na OfficeDrop para manter a sessão do usuário mesmo se houver um erro tanto no memcached master ou se houver uma sobrecarga no uso do mesmo.
O workflow de sessão
O fluxo de utilização do memcached pelo Rails é bem simples. O memcache-client instanciado no Rails estabelece um conexão TCP/IP(pode utilizar UDP também) com o memcached, considerando uma conexão ativa ente eles, você pode utilizar vários comandos disponíveis no memcache-client que na verdade é um wrapper facilitador. Se você for curioso o bastante inicie o memcached e teste-o com o telnet.
Inicie com very verbose mode pra ver as mensagens sendo trocadas pelo protocolo
memcached -vv
Obs: estou considerando o memcached instalado no seu sistema.
A porta padrão do memcached é 11211
telnet localhost 11211
Com a conexão ativa você pode executar comandos como o set para armazenar uma valor utilizando uma chave como indexado.
set msg 0 0 7
diga oi
As duas linhas acima estão dizendo “utilize a chave “msg” com flag 0 (o primeiro), sem tempo de expiração (o segundo 0 ) com o valor ‘diga oi’ com o comprimento 7 (length)”. Para obter o valor, use get com a chave e comprimento do valor.
get msg 7
ficando assim seu shell
telnet localhost 11221
Trying ::1...
Connected to localhost.
Escape character is '^]'.
set msg 0 0 7
diga oi
STORED
get msg 7
VALUE msg 0 7
diga oi
END
Dê uma olhada aqui para saber sobre mais comandos.
Na Prática
É bem popular a utilização do memcached no rails mas tem um problema na utilização padrão. Mesmo configurando um failover em um array de servidores ["mem1:11211", "mem2:11212"] isso não garante, por exemplo, que a sessão do usuário iniciada no mem1 será mantida no servidor mem2 se houver uma queda no mem1. Então, falhando mem1 o usuário terá que logar novamente, mas dessa vez usando o mem2.
Repcached
Para evitar que o usuário tenha esse desprazer de logar novamente utilizamos o Repcached.

O Repcached é um patch para lidar com replicação multi-master assíncrona, multi-master aqui quer dizer, mem1 replicará para mem2, se mem1 falhar mem2 receberá as novas requisições e quando o mem1 tonar ativo o mem2 enviará os dados para o mem1.
Cenários
Cenário 1 – Apenas uma VM
Ideal se o seu sistema só tem um servidor.

Não é tão ideal assim, se seu sistema só tem um servidor … repcached não resolve muito o seu caso. Você usaria repcached apenas para redundar o servidor master na mesmo host.
Cenário 2 – Dois ou mais VMs
Ideal para sistema médio ou grande. Com essa configuração cada instância do Rails utilizará o memcached na máquina local e o failover para o servidor central. Então quando o usuário inicia a sessão no repcached1 e por acaso venha a falhar a sessão pode ser retomada usando o repcached2.

Para os dois cenários a configuração é a mesma, mudando apenas os IPs.
Compilando
A instalação é bem simples apenas com libevent como dependência, a compilação abaixo é para MacOS, se você esta usando linux verifique no seu gerenciador de pacote como instalar libevent no seu sistema.
Instale libevent
sudo port install libevent
Baixe, descompacte e entre no diretório que foi criado
wget http://sourceforge.net/projects/repcached/files/repcached/2.2-1.2.8/memcached-1.2.8-repcached-2.2.tar.gz/download
tar zxf memcached-1.2.8-repcached-2.2.tar.gz
cd memcached-1.2.8-repcached-2.2
No configure, passe o flag --enable-replication
./configure --prefix=/opt/local --enable-replication
Compile e instale
sudo make install
Observe o destino em que o repcached foi instalado, observe também que o nome do executável é o mesmo do memcached. Lembrando… repcached é o memcached com patch para replicação.
/usr/bin/install -c 'memcached' '/opt/local/bin/memcached'
/usr/bin/install -c 'memcached-debug' '/opt/local/bin/memcached-debug'
Se você achar melhor, crie um alias
alias repcached=/opt/local/bin/memcached
Após a instalação execute o repcached com -h, nas duas primeiras linhas o memcached original e o repcached.
/opt/local/bin/memcached -h
memcached 1.2.8
repcached 2.2
E nas últimas linhas o -x host e -X port para a replicação
-x hostname or IP address of peer repcached
-X TCP port number for replication (default: 11212)
Veja que a porta de replicação padrão é 11212, então nenhum memcached poderá ser iniciado na mesma.
Configure /etc/hosts para ambas as VMs
192.168.20 mem1
192.168.20 mem2
Iniciando os servidores
Vamos iniciar o primeiro servidor, o mem1, -p(TCP port 11220) e -U (UDP port 11220) são parâmetros para o memcached local, -x (ip mem2) e -X (port 11230) são parâmetros para replicação. Então, no mem1 o comando é.
memcached -vv -p 11220 -U 11220 -x mem2 -X 11230
Observe que na inicialização aparecerá o servidor de replicação utilizado
replication: connect (peer=mem2:11230)
replication: marugoto copying
replication: close
<5 connection closed.
Mas com a replicação inativa.
Agora na mem2, inicie o memcached apontando pra mem1
memcached -vv -p 11220 -U 11220 -x mem1 -X 11230
Agora, o mem2 ativa a replicação
replication: connect (peer=mem1:11230)
<4 new client connection
replication: marugoto copying
<4 marugoto_end
<7 server listening
<8 server listening
<9 send buffer was 9216, now 3728270
<9 server listening (udp)
<10 send buffer was 9216, now 3728270
<10 server listening (udp)
replication: start
Se você der uma olhada no mem1 depois de iniciar a replicação no mem2 o handshake começa.
<4 server listening (replication)
<5 server listening (replication)
replication: listen
<6 server listening
<7 server listening
<8 send buffer was 9216, now 3728270
<8 server listening (udp)
<9 send buffer was 9216, now 3728270
<9 server listening (udp)
replication: accept
Configurando o Rails 3.0.3
Dado que os servidores estão configurados vamos para o Rails. Utilizaremos o Rails 3 para o teste. Então no Gemfile use
gem "memcache-client"
gem 'memcached-northscale', :require => 'memcached'
Execute o bundle
bundle install
Configure os servidores no config/environment.rb, com opções essenciais, habilitando multi-thread, compressão para grande quantidade de dados e os servidores. Usei o nome App para a aplicação então o arquivo config/environment.rb fica parecido com:
require File.expand_path('../application', __FILE__)
RAILS_CACHE = Memcached::Rails.new {
:compression => true,
:multithread => true,
:servers => ['mem1:11211','mem2:11211']
}
# Initialize the rails application
App::Application.initialize!
App::Application.config.cache_store = :mem_cache_store
App::Application.config.session_store(:mem_cache_store, :cache => RAILS_CACHE)
O teste
Você pode testar criando um scaffold usando session[] mas aqui vou usar o console pra demonstrar a replicação. Um simples teste,
1. Usar Rails.cache
2. Armazenar um valor
3. Fechar o mem1
4. Ler o valor
Então abra o console e verifique se o Rails.cache esta usando o memcache-client
rails console
Loading development environment (Rails 3.0.3)
>> Rails.cache.class
=> Memcached::Rails
Armazenando um valor
>> Rails.cache.set "message", "oi tudo bem?"
=> true
No STDOUT do mem1 você verá algo como
<11 set mytestmessage 0 604800 16
>11 STORED
e no mem2
<10 rep mytestmessage 0 1292878695 16 2
REP>10 STORED
Agora feche o mem1, volte no console do rails e leia o valor
>> Rails.cache.get "message"
=> "oi tudo bem?"
Hmm então funcionou!
No mem2 você verá que o repcached está tentando replicar para mem1. Como o memcached é volátil quando o servidor memcached é fechado todas as chaves são perdidas mas como a replicação esta ativa e o mem2 tem as chaves, no ato de iniciar o mem1 novamente, mem2 sincronizará as chaves e os valores com mem1.
replication: marugoto copyinga
<4 rep mytestmessage 0 1292878695 16 2
REP>4 STORED
<4 marugoto_end
Então é isso, se você precisar de esteroides na sua sessão da sua aplicação... repcached é uma boa pedida.
A gem memcached-northscale tem várias opções de configuração do memcached no rails, recomendo a leitura da documentação que esta em rdoc.
Outra opção seria o Redis talvez em outro momento eu posto algo sobre ele.