Caelum | Ensino e Inovação - Cursos de Java, Scrum, Ruby on Rails


Um produto para muitos clientes: implementando multitenancy

Por Guilherme Silveira em 23/08/10

São diversos as aplicações web disponíveis, como quadros eletrônicos, sistemas de tracking, email e aplicações para empresas, ou até mesmo controle de clientes e vendas. Eles até ganharam um pomposo nome dentro do cloud computing: Software as a Service (SAAS).

O que essas aplicações possuem em comum? Todas elas atendem diversos clientes sem que um tenha conhecimento da existência dos outros.

Em um post recente no Tectura.com.br foram discutidas vantagens e desvantagens de diversas abordagens para produtos com a necessidade de suportar mais de um tenant.

A Microsoft categoriza três tipos de abordagens dependendo do nível de compartilhamento de recursos entre os clientes e apresente um relatório onde analisa os custos contra a segurança.

Em um extremo, nada é compartilhado entre cada cliente: para cada nova conta criada dentro de seu serviço, é criada uma nova máquina na cloud e uma instalação limpa é executada, com seu próprio banco.

Dois servidores, dois bancos

Nessa abordagem p processo de autorização é automático, um cliente é criado através da instalação automática de uma nova máquina virtual e os recursos são compartilhados se o ambiente for uma cloud.

Na outra ponta, tudo é compartilhado: novos clientes são inseridos no mesmo conjunto de máquinas e a instalação é feita através de um simples insert no banco, adicionando um novo cliente. Qual abordagem escolher?

Um servidor, um banco

A autorização é feita programaticamente na aplicação, os recursos são compartilhados entre todas elas, escala-se tipicamente através do uso de load balancers e replicação master/slave e a cada novo cliente basta executar um insert no banco.

Em qualquer abordagem onde o banco seja compartilhado por diversas empresas, precisamos garantir a segurança dos dados de cada uma, para que nenhum acesso indevido ocorra, trazendo preocupações de autorização para dentro do código de nossa aplicação. Caso seja criado para cada cliente um banco em uma máquina no cloud, essa questão fica concentrada em um único ponto da arquitetura, a segurança está implícita.

Dois servidores, um banco

Diversos servidores e um banco implica em clientes não afetarem uns aos outros, um controle programático de autorização e novos clientes são criados a partir da instalação de um novo contexto web.

Ao mesmo tempo, o processo de customização de seu serviço por cliente também é afetada de acordo com o nível de compartilhamento de dados entre eles. Na abordagem onde os clientes compartilham o mesmo servidor web, a customização é uma preocupação de nossa aplicação, enquanto ao utilizarmos aplicações web distintas para cada cliente, podemos facilmente separar customizações por instância, por deploy efetuado.

A escalabilidade é afetada pois compartilhar recursos em memória na camada web, entre eles dados cacheados do banco ou filas, permitem diminuir o tempo de processamento ou de latência, aumentando o número de requisições suportados.

O processo de escalar também está diretamente ligado: quando uma máquina não aguentar mais as requisições, será levantada uma outra máquina que funcionará em cluster para um, n ou todos os clientes?

Outro fator importante é como limitar o uso do serviço de acordo com as regras contratadas pelo cliente e controlar o dano que um pode causar a outros. Em um pico de uso por parte dos usuários de um cliente, ativamos o chargeback, dependendo do tipo de serviço que é prestado. Implementações comuns de chargeback em cloud (baseados em virtualização) permitem um pico de consumo temporário ou até mesmo mover a aplicação de uma máquina para outra sem que cliente algum perçeba o que está acontecendo.

Dois servidores, um banco

Diversos servidores e um banco implica em clientes não afetarem uns aos outros, um controle programático de autorização e novos clientes são criados a partir da instalação de um novo contexto web.

Ao invés daqueles usuários atrapalharem a performance de outros clientes, é alocado memória e processador distintos dos atuais para ele.

A simplicidade do código é um dos fatores mais afetados pela escolha feita: um código que sabe da existência de múltiplos clientes está diretamente ligado a uma chave que identifica o cliente atual. E essa ligação se reflete por todos os lados do código, uma vez que o comportamento da aplicação em geral depende dele: uma chave estrangeira, um relacionamento, que vai permear toda a aplicação.

Caso a aplicação implemente o suporte a multi tenant através de instalções web distintas, com configurações para cada cliente apontando para um bancos distintos, o código fica mais simples e fácil de manter, uma vez que não é necessário se preocupar com a existência de outros clientes.

Por fim, uma preocupação que surge para a empresa provedora dos serviços é de como agrupar os dados existentes em todos os clientes e gerar relatórios de administração e estatísticas que permitam a melhora do serviço prestado. Quando possuimos um único banco, escalado através de master slave ou múltiplos nós, basta executarmos queries longas (sql ou não), mas se o sistema estiver distribuido entre diversos bancos sem ligação entre si, um processo de batch deve rodar para agrupar os dados e permitir o consumo posterior pelas ferramentas de BI.

É importante analisar todos esses pontos antes de tomar a decisão se o controle de espaço de aplicação por cliente será feito no nosso código, na camada web, no banco ou em algum outro ponto. Como discutimos bastante no curso de arquitetura, toda e qualquer decisão implica em um tradeoff: o importante é saber o que está sendo trocado e qual é o impacto disso.

  • Share/Bookmark

Escalando sistemas com soluções NoSQL

Por douglas.campos em 07/06/10

Um dos grandes desafios enfrentados no dia a dia do desenvolvedor eficaz é o de cumprir requisitos não-funcionais de uma aplicação, principalmente os relacionados a performance e escalabilidade. Uma das alternativas mais conhecidas para escalar horizontalmente é a de dividir as tarefas que não necessitam de retorno imediato ao cliente em processos batch. Para tanto, podemos usar diversas ferramentas, desde soluções caseiras até diversos frameworks, tanto em ruby como em java.

Quando se trata de processos batch, é comum buscar um controle mais fino sobre o resultado da execução (sucesso, falha, pendente, etc). Normalmente, usaríamos bancos relacionais para isso, tanto pela sua popularidade como pela performance. Supondo que eu precise gerenciar 1000 pequenas tarefas simultâneas, pagaríamos um preço bem alto pela concorrência, visto que o banco começaria a demorar para obter o lock, fazer o insert, refazer o lock e atualizar o status da tarefa. Muitos consideram o uso de banco de dados relacionais um erro para casos como esse. Buscando eficiência, poderíamos usar uma solução NoSQL para persistência e controle das tarefas, onde evitaríamos os locks, graças ao modelo de concorrência simplificado.

Uma opção em ruby é o resque, um framework de processamento batch criado pelo github que utiliza o redis, um banco de dados não-relacional, tanto para persistir as tarefas, como para coordenar os diversos processos executores de tarefas, mais conhecidos como workers.

Agendar um processo é bem simples – basta que eu tenha uma classe ruby com um método perform, com o processamento a ser executado e um atributo de instância @queue define em qual fila a tarefa será colocada:

# uma classe qualquer, poderia ser um model
class Relatorio
    @queue = :relatorio_anual

    def self.perform(ano)
        gera_relatorio_anual(ano)
    end
end

e finalmente, faço o agendamento:

#método recebe a classe da tarefa, e os argumentos
Resque.enqueue(Relatorio, 2009)

Essa chamada faz uso bem eficiente de metaprogramação, de maneira que um worker posteriormente executará um código semelhante a esse:

classe, argumentos = Resque.reserve(:relatorio_anual)
classe.perform(*argumentos) if class.respond_to? :perform

posteriormente iniciamos um worker usando as tasks do rake que vem no próprio resque, indicando qual fila o worker que estamos criando vai atender

$ QUEUE=relatorio_anual rake resque:work

assim que o worker subir ele já irá executar Resque.reserve, que o colocará em espera por novos trabalhos. opcionalmente, poderíamos executar um worker para atender todas as filas:

$ QUEUE=* rake resque:work

O principal fator que contribui para que resque e redis funcionem como um relógio é o fato de ambos estarem focados em alta performance e, ao mesmo tempo, extrema simplicidade sem ser muito invasivo, já que permitem que você mantenha sua estrutura de persistência relacional inalterada.

Cada vez mais sistemas vem utilizando solucões NoSQL para algumas funcionalidades específicas, como foi o caso recente do Large Hadron Collider com o Mongo DB. Parece que a combinação de bancos relacionais e não-relacionais no mesmo sistema, cada um com seu propósito, tem sido a direção tomada pelo mercado.

  • Share/Bookmark

Escrevendo e migrando aplicações para o Google App Engine

Por Pedro Matiello em 17/11/09

Recentemente migramos nosso site para o Google App Engine (GAE), o serviço de cloud computing do Google, fazendo uso do suporte a aplicações Java. A ideia é que você desenvolva sua webapplication normalmente e faça o upload do war para os servidores do Google usando um SDK, que também possui um servidor local para desenvolvimento. Pronto! Sua aplicação web agora oferece alta escalabilidade e disponibilidade.

Algumas restrições existem, no entanto. Vamos passar por elas e comentar cada uma, que no início podem ser uma barreira para o desenvolvedor. Para começar, há uma whitelist especificando as classes do Java que são suportadas. As classes da biblioteca padrão não listadas são bloqueadas. Pode parecer inconveniente, mas estas limitações são necessárias para garantir a segurança e escalabilidade do serviço — basta lembrar que uma mesma máquina é dividida entre muitas aplicações, e que cada aplicação pode estar rodando em um grande número de servidores.

O Google App Engine também não permite que novos arquivos sejam criados pela aplicação no servidor, já que isso poderia implicar em problemas de sincronização entre os sistemas de arquivos de suas muitas instâncias. Também não é oferecido um banco de dados relacional, mas sim uma datastore sobre o BigTable, que pode ser acessada pelas APIs do JDO e da JPA. Relacionamentos many-to-many, consultas com join, agregações (sum, avg, max, etc) e polimórficas não são suportadas, mas a maior parte das demais operações funciona como de costume sem a necessidade de maiores mudanças. Trata-se de uma característica comum dos bancos de dados não-relacionais, que abrem mão de algumas funcionalidades para obter maior desempenho, escalabilidade e disponibilidade. Pode ser trabalhoso migrar de um banco de dados relacional para o BigTable, além de ser uma tecnologia proprietária do Google.

Outra limitação importante é a impossibilidade de criar sockets e Threads. Todavia, o App Engine possui mecanismos próprios que permitem a realização de requisições HTTP e o envio de emails (com restrições na especificação do remetente). Em breve uma API de agendamento de tarefas será disponibilizada para o Google App Engine Java.

Ainda, o suporte a Expression Language vem desativado por padrão. Para ativá-lo, é necessário adicionar a seguinte linha em todos os arquivos JSP que fazem uso de EL:
<%@ page isELIgnored="false" %>

No caso de arquivos de tags, deve-se adicionar:
<%@ tag isELIgnored="false" %>

A tag <include-prelude> no web.xml também é ignorada, mas é possível contornar o problema adicionando algo como a linha abaixo no começo dos arquivos JSP:
<%@ include file="../prelude.jspf" %>

Um problema de outra é ordem é a inicialização e a manutenção das instâncias, conhecido como cold start. Quando sua aplicação é chamada pela primeira vez, várias instâncias são produzidas de forma distribuída pelos servidores da nuvem. Este processo é bastante lento, podendo consumir vários segundos, e depende também da velocidade do contexto da sua aplicação web. Os acessos seguintes são respondidos com baixa latência, mas, após um período de inatividade, as instâncias são destruídas, exigindo que todo o lento processo de inicialização seja refeito pelo serviço em um próximo acesso. Para evitar isso, a prática mais comum tem sido usar o agendador de tarefas do próprio App Engine para visitar alguma página do site a cada poucos minutos — e esta é uma solução que também possui seus problemas.

Por causa da grande quantidade de componentes restritos, muitos frameworks, como Spring, implementações de JSF, mapeadores XML, exigem ajustes para rodar no GAE. O mesmo se aplica ao VRaptor 3 e, para facilitar o trabalho dos usuários, muitos deles oferecem uma versão voltada para o uso no Google App Engine, incluindo todas as alterações necessárias. No caso do VRaptor 3, você pode obter um blank-project pro GAE na página de downloads e usa-lo como esqueleto de um novo projeto, que pode ser facilmente importado e modificado no Eclipse (existe um plugin do Google bastante prático, mas um build.xml adequado ao GAE é uma alternativa bastante razoável).

Em post anterior, o Paulo Silveira já havia falado, de forma geral, sobre as vantagens de manter sua aplicação no cloud, mesmo quando o volume de requisições não é grande o bastante para se representar um gargalo, como é o nosso caso e pode ser visto pelos paineis de controle do GAE:

Gráfico de requisições por segundo no www.caelum.com.br

Requisições por segundo no www.caelum.com.br

Sobre o Google App Engine, em particular, vale ainda acrescentar que a administração é bastante simples, com relatórios e estatísticas apresentados de forma coerente, e também que é possível executar múltiplas versões da sua aplicação facilmente. O serviço ainda é beta e tem seus problemas ocasionais, mas, até o momento, tem se comportado de forma bastante satisfatória. Considere o uso do cloud para sua próxima aplicação e livre-se de boa parte da preocupação com hardware, grids, clusters e infraestrutura.

  • Share/Bookmark

Bancos de dados não relacionais e o movimento NoSQL

Por Nico Steppat em 30/10/09

Nas grandes aplicações web é cada vez mais comum a quantidade de informações ser enorme, e ainda temos uma certeza: amanhã teremos mais dados para armanezar. Como lidar com isso de maneira eficiente?

Muito se fala ultimamente sobre os novos bancos não relacionais. Houve um encontro inicial e a segunda conferência também já aconteceu. O movimento acabou ganhando o nome de NoSQL, até mesmo por que cada banco de dados tem uma maneira diferente de se escrever queries.

A grande motivação para NoSQL é resolver o problema de escalabildade dos bancos tradicionais. Pode ser muito caro ou/e complexo escalar um banco SQL.

Esse movimento está bastante enraizado no open source. Dá para perceber isso até mesmo pelos curiosos nomes dos projetos: Voldemort, MongoDB, Tokyo Tyrant e CouchDB.

Apesar de grande quantidade desses bancos serem open source, o movimento ganhou muita força com a publicação de dois papers sobre implementações proprietárias: o Google Bigtable (que a Caelum usa atualmente) e o Amazon Dynamo. Não por acaso são duas empresas que lidam com uma quantidade enorme informações. Outros grandes nomes participam do movimento NoSQL: Yahoo! (Hadoop com HBase, Sherpa), Facebook e Digg (Cassandra), LinkedIn (Voldemort), Mixi (Facebook do Japão) (Tokyo Cabinet) e a Engine Yard (MongoDB).

Muitos acham que esses bancos de dados escalam simplesmento por causa da ausência de um schema (schema free), logo não há verificação de integridade e de relacionamentos. Mas seria só isso? O MySQL, nos seus primórdios, quando não fazia tais verificações, ainda assim não era rápido como esses novos competidores. Quais são então os segredos para tanta escalabilidade?

O Amazon Dynamo se destacou por causa da forma como o sistema escala. Cada nó no cluster comunica com outros nós (p2p) e faz ativamente parte da partição/replicação. Não tem um single-point-of-failure, mas essa facilidade de escalar não vem sem custo.

Todos os novos bancos tem em comun que eles são key-value stores, ou seja salvam, como o nome sugere, um conjunto de enradas formadas por uma chave associada a um valor e o valor poderia ser de qualquer tipo, um binário ou string que está sendo salvo de forma denormalizada (schema-free). Diferentemente dos bancos SQL não existe uma esquema forte. Essa abordagem facilita a distribuição dos dados entre vários servidores onde cada servidor possui apenas uma fatia dos dados (shard).

O CouchDB é um dos mais famosos no time dos key-value stores. Ele usa documentos para definir uma estrutura no banco, armazenando uma chave associada ao um documento. Um documento é apresentado como JSON. Por exemplo:

{
  "Subject": "Bancos não relacionais"
  "Author": "Nico Stepat"
  "PostedDate": "10/15/2009"
  "Tags": ["database", "nosql", "rest"]
}

Repare a estrutura dos dados é definido através da aplicação, o CouchDB não exige nada, apenas um documento JSON.

Talvez o CouchDB ficou famoso por causa da simples API REST e do uso do JSON, ou da interface grafica bonita ou por causa dos views interessantes usando Map-Reduce ou da replicação Multi-Master ou por que foi escrito em Erlang (como esse e esse também). Seja que for, a promessa principal do NoSQL – sendo escalável – o CouchDB não compriu ainda. Ele não é distribuído sozinho, e precisa de ajuda externa para tal.

Outra forma de dar alguma estrutura aos dados ficou famosa por causa do Google Bigtable. A idéia é não salvar os dados em linhas como estamos acustomados pelos bancos relacionais. Os dados serão salvos através de colunas. Veja a diferença:

Row-Oriented (3 rows presentes – Nome, Salário, Data):

João,1432.00,15/10/2009
Maria,1511.00,13/10/2009
Pedro,1721.00,01/10/2009

Column-Oriented (mesmo exemplo):

João,Maria,Pedro
1432.00,1511.00,1721.00
15/10/2009,13/10/2009,01/10/2009

No column-oriented vem primeiro TODOS os dados da primeira coluna Nome, depois a segunda coluna Salario e por último a coluna Data.

E isso altera algma coisa? Para o desenvolvedor que vai utilizar o banco de dados, a idéia é que isso seja transparente, mas para que desenvolveu o banco, há enormes melhorias.

Isso não é muito vantajoso quando for salvo apenas um registro, como cada coluna tem que ser accessada separadamente. Também complica mais na recuperação de um registro específico (random-access) pelo mesmo motivo. Aqui a abordagem row-oriented tem vantagens.

Por outro lado, usando colunas, podemos empacotar os dados melhor já que os dados semelhantes, de mesmo formato, estão próximos um do outro. Gravando dados empacotados em BDs traz grandes vantagens, porque podemos recuperar e armanezar mais informações em menos tempo.

Com colunas também podemos aplicar projeções sobre os dados mais fácil. A segunda vantagem é importante principalmente para sistemas OLAP (online analytic process) que usam esse tipo de pesquisas pesadamente.

Banco de dados orientados a coluna vão além de um simples key-value store. Eles representam normalmente um array/hash de 4 ou 5 dimensões, usando ou adaptando o modelo proposto pelo BigTable.

Primeiramente, também temos tabelas só que eles chamem column-families. Como o nome indica um column-family são váras colunas. Quais são essas colunas, a aplicação define.

Cada coluna salva um valor. O grupo de colunas dentro de uma familia é acessível atraves de uma chave (row-key). O esquema fica:

  • columnFamily – parecido com uma tabela (tabela de colunas)
  • rowKey – ID do grupo de colunas
  • column – nome da coluna
  • value – valor a salvar

<columnFamily>.<rowKey>.<column> = <value>

Por exemplo, para apresentar dados sobre um DVD com a ID 234:

    <dvds>.<234>.<nome> = 'Beleza americana'
    <dvds>.<234>.<ator_principal> = 'Kevin Spacey'
    <dvds>.<234>.<ano> = '1999'

ou para apresentar um usuário com joao.silva:

    <usuarios>.<joao.silva>.<nome> = 'Joao Silva'
    <usuarios>.<joao.silva>.<email> = 'joao.silva@foo.com'

e o relacionamento:

   <usuarios_dvds>.<joao.silva>.<234> = ‘data que ele comprou’

Normalmente é salvo uma data automaticamente, ou seja, a estrutura completa fica:

   <columnFamily>.<rowKey>.<column>.<timestamp> = <value>

Os bancos orientados a coluna não oferecem joins entre column-families nem chaves compostas. A Google AppEngine, por exemplo, não aceita chaves compostas porque usa o BigTable por baixo. Existem pequenas diferenças entre implementações orientadas a coluna.

Seria o “início do fim” dos bancos relacionais? Aderindo a um banco de dados não relacional muita da responsibilidade de cuidar dos dados fica a cargo da aplicação. É ela que define como funcionam e como se relacionam os documentos. O banco fica com certeza mais simples, escalavél e rápido, mas perdemos as conhecidas garantias dos bancos relacionais. Como toda decisão arquitetural, escolher por bancos de dados não relacionais apresenta um trade off: ACID ou BASE ou alguma coisa no meio. Vale a pena? Cada caso deve ser estudado com cuidado. Algumas aplicações usam bancos de dados não relacionais para uma leitura e escrita temporária, atualizando um banco relacional de tempos em tempos, tirando vantagem das duas estratégias.

Semana que vem, no Caelum Day in Rio conversaremos sobre muitos outros detalhes do movimento NoSQL. Nos encontramos lá!

  • Share/Bookmark

O cloud computing é inevitável?

Por Paulo Silveira em 28/10/09

A Caelum.com.br roda agora no cloud. Por que? Vale a pena? Temos tantos acessos assim?

Entre os exemplos clássicos do bom uso do cloud temos o sucesso do New York Times: eles conseguiram digitalizar em PDF mais de 4 terabytes de edições do jornal muito antigas (1851-1922) usando 100 máquinas e gastando apenas 240 dólares! O cloud é perfeito para situações em que você precisa de muito desempenho e recursos por um curto período de tempo.

Essa definitivamente não é a necessidade da Caelum em rodar em um cloud. Qual seria então?

Temos várias opções para você ter seu próprio cloud: através do TerraCotta, pelo VMWare ou pelo Grid Gain. Usando esses softwares você mesmo precisa prover sua infraestrutura de máquinas, o que pode ser trabalhoso. Um nível mais abstrato e encapsulado seria usar o EC2 da Amazon: ele já fornece as máquinas, facilitando muito a manutenção, e cobra por tempo de processamento (eliminando o gasto inerente ao tempo ocioso que suas máquinas poderiam ficar). No Brasil, a Locaweb foi a pioneira ao oferecer esse serviço.


cloud locaweb

Por último, temos hosts que já provêm tudo para você: a infraestrutura e o software para permitir a escalabilidade. O Google App Engine é um desses hosts.

Aqui, sim, entra a Caelum e entra também o interesse das empresas de hosting: se o cloud é mais barato para os fornecedores por compartilhar recursos ociosos, e ainda oferece altíssima escalabilidade e disponibilidade para os clientes, temos então um casamento de interesses. Essa relação ganha-ganha fortalece muito a tendência da adoção de uma arquitetura mais elástica. Os clientes também ganham uma enorme independência de hardware, terceirizando os grandes problemas que acabam acontecendo quando temos servidores dedicados: esses servidores não são à prova de bala. Para a Caelum, mesmo sem necessidade de rodar em 100 servidores, é interessante ter o poder de fazer o deploy de nossas aplicações Web sem ter de nos preocupar se “é suficiente X micros e Y de RAM para esta aplicação?”, por menor que ela seja.

O site da Caelum usava VRaptor 3 como controlador, JSP e taglibs na visualização e Hibernate para persistência. Para colocá-lo no cloud do Google App Engine, a única grande mudança que fizemos foi migrar a camada de persistência para o BigTable. Trata-se de um banco de dados não relacional proprietário, que possui uma API específica, mas também há uma implementação (não completa) de JPA para ele, o que facilita bastante a migração.

Desvantagens? O Google App Engine ainda está em beta, e já ficou fora do ar alguns momentos, em especial o serviço do DataStore (o BigTable). Há também o problema do cold start e do plugin para o Eclipse, que é um pouco lento para iniciar o servidor de testes: falaremos com detalhes sobre ambos em um outro post. Outra desvantagem é a dependência ao BigTable: como é um serviço proprietário, migrar para qualquer outro cloud vai necessitar de ajustes, mesmo que usando a JPA.

Respondendo o título do post: independente se o seu cloud é Amazon EC2, Google App Engine ou GridGain, algo é certo: ele traz vantagens tanto para os desenvolvedores quanto para o host, mostrando que a estratégia do cloud está cada vez mais solidificada.

Agradecimentos ao Pedro Matiello, Guilherme Silveira e Sérgio Lopes pela migração do site. Em breve, postaremos detalhes de como trabalhar na Google App Engine. Teremos uma palestra sobre Cloud computing no CaelumDay no Rio de Janeiro, esse 7 de novembro!

  • Share/Bookmark



Caelum | Ensino e Inovação
São Paulo: Rua Vergueiro, 3185, cj. 87, próximo ao Metrô Vila Mariana   |   Tel. (11) 5571-2751
Rio de Janeiro: Rua Senador Dantas, 80, cj. 307/308 - Centro   |   Tel. (21) 2220-4156 ou 2297-0033
Brasília: SCS Qd. 8 Bl. B-50, Sala 521 - Ed. Venâncio 2000   |   Tel. (61) 3039-4222