Back-End

27 mar, 2012

Sharding de banco de dados baseado em Hibernate Framework para aplicativos SaaS

Publicidade

O SaaS está mudando o modo como os aplicativos são projetados,
construídos, implementados e
operados. Uma diferença chave entre desenvolver um
aplicativo SaaS e desenvolver um aplicativo
corporativo genérico é que os aplicativos SaaS devem ser
multiarrendatários.

Outros requisitos principais de SaaS,
como segurança, customização, Arquitetura Orientada a Serviços (SOA)
e integração, também afetam a arquitetura do aplicativo SaaS.

Multiarrendamento refere-se à capacidade de um aplicativo de permitir
o alojamento de múltiplos
arrendatários em um único código de base e compartilhar
infraestrutura como
bancos de dados.

Há múltiplas opções de design para
ativar o banco de dados dedicado
a arquitetura de dados de multiarrendatários – para cada
arrendatário, esquema separado de banco de dados compartilhado
para cada arrendatário e esquema de banco de dados de
shard compartilhado para todos os
arrendatários.

Uma estratégia chave com custo reduzido é
ter um banco de dados compartilhado e uma
estratégia de esquema compartilhada para todos os
arrendatários.

Adotar essa abordagem apresenta
um grande desafio à escala de banco de dados conforme o
banco de dados é compartilhado entre todos os
arrendatários suportados pelo aplicativo SaaS. Uma
técnica simples para escalar um banco de dados compartilhado é através
de
sharding de banco de dados baseado no ID do
arrendatário.

Sharding de banco de dados refere-se ao particionamento
horizontal de um banco de dados, sendo que cada
partição individual é chamada de shard. Há alguns softwares
livres, bem como softwares proprietários, como
Hibernate Shards, Apache Slice, SQLAlchemy ORM e dbShards, que
oferecem o recurso de sharding.

Este artigo
abordará o Hibernate, já que ele é a estrutura ORM usada com mais
frequência no espaço do J2EE. Abordaremos
os problemas de escala de banco de dados de aplicativos SaaS usando
Hibernate Shards, que são uma
extensão do Hibernate, e que tornam disponíveis os recursos de
sharding para usuários do Hibernate.

Sharding de banco de dados para aplicativos SaaS

  1. Os aplicativos SaaS que adotam um banco de dados compartilhado e uma
    abordagem de esquema compartilhada devem ser projetados
    levando-se em consideração que precisarão ser escalados quando não
    puderem mais atender às métricas de desempenho
    de linha de base, como, por exemplo, quando muitos usuários tentam
    acessar o banco de dados
    simultaneamente ou quando o tamanho do banco de dados faz com que
    consultas e atualizações levem muito tempo
    para serem executadas. Uma forma de escalar um banco de dados
    compartilhado é por sharding. É
    a forma mais efetiva de escalar um banco de dados, já que as linhas em
    um esquema compartilhado são diferenciadas
    dos arrendatários – com base no ID do arrendatário. O banco de dados pode
    ser particionado facilmente de forma horizontal
    com base no ID do arrendatário. Isso torna mais fácil mover dados que
    pertencem a cada um dos arrendatários
    para partições individuais.
  2. Ao particionar dados de um aplicativo SaaS multiarrendatário,
    precisamos considerar
    fatores como degradação de desempenho devido ao aumento do número de
    usuários simultâneos ou aumento do tamanho do banco de dados devido ao
    fornecimento
    de múltiplos arrendatários. Este fator pode afetar as características de
    desempenho do banco de dados
    existente. Isso ajudará a selecionar as técnicas apropriadas de
    partição de acordo com o requisito de tamanho do banco de dados de um
    arrendatário individual, ou número de usuários de um arrendatário
    individual acessando o banco de dados
    simultaneamente. O sharding fornece muitas vantagens,
    como leituras e gravações mais rápidas no banco de dados,
    resposta a procura melhorada, tamanhos menores de tabela e distribuição
    de tabelas com base na necessidade.
  3. O sharding de banco de dados do Hibernate pode ser dinamizado para
    construção de aplicativos SaaS, adotando outras
    opções de arquitetura de dados multiarrendatária, como banco de dados
    dedicado para cada arrendatário, ou esquemas
    separados de banco de dados compartilhado para cada arrendatário. O
    sharding de banco de dados do Hibernate pode extrair o código do
    aplicativo
    conectando-se a múltiplos bancos de dados baseados em contexto de
    arrendatário para fornecer configuração de metadados e
    sharding de API – responsável por lidar com inserção de dados e leitura de dados de
    múltiplos bancos de dados de acordo com o
    contexto do arrendatário.

Implementação de sharding usando Hibernate Shards

O Hibernate Shards é uma estrutura projetada para encapsular e
minimizar essa complexidade
adicionando suporte a particionamento horizontal para o
Hibernate
Core. Ele minimiza a complexidade de implementação
quando se
trabalha com dados de shards.

O objetivo principal do
Hibernate Shards é
ativar o aplicativo para que você possa consultar e
transacionar nos
conjuntos de dados de shards usando a API padrão do
Hibernate Core. Essa abordagem fornece
os seguintes benefícios:

  • Fornece soluções não invasivas para suportar sharding de banco de
    dados, enquanto os aplicativos existentes
    ativados por SaaS são construídos usando o Hibernate
  • Permite que os aplicativos SaaS existentes que usam Hibernate, mas
    que ainda não precisam de sharding, adotem
    a solução sem uma refatoração grande se e quando eles atingirem esse
    estágio.

O Hibernate Shards fornece:

  • As extensões sensíveis a shard de interfaces são fornecidas pelo
    Hibernate Core, de modo que o código não
    precisa saber que está interagindo com um conjunto de dados de shard. As
    extensões sensíveis a shard que atuam como um
    mecanismo de sharding são as seguintes:
  1. org.hibernate.shards.ShardedSessionFactory
  2. org.hibernate.shards.criteria.ShardedCriteria
  3. org.hibernate.shards.session.ShardedSession
  4. org.hibernate.shards.query.ShardedQuery
  • Interfaces para implementação de estratégia de sharding específica do aplicativo. O mecanismo de sharding descrito acima usa a estratégia de sharding fornecida pelo aplicativo.
  1. org.hibernate.shards.strategy.access.ShardAccessStrategy: Com esta estratégia, o Hibernate determina como aplicar operações do banco de dados pelos múltiplos shards. Sempre que uma consulta for executada, essa estratégia é mencionada. Duas implementações padrão, SequentialShardAccessStrategy eParallelShardAccessStrategy, já são fornecidas.
  2. org.hibernate.shards.strategy.resolution.ShardResolutionStrategy: Esta estratégia é usada para determinar o conjunto de shards no qual um objeto com um certo ID pode residir. A resolução do shard é vinculada à geração de ID para a qual o Hibernate fornece inúmeras estratégias, como geração de UUID no nível do aplicativo e geração distribuída de encadeamento.
  3. org.hibernate.shards.strategy.selection.ShardSelectionStrategy: Esta estratégia nos permite determinar o shard no qual um novo objeto deve ser criado. Com base na necessidade do aplicativo, podemos decidir pela implementação dessa interface. Embora o Hibernate forneça inicialmente uma implementação pronta para uso com alternância de eventos, precisamos fornecer uma implementação baseada no ID do arrendatário para aplicativos baseados em SaaS.
  • Suporte a shards virtuais para facilitar o impacto associado à
    resharding.
  1. Os aplicativos SaaS devem ser projetados levando-se em consideração que
    eles podem ter que escalar um banco de dados no
    futuro, com fornecimento aumentado de novos arrendatários para o
    aplicativo. Quando estava projetando, você pode ter acreditado
    que dois bancos de dados eram suficientes, mas quando um conjunto de
    dados do aplicativo SaaS cresce além da
    capacidade dos bancos de dados originalmente alocados
    para o aplicativo, torna-se necessário adicionar bancos de dados. Muitas
    vezes, desejamos
    redistribuir os dados pelos shards. Resharding é um problema complicado,
    e tem o potencial de causar
    complicações graves no gerenciamento do seu aplicativo de produção, se
    não foi considerado durante o planejamento.
  2. Com shards virtuais, podemos apontar múltiplos shards virtuais para
    shards físicos em
    um arquivo de configuração do Hibernate. Isso permite a definição de
    muitos shards virtuais, que podem mapear
    até dois ou três shards físicos apenas. Com o crescimento do projeto, a
    configuração pode ser mudada
    para adicionar mais shards físicos.
  3. Ao projetar e implementar aplicativos SaaS usando Hibernate Framework
    para Persistence,
    devemos criar shards virtuais para cada um dos arrendatários, que podem
    ser mapeados para um banco de dados físico previsto.
    Com o fornecimento de novos arrendatários, podemos criar mais um shard
    virtual e mapeá-lo para os mesmos
    shards físicos previstos ou mais shards físicos.

Construção de um aplicativo de e-commerce multiarrendatário usando Hibernate Shards

Vamos ver como podemos dinamizar esse conceito ao construir um
aplicativo de e-commerce
multiarrendatário. Vamos presumir que o aplicativo de e-commerce
multiarrendatário está usando estratégia de esquema compartilhado e de
banco de dados compartilhado
para atender os requisitos de dados de multiarrendamento.

Conforme a
quantia de dados aumenta, precisamos
escalar o banco de dados para tratar novos arrendatários. Neste
exemplo, temos dois arrendatários que compartilham um único banco de
dados, e
quando arrendatários adicionais entram, seus dados são movidos para
diferentes shards de banco de dados baseados no
ID do arrendatário.

Esse aplicativo também usa a opção de shard
virtual
para cuidar de requisitos futuros de escalabilidade de banco de
dados. O aplicativo possui
informações de perfil do cliente para arrendatários localizados em
múltiplos locais geográficos
na tabela de cliente.

Configuração:

  • Pré-requisitos

A seguir estão as principais bibliotecas e ferramentas usadas para
testar essa amostra:

  1. hibernate-shards.jar – V3.2.x
  2. hibernate3.jar – V3.0.0.Beta2
  3. hsqldb.jar – V1.8.0
  4. Eclipse IDE – V3.5 (a amostra deve funcionar em versões anteriores sem
    qualquer alteração)
  • Esquema do banco de dados da tabela de cliente

Tabela 1. Tabela de cliente

Column name Column type Remarks
CUSTOMER_ID DECIMAL(40,0) PRIMARY KEY
TENANT_ID VARCHAR(50)
FIRST_NAME VARCHAR(50)
LAST_NAME VARCHAR(50)
LOCATION VARCHAR(50)
UPDATE_TIME TIMESTAMP

Um modelo de objeto Java correspondente à tabela é criado. Um fragmento
dele
é mostrado abaixo:

Listagem 1. Listagem de código de amostra do modelo de objeto de cliente

public class Customer {
private BigInteger customerId;
private String tenantId;
private String firstName;
private String lastName;
private String location;
private Date updateTime;

// Getters and Setters
}

Depois definimos o Customer.hbm.xml correspondente:

Listagem 2. Listagem de código de amostra do arquivo de configuração Customer.hbm.xml

<hibernate-mapping package="com.sample.shards.bean">
<class name="Customer" table="CUSTOMER">
<id name="customerId" column="CUSTOMER_ID" type="big_integer">
<generator class="org.hibernate.shards.id.ShardedUUIDGenerator"/>
</id>
<property name="tenantId" column="TENANT_ID"/>
<property name="firstName" column="FIRST_NAME"/>
<property name="lastName" column="LAST_NAME"/>
<property name="location" column="LOCATION"/>
<property name="updateTime" type="timestamp" column="UPDATE_TIME"/>
</class>
</hibernate-mapping

Em seguida, configuramos os xmls de configuração do Hibernate.
Inicialmente, em um banco de dados compartilhado com
abordagem de esquema compartilhado, um único
hibernate.cfg.xm
l é configurado.

Conforme escalamos o número de instâncias do banco
de dados, o hibernate.cfg.xml é
adicionado para cada instância de banco de dados
usada no shard.

Esses arquivos
de configuração do Hibernate diferem nos detalhes de
conexão, factory de sessão e
ID de shard. A tabela abaixo mostra o
hibernate0.cfg.xml
um dos três arquivos de configuração usados no
exemplo.

Listagem 3. Listagem de código de amostra do arquivo de configuração Hibernate0.cfg.xml

<hibernate-configuration>
<session-factory name="HibernateSessionFactory0">
<property name="dialect">org.hibernate.dialect.HSQLDialect</property>
<property name="connection.driver_class">org.hsqldb.jdbcDriver</property>
<property name="connection.url">jdbc:hsqldb:mem:shard0</property>
<property name="connection.username">sa</property>
<property name="connection.password"></property>
<property name="hibernate.connection.shard_id">0</property>
<property name="hibernate.shard.enable_cross_shard_relationship_checks">
true
</property>
</session-factory>
</hibernate-configuration>

Como podemos ver acima, a configuração do Hibernate Shards é bem semelhante à do Hibernate. A principal diferença é a adição de propriedades hibernate.connection.shard_id ehibernate.shard.enable_cross_shard_relationship_checks.

Note também que o factory de sessão e o ID de shard devem representar o shard de forma única. O hibernate1.cfg.xml e o hibernate2.cfg.xml são configurados de forma semelhante.

Observe que qualquer banco de dados pode ser usado. No exemplo, usamos um HSQL Database Engine Server para uma instância de dados e HSQL Database Engine In-Memory para outras duas instâncias de banco de dados.

Construção de estratégia de sharding

Agora definimos as estratégias de shard a serem usadas no aplicativo. Criamos uma ShardSelectionStrategy chamada CustomerShardSelectionStrategy, que implementa a ShardSelectionStrategy que decide o shard no qual a linha é restaurada com base no ID do arrendatário.

O ID de shard será mapeado considerando-se o mapa de ID de shard virtual configurado nas últimas etapas. Caso um mapa de ID de shad virtual não seja fornecido (ele é opcional), o mapa de ID de shard físico é usado como padrão.

Listagem 4. Listagem de código de amostra de CustomerShardSelectionStrategy

public class CustomerShardSelectionStrategy implements ShardSelectionStrategy {
public ShardId selectShardIdForNewObject(Object obj) {
if (obj instanceof Customer) {
return getShardId(((Customer) obj).getTenantId());
}
throw new IllegalArgumentException();
}

public ShardId getShardId(String tenantId) {
int shardId = 0;
if(tenantId.equalsIgnoreCase("Tenant1") || tenantId.equalsIgnoreCase("Tenant2"))
shardId = 0;
else if(tenantId.equalsIgnoreCase("Tenant3"))
shardId = 1;
else if(tenantId.equalsIgnoreCase("Tenant4"))
shardId = 2;
else if(tenantId.equalsIgnoreCase("Tenant5"))
shardId = 3;
return new ShardId(shardId);
}
}

Usamos implementações AllShardsShardResolutionStrategy e SequentialShardAccessStrategy fornecidas pelo Hibernate Shards para as outras estratégias. A seguir, construiremos a Factory de Estratégia de Shard com base em implementações de estratégia de shard.

Listagem 5. Construção da Factory de Estratégia de Shard

ShardStrategyFactory buildShardStrategyFactory() {
return new ShardStrategyFactory() {
public ShardStrategy newShardStrategy(List<ShardId> shardIds) {
ShardSelectionStrategy pss = new CustomerShardSelectionStrategy();
ShardResolutionStrategy prs = new AllShardsShardResolutionStrategy(shardIds);
ShardAccessStrategy pas = new SequentialShardAccessStrategy();
return new ShardStrategyImpl(pss, prs, pas);
}
};
}

Depois obtemos a ShardSessionFactory passando os arquivos de configuração hibernate*.cfg.xml e customer.hbm.xml, também criamos quatro shards virtuais para requisitos futuros, que são mapeados no momento para três shards físicos.

Como discutido na seção anterior, criamos um mapa de shard virtual para cuidar de requisitos futuros de escala de banco de dados. Como se pode ver abaixo, configuramos os shards virtuais dois e três para mapear o mesmo shard físico dois no momento.

Posteriormente, quando um novo shard físico for adicionado, somente esse mapeamento e o arquivo de configuração do Hibernate precisam ser fornecidos. Nenhuma outra alteração precisará ser feita no restante do código.

Listagem 6. Listagem do código de amostra para criar factory de sessão

public SessionFactory createSessionFactory() {
Configuration config = new Configuration();
config.configure("hibernate0.cfg.xml");
config.addResource("customer.hbm.xml");

List shardConfigs = new ArrayList();
shardConfigs.add(buildShardConfig("hibernate0.cfg.xml"));
shardConfigs.add(buildShardConfig("hibernate1.cfg.xml"));
shardConfigs.add(buildShardConfig("hibernate2.cfg.xml"));

Map<Integer, Integer> virtualShardMap = new HashMap<Integer, Integer>();
virtualShardMap.put(0, 0);
virtualShardMap.put(1, 1);
virtualShardMap.put(2, 2);
virtualShardMap.put(3, 2);

ShardStrategyFactory shardStrategyFactory = buildShardStrategyFactory();
ShardedConfiguration shardedConfig = new ShardedConfiguration(
config,
shardConfigs,
shardStrategyFactory,
virtualShardMap);
return shardedConfig.buildShardedSessionFactory();
}

Realização de operações do banco de dados usando Hibernate

Finalmente, realizamos operações normais no Hibernate usando a instância SessionFactory recuperada.

Listagem 7. Listagem do código de amostra para realizar operações do banco de dados

Session session = sessionFactory.openSession();
try {
session.beginTransaction();

// Add new records to the database
Customer customer = new Customer();
customer.setFirstName("John");
customer.setLastName("Doe");
customer.setLocation("New York City");
customer.setTenantId("Tenant1");
customer.setUpdateTime(new Date());
session.save(customer);

// Similarly add records for other Tenants...
session.getTransaction().commit();

// List the customers from the database
List customers = getData();
for (int i = 0; i < customers.size(); i++) {
Customer customer = (Customer) customers.get(i);
System.out.println(customer.toString());
}
} finally {
session.close();
}

Saída de código

Ao executar o aplicativo, o resultado a seguir é exibido no console.

Listagem 8. Resultado de amostra exibido no console 

Customer Id: 8006718453799964144490040897437701, Tenant Id: Tenant3, First Name: James, 
Last Name: Bond, Location: London, Update Time: 2010-03-16 09:28:33.526
Customer Id: 13199015312334791773020537226657798, Tenant Id: Tenant4, First Name: Vijay,
Last Name: Raj, Location: Bangalore, Update Time: 2010-03-16 09:28:33.526
Customer Id: 18391312170869619401551033555877895, Tenant Id: Tenant5, First Name: Brett,
Last Name: Lee, Location: Melbourne, Update Time: 2010-03-16 09:28:33.526
Customer Id: 2814421595265136515959544563040259, Tenant Id: Tenant1, First Name: John,
Last Name: Doe, Location: New York City, Update Time: 2010-03-16 09:28:33.401
Customer Id: 2814421595265136515959544568217604, Tenant Id: Tenant2, First Name: Jane,
Last Name: Dane, Location: San Francisco, Update Time: 2010-03-16 09:28:33.526

A tabela de cliente em diferentes bancos de dados terá os dados a seguir.

Tabela 2. Tabela de cliente no banco de dados 1

Customer_ID Tenant_ID First_Name Last_Name Local Update_Time
2814421595265136515959544563040259 tenant1 John Doe New York City 2010-03-16 09:24:31.839
2814421595265136515959544568217604 tenant2 Jane Dane San Francisco 2010-03-16 09:24:31.964

Tabela 3. Tabela de cliente no banco de dados 2

Customer_ID Tenant_ID First_Name Last_Name Local Update_Time
8006718453799964144490040897437701 tenant3 James Bond London 2010-03-16 09:28:33.526

Tabela 4. Tabela de cliente no banco de dados 3

Customer_ID Tenant_ID First_Name Last_Name Local Update_Time
13199015312334791773020537226657798 tenant4 Vijay Raj Bangalore 2010-03-16 09:28:33.526
18391312170869619401551033555877895 tenant5 Brett Lee Melbourne 2010-03-16 09:28:33.526

Agora você já pode fazer o download do código de amostra completa.

Limitação atual do Hibernate Shards Framework

Algumas das principais limitações do Hibernate Shards Framework (V3.0.0.Beta2) são as seguintes:

  • Alguns dos métodos fornecidos pela ShardedSessionImpl,
    ShardedCriteriaImple pela ShardedQueryImpl,
    que usamos raramente, ainda não
    estão completamente implementados
  • No momento, a estrutura de sharding não pode ser usada com HQL. Ela
    só pode ser usada com SQL.
  • O Hibernate Shards não fornece suporte a transações distribuídas
    dentro de um ambiente
    não gerenciado. Se o aplicativo requer transações
    distribuídas,
    precisamos fazer o plug-in de uma implementação de
    gerenciamento
    de transação que suporte transações distribuídas.
  • Atualmente, o Hibernate Shards Framework não suporta gráficos de
    objeto de compartilhamento cruzado.
  • O mecanismo de configuração atual não funciona enquanto estiver
    configurando SessionFactory através de JPA

Conclusão

Este artigo forneceu uma visão geral dos requisitos de sharding de banco
de dados de aplicativos
SaaS, bem como conceitos de sharding de banco de dados e
viabilidade de
implementação de sharding usando a estrutura de software
livre do Hibernate Shards.

Tratamos também das limitações atuais do Hibernate
Shard Framework. Este artigo ajudará arquitetos e
desenvolvedores a dinamizarem
a estrutura do Hibernate Shards na camada de dados de
uma forma efetiva, construindo
e projetando, ao mesmo tempo, verdadeiros aplicativos
SaaS multiarrendatários baseados em J2EE.

Recursos

Aprender

Obter produtos e tecnologias

***

artigo publicado originalmente no developerWorks Brasil, por Chetan Kothari e Arun Viswanathan

Chetan Kothari é o arquiteto principal do Centro de Excelência do Java 2
Platform, Enterprise Edition (J2EE)
da Infosys Technologies Limited, líder global em serviços de
consultoria de negócios
e TI. Ele possui mais de nove anos de experiência no
desenvolvimento da estrutura
do aplicativo J2EE, definindo, projetando e implementando
soluções de TI
de missão crítica em larga escala em uma série de segmentos de
mercado. É formado em
engenharia eletrônica pela Universidade de Nagpur.

Arun Viswanathan trabalha no Centro de Excelência de Java Enterprise no
SETLabs da Infosys Technologies. Ele possui mais de sete anos de
experiência em tecnologias Java e Java EE. Atualmente, está envolvido em
design e consultoria para aplicativos baseados em SaaS.