메인 컨텐츠로 가기

developerWorks 이용 약관에 동의하시는 경우 제출을 클릭하십시오. 이용 약관 보기.

developerWorks에 처음 로그인하면 developerWorks프로파일이 생성됩니다.귀하의 프로파일에서 동의하신 내용이 공개되지만 이 사항은 언제든지 변경 가능합니다. 귀하의 성명(숨김으로 체크되어 있어도 표시됩니다)과 디스플레이 이름은 게시한 컨텐츠나 사이트 엑세스시 표시됩니다.

모든 정보가 안전하게 전송되었습니다.

  • 닫기 [x]

처음 developerWorks에 로그인할 때 프로파일이 작성되므로, 이를 위해 디스플레이 이름을 선택해야 합니다. 선택하신 디스플레이 이름은 developerWorks에 게시한 컨텐츠에 표시됩니다.

3글자 이상 31글자 이하의 길이로 사용 가능합니다. dW커뮤니티 내에서는 보안상 이메일주소를 제외한 다른 이름을 지정하셔야 합니다.

developerWorks 이용 약관에 동의하시는 경우 제출을 클릭하십시오. 이용 약관 보기.

모든 정보가 안전하게 전송되었습니다.

  • 닫기 [x]

자바 개발 2.0:: Hibernate Shards를 이용한 샤딩

관계형 데이터베이스를 위한 수평적 확장성

Andrew Glover, 대표, Stelligent Incorporated
Andrew Glover 사진
Andrew Glover는 Stelligent Incorporated의 사장이다. 회사들이 코드 품질을 일찍 그리고 자주 모니터할 수 있게 하는 효과적인 개발자 테스팅 전략과 지속적 통합 기법으로 소프트웨어 품질 문제를 해결하는 것을 돕고 있다. Andy의 저서 목록은 그의 블로그를 보라.

요약:  샤딩이 모든 이를 위한 것은 아니지만, 관계형 시스템이 큰 데이터의 요구를 충족시킬 수 있는 한 가지 방법입니다. 일부 상점의 경우, 샤딩은 데이터 확장성이나 시스템 성능을 희생시키지 않고 신뢰할 수 있는 RDBMS를 올바른 상태로 유지할 수 있음을 의미합니다. Java development 2.0 시리즈의 이 기사에서 샤딩이 작동할 때와 그렇지 않을 때를 파악한 후, 테라바이트 단위의 데이터를 처리할 수 있는 간단한 애플리케이션을 샤딩해 보십시오.

이 연재 자세히 보기

원문 게재일:  2010 년 8 월 31 일 번역 게재일:   2010 년 12 월 14 일
난이도:  중급 원문:  보기 PDF:  A4 and Letter (64KB | 15 pages)Get Adobe® Reader®
페이지뷰:  4290 회
의견:  


관계형 데이터베이스가 단일 테이블에서 테라바이트 단위의 데이터를 저장하려 할 때, 전체적인 성능은 일반적으로 떨어진다. 그 데이터를 전부 인덱싱하려면 읽기 작업에 큰 대가를 치러야 함이 분명하지만, 쓰기 작업 역시 그에 못지않다. NoSQL 데이터 저장소는 특히 큰 데이터(Google의 Bigtable을 생각할 것)를 저장하기에 적합하지만, NoSQL은 명백히 비관계형 데이터베이스이다. ACID-ity와 관계형 데이터베이스의 견고한 구조를 선호하는 개발자나 이를 필요로 하는 프로젝트의 경우, 샤딩이 호응을 이끌어낼 대안이 될 수 있다.

데이터베이스 파티셔닝의 한 파생물인 샤딩은 기본 데이터베이스 기술은 아니고, 애플리케이션 레벨에서 발생한다. 다양한 샤딩 구현 중에서도, Hibernate Shards가 아마 Java™ 기술의 세계에서 가장 잘 알려져 있을 것이다. 이 매력적인 프로젝트에서는 논리 데이터베이스로 맵핑되는 POJO를 사용하여 샤딩된 데이터세트로 다소 원활하게 작업할 수 있다("다소"라고 표현한 부분은 간단히 설명할 것임). Hibernate Shards를 사용할 때, POJO를 샤드에 명확하게 맵핑하지 않아도 된다. Hibernate 방식으로 일반적인 관계형 데이터베이스에서와 같이 POJO를 맵핑한다. Hibernate Shards는 로우 레벨 샤딩 작업을 자동으로 관리한다.

지금까지 이 시리즈에서는 레이스와 러너의 유사점을 바탕으로 하는 간단한 도메인을 사용하여 다양한 데이터 스토리지 기술에 대해 설명했다. 이번 달에는 이런 익숙한 예제를 사용하여 실용적인 샤딩 전략을 소개한 후 Hibernate Shards에서 이를 구현할 것이다. 샤딩과 관련된 작업의 방향이 꼭 Hibernate와 관련되는 것은 아니다. 사실, Hibernate Shards를 위한 코딩은 쉬운 부분이다. 진짜 어려운 작업은 무엇을 어떻게 샤딩할 것인지 파악하는 것이다.

이 시리즈의 정보

처음 Java 기술이 발표된 이후로 Java를 개발하는 과정은 급속도로 변화되었다. 오픈 소스 프레임워크와 신뢰할 수 있는 임대용 전개 인프라 덕택에 Java 애플리케이션을 신속하고 저렴하게 어셈블하고 테스트하고 유지할 수 있게 되었다. 이 시리즈에서 Andrew Glover는 이러한 새로운 Java 개발 패러다임을 가능하게 하는 다양한 기술과 도구를 탐구한다.

샤딩 개요

데이터베이스 파티셔닝은 테이블의 행을 데이터 논리적 부분에 의해 더 작은 그룹들로 나누는 본래 관계형 프로세스이다. 예를 들어, 타임스태프를 기반으로 하는 foo라는 이름의 큰 테이블을 파티셔닝하는 경우, 2010년 8월에 대한 모든 데이터는 파티션 A로 이동하고 그 이후의 모든 데이터는 파티션 B로 이동하는 등으로 이루어진다. 개별 파티션에 있는 더 작은 데이터세트를 대상으로 하기 때문에, 파티셔닝은 읽기 및 쓰기 작업을 더 빨리 수행하는 효과가 있다.

파티셔닝은 항상 사용할 수 없고(MySQL이 버전 5.1까지는 이를 지원하지 않음), 상용 시스템으로 이 작업을 수행하는 비용이 너무 비쌀 수 있다. 더욱이, 대부분의 파티셔닝 구현은 같은 실제 시스템에 데이터를 저장하므로, 여전히 하드웨어의 제한에 바인딩된다. 또한, 파티셔닝은 하드웨어의 신뢰성을 해결하지 않거나 신뢰성이 부족하다. 따라서 다양한 영리한 사람들이 확장할 수 있는 새로운 방법을 찾기 시작했다.

샤딩은 본질적으로 데이터베이스 레벨에서 파티셔닝한다. 그래서 테이블의 행을 데이터 조각으로 나누지 않고, 데이터베이스 자체가 (보통 다른 시스템들로) 논리 데이터 요소로 분할된다. 즉, 샤딩은 테이블을 더 작은 청크로 분할하지 않고, 전체 데이터베이스를 더 작은 청크로 분할한다.

샤딩을 위한 표준 예제는 지역별로 전 세계의 고객 데이터를 저장하는 대형 데이터베이스의 분할에 기반을 두고, 미국 고객의 경우 샤드 A, 아시아 고객의 경우 샤드 B, 유럽 고객의 경우 샤드 C 등이다. 샤드 자체는 다른 시스템에 있고 각 샤드는 고객 환경 설정 또는 주문 히스토리와 같은 모든 관련 데이터를 유지한다.

(파티셔닝과 같은) 샤딩의 이점은 큰 데이터를 압축한다는 점이다. 개별 테이블은 각 샤드에서 더 작고, 이는 더 빠른 읽기 및 쓰기 작업을 고려한 것으로, 결국 성능을 높여준다. 한 샤드가 예기치 않게 실패하더라도 다른 샤드는 여전히 데이터 서비스를 제공할 수 있기 때문에, 샤딩이 신뢰성도 개선하는 것으로 생각할 수 있다. 그리고 샤딩은 애플리케이션 계층에서 완료되기 때문에, 일반적인 파티셔닝을 지원하지 않는 데이터베이스에 대해 이 작업을 수행할 수 있다. 금전적 비용 역시 절감될 수 있다.


샤딩 및 전략

대부분의 기술과 마찬가지로, 샤딩에는 몇 가지 상충 관계가 수반된다. 샤딩은 기본 데이터베이스 기술이 아니기 때문에(즉, 애플리케이션에서 샤딩을 구현해야 함), 시작하기 전에 샤딩 전략을 맵핑해야 할 것이다. 기본 키와 크로스 샤드 쿼리는 모두 샤딩을 할 때 중요한 역할을 하며, 주로 할 수 없는 일을 정의한다.

기본 키
샤딩은 여러 데이터베이스를 활용하며, 모든 데이터베이스는 다른 데이터베이스를 인식하지 못한 채 자율적으로 작동한다. 결과적으로, (자동 기본 키 생성을 위한 것과 같이) 데이터베이스 시퀀스에 의존하는 경우, 동일한 기본 키가 데이터베이스 세트 전체에 표시될 것이다. 분산 데이터베이스에서 시퀀스를 조정할 수 있지만, 그렇게 하면 시스템 복잡도가 증가한다. 중복 기본 키가 발생하지 않도록 하는 가장 안전한 방법은 (어떤 식으로든 공유 시스템을 관리하게 될) 애플리케이션에서 키를 생성하도록 하는 것이다.

크로스 샤드 쿼리
대부분의 샤딩 구현(Hibernate Shards 포함)에서는 크로스 샤드 쿼리를 허용하지 않는데, 이는 곧 다른 샤드에서 두 개의 데이터 세트를 사용할 경우 길이를 증가시켜야 한다는 뜻이다. (흥미롭게도, Amazon의 SimpleDB 역시 상호 도메인 쿼리를 금지한다.) 예를 들어, Shard 1에 미국 고객을 저장하는 경우 그 곳의 관련 데이터도 전부 저장해야 한다. Shard 2에 그 데이터를 저장하려면 일이 복잡해지므로, 시스템 성능이 저하될 수 있다. 이 상황 역시 앞서 지적한 사항과 관계되어 있다. 아무튼 결국 크로스 샤드 결합을 수행해야 하는 경우, 중복 가능성을 없애는 방식으로 키를 관리하는 것이 좋다.

분명히, 샤딩 전략을 완전히 고려한 후 데이터베이스를 설정해야 한다. 그리고 특정 방향을 선택한 후 그 방향에 다소 연결되므로, 데이터가 샤딩된 후에는 데이터를 이동하기 어렵다.

때이른 샤딩 회피

샤딩은 이 게임의 후반부에 사용하는 것이 가장 좋다. 때이른 최적화와 같이, 예상되는 데이터 증가를 바탕으로 한 샤딩이 재해 방지를 위한 방책이 될 수 있다. 성공적인 샤딩 구현은 시간의 경과에 따른 애플리케이션의 데이터 증가와 외삽법에 의한 미래의 추정을 어느 정도 이해하느냐에 달렸다. 데이터를 이미 샤딩했으면 데이터를 이동하기가 매우 어려울 수 있다.

전략의 예

샤딩은 선형 데이터 모델에 한정되기 때문에(즉, 다른 샤드에서 데이터를 손쉽게 결합할 수 없음), 데이터가 각 샤드에 대해 얼마나 논리적으로 구성될지에 대한 분명한 그림을 그리는 일부터 시작해야 한다. 일반적으로 도메인의 기본 노드에 집중하는 것이 가장 쉬운 방법이다. 전자상거래 시스템의 경우, 기본 노드는 주문 또는 고객 중 하나일 수 있다. 따라서 샤딩 전략을 위한 기초로 "고객"을 선택하면, 여전히 그 데이터를 이동하기 위해 샤딩을 선택해야 하지만 고객과 관련된 모든 데이터가 각 샤드로 이동된다.

고객에 대해 위치(유럽, 아시아, 아프리카 등)를 바탕으로 샤딩하거나 다른 것을 바탕으로 샤딩할 수 있다. 이는 스스로에게 달린 문제다. 하지만, 샤드 전략에는 모든 샤드 사이에 데이터를 고르게 분배하기 위한 방법이 포함된다. 샤딩의 전체적 아이디어는 큰 데이터 세트를 작은 데이터 세트로 나누는 것이므로, 특정 전자상거래 도메인에 유럽 고객 중 다수가 있고 미국 고객은 상대적으로 적은 경우 고객 위치를 바탕으로 샤딩하는 것은 합당하지 않다.


샤딩을 이용해 레이스에 돌입!

익숙한 필자의 레이싱 애플리케이션 예제로 다시 돌아가서, 레이스 또는 러너별로 샤딩할 수 있다. 이 경우, 레이스에 속한 러너가 구성 중인 도메인을 보기 때문에 레이스를 기준으로 샤딩할 것이다. 그래서 레이스가 도메인의 루트이다. 필자의 레이싱 애플리케이션은 무수한 러너와 함께, 길이가 다른 무수한 레이스를 보유하고 있기 때문에, 레이스 거리를 기준으로도 샤딩할 것이다.

이런 결정을 할 때, 필자는 이미 상충 관계를 받아들였다. 러너가 둘 이상의 레이스에 참여하는 경우 각 레이스가 다른 샤드에 있다면 어떨까? (대부분의 샤딩 구현과 마찬가지로) Hibernate Shards는 크로스 샤드 결합을 지원하지 않는다. 이런 약간의 불편함을 감수하면 러너(runner)가 여러 샤드에 있을 수 있다. 즉, 각 러너의 다양한 레이스가 있는 샤드에서 러너를 다시 작성할 것이다.

간단하게 하기 위해 두 개의 샤드를 작성할 것이며, 하나는 10마일 미만의 레이스용이고 다른 하나는 10마일 이상의 레이스용이다.


Hibernate Shards 구현

Hibernate Shards는 기존의 Hibernate 프로젝트와 거의 완벽하게 작동하도록 되어 있다. 유일한 결함은 Hibernate Shards에 몇 가지 특정한 정보가 필요하고 사용자가 어떤 작업을 해줘야 한다는 점이다. 이를테면, 샤드 액세스 전략, 샤드 선택 전략 및 샤드 해결 전략이 필요하다. 어떤 경우에는 기본 인터페이스를 사용할 수 있지만, 이들은 반드시 구현해야 하는 인터페이스이다. 다음 섹션에서 각각의 인터페이스를 따로 살펴보겠다.

ShardAccessStrategy

쿼리가 실행될 때, Hibernate Shards는 어떤 샤드가 첫 번째, 두 번째 등을 발견하는지 결정하기 위한 메커니즘이 필요하다. Hibernate Shards가 반드시 쿼리에서 찾고 있는 것(Hibernate Core 및 기본 데이터베이스가 실행하기 위한 것)을 이해할 필요는 없지만, 여러 샤드에 대해 쿼리를 실행해야 어떤 해답을 얻을 수 있음을 인식한다. 그래서 Hibernate Shards는 기본적으로 두 가지 논리적 구현을 제공한다. 하나는 해답이 반환되거나 모든 샤드에 대한 쿼리 실행이 완료될 때까지 샤드에 대해 순차적 메커니즘(한 번에 하나씩)에서 쿼리를 실행한다. 다른 구현은 한 번에 모든 샤드를 실행하기 위해 스레딩 모델을 사용하는 병렬 액세스 전략이다.

필자는 계속 문제를 단순화하고 순차적 전략을 활용할 것이며, 이를 적절히 SequentialShardAccessStrategy라는 이름으로 부르겠다. 이를 간단히 구성해보겠다.

ShardSelectionStrategy

새 오브젝트가 작성될 때(즉, Hibernate를 통해 새 Race 또는 Runner가 작성될 때), Hibernate Shards는 해당 데이터가 기록되어야 하는 샤드가 무엇인지 알아야 한다. 따라서 이 인터페이스를 구현하고 샤딩 논리를 코딩해야 한다. 기본 구현을 원하는 경우, 샤드에 데이터를 넣기 위한 라운드 로빈 전략을 사용하는 더빙된 RoundRobinShardSelectionStrategy 하나가 있다.

레이싱 애플리케이션의 경우, 레이스 거리를 기준으로 샤딩하는 동작을 제공할 필요가 있다. 따라서 ShardSelectionStrategy 인터페이스를 구현하고 selectShardIdForNewObject 메소드에서 Race 오브젝트의 distance를 바탕으로 샤딩하는 간단한 논리를 제공해야 한다. (Race 오브젝트를 간단히 보여주겠다.)

런타임에서, 필자의 도메인 오브젝트에서 save와 같은 메소드에 대한 호출이 이루어질 때 이 인터페이스의 동작이 사실은 Hibernate의 코어에서 활용된다.


목록 1. 간단한 샤드 선택 전략
import org.hibernate.shards.ShardId;
import org.hibernate.shards.strategy.selection.ShardSelectionStrategy;

public class RacerShardSelectionStrategy implements ShardSelectionStrategy {

 public ShardId selectShardIdForNewObject(Object obj) {
  if (obj instanceof Race) {
   Race rce = (Race) obj;
   return this.determineShardId(rce.getDistance());
  } else if (obj instanceof Runner) {
   Runner runnr = (Runner) obj;
   if (runnr.getRaces().isEmpty()) {
    throw new IllegalArgumentException("runners must have at least one race");
   } else {
    double dist = 0.0;
    for (Race rce : runnr.getRaces()) {
     dist = rce.getDistance();
     break;
    }
    return this.determineShardId(dist);
   }
  } else {
   throw new IllegalArgumentException("a non-shardable object is being created");
 }
}

 private ShardId determineShardId(double distance){
  if (distance > 10.0) {
   return new ShardId(1);
  } else {
   return new ShardId(0);
  }
 }
}

목록 1에서 볼 수 있듯이, 지속되고 있는 오브젝트가 Race인 경우에는 그 거리가 결정되고 그에 따라 샤드가 선택된다. 이 경우, 0과 1의 두 가지 샤드가 있는데, Shard 1에는 거리가 10마일 이상인 레이스, Shard 0에는 다른 모든 레이스가 있다.

Runner 또는 다른 오브젝트가 지속되고 있는 경우, 문제가 좀 더 복잡해진다. 필자는 세 개의 규정이 있는 논리 규칙을 코딩했다.

  • Runner는 해당 Race 없이는 존재할 수 없다.
  • 여러 개의 RaceRunner가 작성된 경우, Runner는 검색된 첫 번째 Race에 대해 샤드에서 존속된다. (그런데, 이 규칙은 미래에 부정적인 영향을 미친다.)
  • 다른 도메인 오브젝트가 저장되고 있는 경우, 지금은 예외가 발생한다.

이렇게 대부분의 어려운 작업이 완료되었기 때문에, 이마의 땀을 닦고 한숨 돌려도 된다. 필자가 캡처한 논리는 레이싱 애플리케이션의 성장에 따라 충분히 유연하지 않을 수 있지만, 이 데모의 목적으로는 효과가 있을 것이다.

ShardResolutionStrategy

키로 오브젝트를 검색할 때, Hibernate Shards는 어떤 샤드를 처음으로 실행할지 결정할 방법이 필요하다. SharedResolutionStrategy 인터페이스를 사용하여 안내한다.

앞서 언급했듯이, 샤딩에서는 사용자가 스스로 관리할 것이므로 기본 키를 꼭 알고 있어야 한다. 다행히도, Hibernate에서는 이미 키 또는 UUID 생성 기능을 확실히 제공하고 있다. 따라서 기본적으로 Hibernate Shards는 ID 생성기로 더빙된 ShardedUUIDGenerator를 제공하며, UUID 자체에 샤드 ID 정보를 임베드하기 위한 스마트 기능이 있다.

(이 기사에서 설명할 내용처럼) 결국 키 생성을 위해 ShardedUUIDGenerator를 사용할 경우, Hibernate Shards에서 기본 제공하는 ShardResolutionStrategy 구현에서 더빙된 AllShardsShardResolutionStrategy를 사용할 수도 있고, 이를 통해 특정 오브젝트의 ID를 바탕으로 어떤 샤드를 검색할지 결정할 수 있다.

Hibernate Shards가 올바로 작동하는 데 필요한 세 가지 인터페이스를 구성한 경우, 예제 애플리케이션을 샤딩하는 다음 단계로 넘어갈 수 있다. 이제 Hibernate의 SessionFactory를 시작할 시점이다.


Hibernate Shards 구성

SessionFactory는 Hibernate의 핵심 인터페이스 오브젝트 중 하나이다. 예를 들어, 이 작은 오브젝트가 맵핑 파일 및 구성을 로드하여 Hibernate 애플리케이션을 구성하므로 이 오브젝트를 통해 Hibernate의 모든 놀라운 기능이 이루어진다. 어노테이션 또는 Hibernate의 유서 깊은 .hbm 파일을 사용하는 경우, Hibernate가 어떤 오브젝트가 존속 가능하고 어디서 존속할지 알 수 있게 하기 위해 여전히 SessionFactory가 필요하다.

따라서 Hibernate Shards에서는 여러 데이터베이스를 구성할 수 있는 확장된 SessionFactory 유형을 활용해야 한다. 이름은 적절히 ShardedSessionFactory로 되어 있고, 물론 SessionFactory 유형이다. ShardedSessionFactory를 작성할 때, 이전에 구성된 세 가지 샤드 구현 유형(ShardAccessStrategy, ShardSelectionStrategyShardResolutionStrategy)을 제공해야 한다. 또한, POJO에 필요한 모든 맵핑 파일을 제공해야 할 것이다. (어노테이션 기반 Hibernate POJO 구성을 사용하는 경우 이는 약간 다르다.) 마지막으로, ShardedSessionFactory 인스턴스에는 활용하려는 각각의 샤드에 해당하는 여러 개의 Hibernate 구성 파일이 있어야 한다.

Hibernate 구성 작성

필자는 적절히 구성된 SessionFactory를 작성하는 한 기본 메소드인 createSessionFactory를 가진 ShardedSessionFactoryBuilder 유형을 작성했다. 향후, 필자는 Spring을 이용해 모든 것을 함께 연결할 것이다(요즘 누가 IOC 컨테이너를 활용하지 않겠는가?). 목록 2는 Hibernate Configuration을 작성하기 위해 ShardedSessionFactoryBuilder의 기본 기능을 나타낸 것이다.


목록 2. Hibernate Configuration 작성
private Configuration getPrototypeConfig(String hibernateFile, List<String>
  resourceFiles) {
 Configuration config = new Configuration().configure(hibernateFile);
 for (String res : resourceFiles) {
  configs.addResource(res);
 }
 return config;
}

목록 2에서 볼 수 있는 바와 같이, Hibernate 구성 파일에서 간단한 Configuration이 작성된다. 이 파일에는 POJO용 .hbm 파일과 같은 모든 필수 자원 파일뿐 아니라, 사용 중인 데이터베이스의 유형, 사용자 이름, 비밀번호 등의 정보가 들어 있다. 여러 데이터베이스 구성을 사용하고 있는 샤딩된 상황에서는 Hibernate Shards를 통해 한 hibernate.cfg.xml 파일만 간단히 사용할 수 있다(하지만, 목록 4에서 알 수 있는 것처럼 사용하려는 각 샤드에 대해 이 파일이 하나 필요할 것임).

다음으로, 목록 3에서 필자는 모든 샤드 구성을 List로 수집한다.


목록 3. 샤드 구성 목록
List<ShardConfiguration> shardConfigs = new ArrayList<ShardConfiguration>();
for (String hibconfig : this.hibernateConfigurations) {
 shardConfigs.add(buildShardConfig(hibconfig));
}

Spring 구성

목록 3에서, hibernateConfigurations에 대한 참조가 StringList를 가리키고, 그 각각에 Hibernate 구성 파일의 이름이 들어 있다. Spring에서 이 List가 자동으로 연결된다. 목록 4는 이 내용을 보여주는 필자의 Spring 구성 파일에 있는 스니펫이다.


목록 4. Spring 구성 파일의 일부
<bean id="shardedSessionFactoryBuilder"
  class="org.disco.racer.shardsupport.ShardedSessionFactoryBuilder">
    <property name="resourceConfigurations">
        <list>
            <value>racer.hbm.xml</value>
        </list>
    </property>
    <property name="hibernateConfigurations">
        <list>
            <value>shard0.hibernate.cfg.xml</value>
            <value>shard1.hibernate.cfg.xml</value>
        </list>
    </property>
</bean>

여기서 알 수 있는 바와 같이, 목록 4에서 ShardedSessionFactoryBuilder는 POJO 맵핑 파일 한 개와 샤드 구성 파일 두 개와 함께 연결되는 중이다. 목록 5에 POJO 파일의 스니펫이 표시되어 있다.


목록 5. 레이스 POJO 맵핑
<class name="org.disco.racer.domain.Race" table="race"dynamic-update="true"
   dynamic-insert="true">

 <id name="id" column="RACE_ID" unsaved-value="-1">
  <generator class="org.hibernate.shards.id.ShardedUUIDGenerator"/>
 </id>

 <set name="participants" cascade="save-update" inverse="false" table="race_participants"
    lazy="false">
  <key column="race_id"/>
  <many-to-many column="runner_id" class="org.disco.racer.domain.Runner"/>
 </set>

 <set name="results" inverse="true" table="race_results" lazy="false">
  <key column="race_id"/>
  <one-to-many class="org.disco.racer.domain.Result"/>
 </set>

 <property name="name" column="NAME" type="string"/>
 <property name="distance" column="DISTANCE" type="double"/>
 <property name="date" column="DATE" type="date"/>
 <property name="description" column="DESCRIPTION" type="string"/>
</class>

목록 5의 POJO 맵핑에서 유일하게 고유한 부분이 어떻게 ID에 대한 생성기 클래스인지 주목해 보자. 이것은 UUID 자체에 샤드 ID 정보를 임베드하는 ShardedUUIDGenerator이다. 그것이 필자의 POJO 맵핑에서 샤딩에 관해 유일하게 특별한 측면이다.

샤드 구성 파일

다음으로 목록 6에서 필자는 샤드 하나를 구성했는데, 이 경우에는 Shard 0이다. Shard 1의 파일은 샤드 ID 및 연결 정보를 제외하면 동일하다.


목록 6. Hibernate Shards 구성 파일
<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-configuration PUBLIC
        "-//Hibernate/Hibernate Configuration DTD//EN"
        "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<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:file:/.../db01/db01
        </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>

그 이름이 암시하는 바와 같이, enable_cross_shard_relationship_checks 특성은 크로스 샤드 관계를 검사한다. Hibernate Shards 문서에 따라, 이 특성은 꽤 많은 비용이 들기 때문에 프로덕션 환경에서는 해제해야 한다.

마지막으로, 목록 7에서와 같이 ShardedSessionFactoryBuilderShardStrategyFactory를 작성한 다음 세 가지 유형(목록 1RacerShardSelectionStrategy 포함)을 추가하여 모든 것을 모은다.


목록 7. ShardStrategyFactory 작성
private ShardStrategyFactory buildShardStrategyFactory() {
 ShardStrategyFactory shardStrategyFactory = new ShardStrategyFactory() {
  public ShardStrategy newShardStrategy(List<ShardId> shardIds) {
   ShardSelectionStrategy pss = new RacerShardSelectionStrategy();
   ShardResolutionStrategy prs = new AllShardsShardResolutionStrategy(shardIds);
   ShardAccessStrategy pas = new SequentialShardAccessStrategy();
   return new ShardStrategyImpl(pss, prs, pas);
  }
 };
 return shardStrategyFactory;
}

마침내, 그처럼 멋진 메소드 더빙된 createSessionFactory를 실행하며, 이 경우 목록 8에 표시된 것처럼 ShardedSessionFactory가 작성된다.


목록 8. ShardedSessionFactory 작성
public SessionFactory createSessionFactory() {
 Configuration prototypeConfig = this.getPrototypeConfig
  (this.hibernateConfigurations.get(0), this.resourceConfigurations);

 List<ShardConfiguration> shardConfigs = new ArrayList<ShardConfiguration>();
 for (String hibconfig : this.hibernateConfigurations) {
  shardConfigs.add(buildShardConfig(hibconfig));
 }

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

Spring으로 도메인 오브젝트 연결

거의 막바지에 이르렀으므로, 잠시 심호흡을 하자. 여태까지 실제로는 편재하는 Hibernate의 SessionFactory 유형을 구현한 것에 불과한 ShardedSessionFactory를 올바로 구성하는 빌더 클래스를 작성했다. 바로 이 ShardedSessionFactory가 샤딩이 보여주는 모든 마법을 부린다. 이것은 필자가 목록 1에서 보여준 샤드 선택 전략을 활용하고 필자가 구성한 두 개의 샤드에 데이터를 기록하고 이들 샤드의 데이터를 읽는다. (목록 6은 Shard 0에 대한 구성을 나타낸 것이며, Shard 1은 거의 동일하다.)

필자가 지금 해야 할 일은 도메인 오브젝트를 연결하는 것뿐이며, 이 경우에는 도메인 오브젝트가 Hibernate에 의존하기 때문에 SessionFactory 유형이 작동해야 한다. 목록 9에서와 같이, 필자는 단지 ShardedSessionFactoryBuilder를 사용하여 SessionFactory 유형을 제공할 것이다.


목록 9. Spring에서 POJO 연결
<bean id="mySessionFactory"
 factory-bean="shardedSessionFactoryBuilder"
 factory-method="createSessionFactory">
</bean>

<bean id="race_dao" class="org.disco.racer.domain.RaceDAOImpl">
 <property name="sessionFactory">
  <ref bean="mySessionFactory"/>
 </property>
</bean>

목록 9에서 알 수 있는 바와 같이, 필자는 우선 Spring에서 factory와 같은 Bean을 작성했다. 즉, 필자의 RaceDAOImpl 유형에는 SessionFactory 유형의 sessionFactory라는 특성이 있다. 따라서 mySessionFactory 참조는 목록 4에서 정의된 ShardedSessionFactoryBuilder에서 createSessionFactory 메소드를 호출하여 SessionFactory의 인스턴스를 작성한다.

(기본적으로 미리 구성된 오브젝트를 리턴하기 위해 거대한 팩토리로 사용 중인) Spring에 필자의 Race 오브젝트 인스턴스를 요청하면 모든 것이 설정될 것이다. 표시되어 있지는 않지만, RaceDAOImpl 유형은 데이터 저장 및 검색을 위해 Hibernate 템플리트를 활용하는 오브젝트이다. 필자의 Race 유형에는 모든 데이터 저장소 관련 활동을 연기하는 대상 RaceDAOImpl의 인스턴스가 들어 있다. 정말 편한 일이다.

필자의 DAO는 구성에 의한 경우를 제외하고 코드에서 Hibernate Shards에 결합되어 있지 않다. (목록 5에서) 샤딩에 특정한 UUID 생성 스킴에 DAO를 결합하도록 구성되어 있는데, 이는 샤딩할 필요가 있을 때 기존 Hibernate 구현에서 도메인 오브젝트를 다시 사용할 수 있다는 뜻이다.


샤딩: easyb를 포함한 테스트 드라이브

다음으로, 샤딩 구현이 작동하는지 확인할 필요가 있다. 필자는 두 개의 데이터베이스를 가지고 있고 거리를 기준으로 샤딩하는 것이므로, (10마일 이상인) 마라톤을 작성한다. 즉, Shard 1에 Race 인스턴스가 있어야 한다. Shard 0에서 5K(5km, 즉 3.1마일)와 같은 단거리 레이스를 찾을 수 있어야 한다. Race를 작성한 후 개별 데이터베이스의 레코드를 검사할 수 있다.

목록 10에서는 마라톤을 작성한 후 계속 진행하여 해당 레코드가 실은 Shard 0이 아니라 Shard 1에 있는지 확인했다. 좀 더 재미있고 쉽게 보여주기 위해, 자연어 확인을 손쉽게 만들어주는 Groovy 기반의 동작 중심 개발 프레임워크인 easyb를 사용했다. easyb는 Java 코드에서도 손쉽게 작동한다. Groovy나 easyb를 모르더라도 목록 10의 코드를 따르고 모든 것이 계획대로 작동하는 것을 확인할 수 있어야 한다. (필자는 easyb의 작성을 돕고 그에 관한 기사를 developerWorks에 기고했다.)


목록 10. 샤드의 정확성을 확인하는 easyb 기사의 스니펫
scenario "races greater than 10.0 miles should be in shard 1 or db02", {
  given "a newly created race that is over 10.0 miles", {
    new Race("Leesburg Marathon", new Date(), 26.2,
            "Race the beautiful streets of Leesburg!").create()
  }
  then "everything should work fine w/respect to Hibernate", {
    rce = Race.findByName("Leesburg Marathon")
    rce.distance.shouldBe 26.2
  }
  and "the race should be stored in shard 1 or db02", {
    sql = Sql.newInstance(db02url, name, psswrd, driver)
    sql.eachRow("select race_id, distance, name from race where name=?", 
      ["Leesburg Marathon"]) { row ->
      row.distance.shouldBe 26.2
    }
    sql.close()
  }
  and "the race should NOT be stored in shard 0 or db01", {
    sql = Sql.newInstance(db01url, name, psswrd, driver)
    sql.eachRow("select race_id, distance, name from race where name=?", 
      ["Leesburg Marathon"]) { row ->
      fail "shard 0 contains a marathon!"
    }
    sql.close()
  }
}

물론, 작업이 완료된 것은 아니고 여전히 더 짧은 레이스를 작성하고 Shard 1이 아니라 Shard 0에 랜딩하는 것을 확인해야 한다. 이 기사에 소개된 코드 다운로드에서 확인 작업 실행 예제를 볼 수 있다.


샤딩에 대한 찬반 양론

특히 애플리케이션에 막대한 양(테라바이트 단위)의 데이터가 들어 있거나 Google 또는 Facebook과 같이 무제한적으로 성장하는 도메인에 있는 경우, 샤딩을 이용해 애플리케이션 읽기 및 쓰기 속도를 높일 수 있다.

샤딩하기 전, 애플리케이션의 크기와 성장 속도가 샤딩을 이용할 가치가 있는지 확인해야 한다. 샤딩의 비용 또는 샤딩에 대한 반대의 논거에는 데이터의 저장 및 검색 방식에 대한 애플리케이션별 논리를 코딩해야 한다는 부담이 포함된다. 일단 샤딩을 선택하게 되면, 다시 샤딩하기란 쉽지 않은 일이기 때문에 샤딩 모델에 다소 예속되기도 한다.

적당한 상황에서는 샤딩이 전통적인 RDBMS의 확장 능력과 속도를 크게 높이는 데 관건이 될 수 있다. 대폭적으로 확장 가능한 데이터 스토리지에 대한 필요를 충족시키기 위해 하드웨어를 계속 업그레이드할 수 없는 관계형 인프라에 결합된 조직에게는 샤딩이 특히 값비싼 비용을 치러야 하는 의사결정이다.



다운로드 하십시오

설명이름크기다운로드 방식
Sample code for this articlej-javadev2-11.zip15KBHTTP

다운로드 방식에 대한 정보


참고자료

교육

제품 및 기술 얻기

  • Hibernate Shards: Hibernate Core에 수평적 파티셔닝에 대한 지원을 추가하여 데이터 복잡도를 캡슐화하고 최소화하도록 디자인되었다.

  • easyb: easyb는 스펙 기반 Domain Specific Language를 사용하여 실행 가능하면서도 읽을 수 있는 문서를 사용하는 것을 목표로 한다.

토론

  • My developerWorks 커뮤니티에 참여하자. 개발자가 이끌고 있는 블로그, 포럼, 그룹 및 Wiki를 살펴보면서 다른 developerWorks 사용자와 의견을 나눌 수 있다.

필자소개

Andrew Glover 사진

Andrew Glover는 Stelligent Incorporated의 사장이다. 회사들이 코드 품질을 일찍 그리고 자주 모니터할 수 있게 하는 효과적인 개발자 테스팅 전략과 지속적 통합 기법으로 소프트웨어 품질 문제를 해결하는 것을 돕고 있다. Andy의 저서 목록은 그의 블로그를 보라.

잘못된 도움말 신고

부정사용 신고

감사합니다. 이 항목은 운영자가 관심을 표시했습니다.


잘못된 도움말 신고

부정사용 신고

제출실패 신고. 나중에 다시 실행해주세요.


디벨로퍼웍스 로그인


IBM ID가 필요하세요?
IBM ID를 잊으셨습니까?


비밀번호를 잊으셨습니까?
비밀번호 변경

developerWorks 이용 약관에 동의하시는 경우 제출을 클릭하십시오. 이용 약관.

 


developerWorks에 처음 로그인하면 developerWorks프로파일이 생성됩니다.귀하의 프로파일에서 동의하신 내용이 공개되지만 이 사항은 언제든지 변경 가능합니다. 귀하의 성명(숨김으로 체크되어 있어도 표시됩니다)과 디스플레이 이름은 게시한 컨텐츠나 사이트 엑세스시 표시됩니다.

화면상에 보여지는 닉네임을 정하세요.

처음 developerWorks에 로그인할 때 프로파일이 작성되므로, 이를 위해 디스플레이 이름을 선택해야 합니다. 선택하신 디스플레이 이름은 developerWorks에 게시한 컨텐츠에 표시됩니다.

3글자 이상 31글자 이하의 길이로 사용 가능합니다. dW커뮤니티 내에서는 보안상 이메일주소를 제외한 다른 이름을 지정하셔야 합니다.

3개의 &이나 대쉬를 포함해주시고 31글자내로 제한해주세요.


developerWorks 이용 약관에 동의하시는 경우 제출을 클릭하십시오. 이용 약관.

 


아티클 순위

의견

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=20
Zone=자바
ArticleID=600265
ArticleTitle=자바 개발 2.0:: Hibernate Shards를 이용한 샤딩
publish-date=08312010
author1-email=aglover@stelligent.com
author1-email-cc=

태그

Help
검색 필드를 사용하여 My developerWorks 내에서 해당 태그가 사용된 모든 종류의 컨텐츠를 검색하십시오.

태그를 더 많이 보거나 적게 보기 위해 슬라이더 막대를 사용하십시오.

인기 태그는 특정 컨텐츠 존(예를 들어, 자바, 리눅스, WebSphere)의 최고 인기 태그를 보여줍니다.

내 태그는 특정 컨텐츠 존(예를 들어, 자바, 리눅스, WebSphere)의 귀하의 태그를 보여줍니다.

검색 필드를 사용하여 My developerWorks 내에서 해당 태그가 사용된 모든 종류의 컨텐츠를 검색하십시오. 인기 태그는 특정 컨텐츠 존(예를 들어, 자바, 리눅스, WebSphere)의 최고 인기 태그를 보여줍니다. 내 태그는 특정 컨텐츠 존(예를 들어, 자바, 리눅스, WebSphere)의 귀하의 태그를 보여줍니다.