메인 컨텐츠로 가기

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

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

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

  • 닫기 [x]

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

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

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

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

  • 닫기 [x]

Slice를 사용하여 OpenJPA 애플리케이션 확장하기

Pinaki Poddar, Senior Software Engineer, IBM  
Pinaki Poddar
Pinaki Poddar works in middleware technology with an emphasis on object persistence. He is a member of the Expert Group for the Java Persistence API (JSR 317) specification and a committer for the Apache OpenJPA project. In a past life, he contributed to the building of component-oriented integration middleware for a global investment bank and a medical image-processing platform for the healthcare industry. For his doctoral thesis, he developed an indigenous, neural-network-based automatic speech-recognition system.

요약:  Slice는 OpenJPA의 분산 지속성을 위한 모듈입니다. Slice를 사용하면 단일 데이터베이스용으로 개발된 애플리케이션을 수평 파티셔닝된 분산 이기종 데이터베이스 환경용으로 변경할 수 있습니다. 그리고 원래 애플리케이션 코드나 데이터베이스 스키마를 변경하지 않은 채로 이 모든 작업을 수행할 수 있습니다. 이 유연한 기술을 사용자의 애플리케이션 특히, 클라우드 또는 SaaS(Software as a Service)용으로 개발된 애플리케이션에 활용하는 방법을 살펴봅시다.

원문 게재일:  2010 년 8 월 24 일 번역 게재일:   2010 년 12 월 07 일
난이도:  초급 원문:  보기 PDF:  A4 and Letter (154KB | 20 pages)Get Adobe® Reader®
페이지뷰:  2647 회
의견:  


소개

Slice는 OpenJPA를 수평 파티셔닝된 분산 데이터베이스 환경에 적합하게 확장한다. 현재 단일 데이터베이스를 사용하는 OpenJPA 기반 애플리케이션은 Slice를 사용하여 데이터가 여러 데이터베이스에 파티셔닝된 스토리지 환경에 맞게 다시 구성할 수 있다. 이 업그레이드는 애플리케이션 코드나 데이터베이스 스키마를 변경하지 않아도 된다.

수평 데이터 파티셔닝의 직접적인 장점은 대용량 데이터 볼륨에 대한 성능이 향상된다는 것이다. 특히, 작업 또는 쿼리의 트랜잭션 단위가 전체 데이터세트의 서브세트로 제한되는 애플리케이션의 경우(예를 들어, 여러 지역에 파티셔닝되어 있는 멀티테넌트 SaaS(Software-as-a-Service) 플랫폼 또는 고객 데이터베이스) 높은 성능 향상을 기대할 수 있다. 그러한 시나리오에서 Slice와 같은 데이터 파티셔닝 기반 솔루션이 유용한 이유는 Slice가 모든 데이터베이스 조작을 여러 파티션에 대해 병렬로 실행하여 멀티코어 하드웨어 및 I/O 관련 조작의 동시성을 효율적으로 활용할 뿐만 아니라 파티션의 서브세트를 대상으로 데이터베이스 쿼리를 수행할 수도 있기 때문이다.

이 기사에서 다루는 주제는 다음과 같다.

  • Slice용 애플리케이션 구성
  • 여러 파티션에 데이터를 분배하는 Slice
  • 여러 파티션의 쿼리 결과를 집계 또는 정렬하는 Slice
  • 파티셔닝을 병렬로 효과적으로 작동하기 위해 충족해야 하는 조건
  • 파티셔닝된 데이터베이스에 적합하게 OpenJPA 런타임을 확장하기 위해 Slice에 의해 해결된 핵심 설계 및 아키텍처 과제

JPA에 대한 엘리베이터 피치

JPA(Java™ Persistence API)는 관계형 데이터베이스의 관리되는 오브젝트 지속성에 대한 스펙이다. JPA의 핵심 개념인 지속성 유닛과 지속성 컨텍스트는 javax.persistence 패키지의 EntityManagerFactoryEntityManager 인터페이스로 실현된다. 지속성 유닛이 나타내는 바는 다음과 같다.

  • 지속적 Java 유형 세트
  • 지속적 Java 유형의 맵핑 스펙
  • 데이터베이스 연결 특성
  • 제공자 관련 사용자 정의 특성 세트

지속성 컨텍스트는 관리되는 지속성 인스턴스의 세트를 나타낸다. 또한 지속성 컨텍스트는 다음과 같은 지속적 조작을 위한 기본 인터페이스이다.

  • 새 인스턴스 작성하기
  • 기본 ID로 인스턴스 찾기
  • 문자열 기반 또는 동적으로 구성된 쿼리를 사용하여 인스턴스 선택하기
  • 트랜잭션 경계 구분하기

지속성 컨텍스트는 JPA 제공자를 통해 애플리케이션별 지속성 상태의 변화를 모니터링하는 방식으로 인스턴스를 관리한다. 트랜잭션이 커미트되거나 컨텍스트가 적용될 때 해당 데이터베이스 레코드가 자동으로 업데이트된다.

일반적으로 JPA는 지속성 조작 및 쿼리가 Java 오브젝트 모델을 참조하는 애플리케이션 프로그래밍 모델을 지원하는 반면 제공자는 오브젝트 모델을 데이터베이스 스키마에 맵핑하는 역할을 담당한다. 즉, Java 클래스는 하나 이상의 데이터베이스 테이블로 변환되고, Java 유형의 지속적 속성은 데이터베이스 열로 변환되며, 관계는 외부 키로 변환된다. 그리고 SQL문에 대한 지속성 조작인 Java의 new() 연산자는 하나 이상의 INSERT에 맵핑되며, find()SELECT에 맵핑되고, 지속적 인스턴스의 setter 메소드는 데이터베이스에 대한 UPDATE문으로 변환된다.

슬라이스와 Slice

이 기사에서는 슬라이스와 고유 명사인 Slice가 자주 사용된다. 슬라이스는 데이터베이스 서브세트를 의미하는 반면 고유 명사인 Slice는 런타임 OpenJPA 모듈을 의미한다.

핵심 JPA 스펙의 기반에는 암묵적이지만 강력한 가정이 있다. 그 가정은 바로 지속적 오브젝트 및 조작이 단일 관계형 데이터베이스에 맵핑되며, 각 지속성 유닛이 반드시 단일 데이터베이스에 연결되고, 관리되는 지속성 오브젝트 상태가 동일한 데이터베이스에 저장된다는 것이다. Slice는 단일 데이터베이스에 대한 이 완벽한 가정을 변경한다. Slice를 사용하면 동일한 JPA 애플리케이션을 사용하면서 기본 데이터 스토리지 환경을 단일 대형 데이터베이스에서 수평으로 파티셔닝된 데이터베이스 세트로 변경할 수 있다. 전체 데이터세트의 서브세트를 저장하는 이러한 실제 데이터베이스를 파티션, 샤드 또는 슬라이스라고 한다.

공식적으로 데이터세트 D의 수평 파티셔닝 또는 샤딩 P(D)D를 상호 분리된 N개의 세트 Di로 분할하는 것이다. 이를 공식으로 표현하면 다음과 같다.

D = D1 ∪ D2 ∪ ⋯ ∪ Dn이며, Di ∩ Dn =  Ø이다. 그리고 i ≠ j이다.

Slice에서는 실제 기본 데이터베이스 파티션 또는 샤드를 포함하기 위해 가상 데이터베이스의 추상화를 도입했다. Slice를 사용하는 애플리케이션의 지속성 유닛은 단일 데이터베이스에 연결된다. 그런 다음 해당 JDBC 드라이버를 통해 모든 지속성 조작을 실제 데이터베이스에 멀티플렉싱한다. 예를 들어, 4개의 슬라이스 또는 샤드를 사용하도록 구성된 Slice를 사용하는 애플리케이션이 있을 경우 select c from Customer c where c.age > 20 order by c.age와 같은 JPA 쿼리는 4개의 각 슬라이스에서 병렬로 실행된다. 각 슬라이스에 저장된 각각의 결과는 가상 데이터베이스에 의해 병합되어 메모리에 다시 저장된 후 애플리케이션에 제공된다. 사용자 애플리케이션의 관점에서 가상 데이터베이스 인터페이스는 단일 데이터베이스와 완전히 동일한 API를 제공하므로 애플리케이션 코드와 데이터베이스 스키마를 수정하지 않고도 단일 데이터베이스 환경을 파티셔닝된 분산 데이터베이스 환경으로 변경할 수 있다. 이는 Slice의 가상 데이터베이스 추상화가 복합 설계 패턴을 따르기 때문에 가능하다. 이 완벽한 특성은 Slice의 유용성을 높여 주는 가장 강력한 특성이다.

EntityManager를 단일 파티션에 연결하도록 구성한 별도의 샤드 또는 단일 지속성 유닛을 사용하여 각 지속성 유닛이 구성된 경우에는 다른 방법으로 파티션을 처리하지 않는 것이 좋다.

왜 그럴까? 그 이유는 JPA 스펙에서 지속성 컨텍스트의 관리되는 인스턴스에 대해 그룹 형태의 작동을 요구하기 때문이다.


Slice 구성하기

Slice를 사용하여 애플리케이션을 다시 구성하는 작업만으로 애플리케이션의 데이터베이스 환경을 파티셔닝된 데이터베이스 환경으로 업그레이드할 수 있다. Slice를 소개하는 이 섹션에서는 구체적으로 사용자가 구성할 수 있는 특성과 이러한 특성이 다양한 기능을 나타내는 방법에 대해 설명한다. Slice 구성에는 표준 JPA 런타임을 구성할 때와 동일한 방법이 사용된다. (예를 들어, META-INF/persistence.xml 자원을 클래스 경로에서 볼 수 있도록 설정한다.) META-INF/persistence.xml에는 주로 제공자 관련 특성 섹션이 이름/값 쌍으로 포함되어 있으며 지속적 클래스 이름 또는 맵핑 설명과 같이 JPA 스펙에 정의된 기타 특성도 포함되어 있다. Slice 관련 특성은 제공자 관련 특성 섹션에 언급되며, openjpa.slice.*라는 접두부가 포함되어 있다.

구성 특성은 크게 다음 3가지 그룹으로 분류할 수 있다.

  • 스토리지 환경을 전체적으로 구성하는 특성
  • 개별 슬라이스를 구성하는 특성
  • 런타임 작동을 구성하는 특성

이러한 특성과 특성이 작동에 영향을 주는 방법에 대해 살펴보자.

파티셔닝된 스토리지 환경에 대한 구성

먼저, 데이터가 3개의 Apache Derby 데이터베이스로 파티셔닝된 환경을 살펴보자. 데이터베이스는 논리적 슬라이스 IDOne, TwoThree로 식별된다. 논리적 슬라이스 ID는 사람이 읽을 수 있는 간단한 이름으로 실제 데이터베이스 URL 및 기타 세부사항을 고유하게(특정 지속성 유닛의 범위 내에서) 나타낸다. Slice를 파티셔닝된 데이터베이스 환경에 적합하게 구성하는 persistence.xml을 Listing 1에서 볼 수 있다.


Listing 1. Slice 구성 예제

<persistence-unit name="slice">
  <properties>
    <property name="openjpa.BrokerFactory" value="slice"/>
    <property name="openjpa.slice.Names"   value="One,Two, Three"/>
    <property name="openjpa.slice.Master"  value="One"/>
    <property name="openjpa.slice.Lenient" value="true"/>
         
    <property name="openjpa.ConnectionDriverName"
              value="org.apache.derby.jdbc.EmbeddedDriver"/>
    <property name="openjpa.slice.One.ConnectionURL"    
              value="jdbc:derby:target/database/slice1"/>
    <property name="openjpa.slice.Two.ConnectionURL"    
              value="jdbc:derby:target/database/slice2"/>
    <property name="openjpa.slice.Three.ConnectionURL" 
              value="jdbc:some-bad-url"/> 
    
    <property name="openjpa.slice.DistributionPolicy" 
                   value="acme.UserDistributionPolicy"/>

  </properties>
</persistence-unit>

Slice 활성화하기

Slice를 활성화하기 위해 가장 중요한 첫 번째 특성은 다음과 같다.

    <property name="openjpa.BrokerFactory" value="slice"/>
            

이 특성은 OpenJPA 런타임에게 실제 데이터베이스 세트를 기반으로 하는 가상 데이터베이스에 연결된 특수 지속성 유닛을 작성하도록 지시한다. 이 특성은 필수이다.

각 슬라이스에는 논리적 이름이 있다.

다음으로 중요한 특성은 논리적 슬라이스 ID의 목록이다.

    <property name="openjpa.slice.Names" value="One,Two,Three"/>
            

이 특성 값은 사용 가능한 모든 논리적 ID를 쉼표로 구분된 목록으로 열거한다. 논리적 ID는 실제 데이터베이스의 이름과 같지 않아야 한다. 논리적 ID는 지속성 유닛 내에서 특정 슬라이스의 고유 ID이다. 예를 들어, 슬라이스와 관련된 각 구성 특성 이름에는 다음과 같이 논리적 ID가 접두부로 사용된다.

    <property name="openjpa.slice.One.ConnectionURL" value="…"/>
            

하지만 openjpa.slice.Names 특성을 통해 논리적 ID를 반드시 나열해야 하는 것은 아니다. 이 특성이 지정되지 않은 경우에는 모든 고유 논리적 슬라이스 ID를 식별하기 위해 전체 persistence.xml이 검색된다. 그러므로 논리적 ID를 명시적으로 열거하는 것이 좋다. 이에 대해서는 나중에 자세히 살펴보자.

한 슬라이스를 마스터로 지정하기

마스터 슬라이스는 필요할 때마다 관리되는 인스턴스의 기본 ID를 생성하는 데 사용된다. JPA 스펙에 따라 각 지속적 인스턴스에는 지속성 ID가 있어야 한다. 이 ID의 값은 애플리케이션에 의해 지정되거나 데이터베이스 시퀀스에 의해 생성될 수 있다. 후자의 경우, 다중 데이터베이스 환경에서 데이터베이스에 의해 생성된 기본 키의 고유성을 유지하기 위해 슬라이스 중 하나가 이러한 키를 생성하도록 지정된다. 이 특별하게 지정된 슬라이스를 마스터 슬라이스라고 한다.

다음 특성을 사용하여 슬라이스를 마스터로 지정한다.

    <property name="openjpa.slice.Master"  value="One"/>
            

마스터 슬라이스의 명시적 지정은 필수가 아니다. 이 특성을 지정하지 않으면 첫 번째 슬라이스가 마스터로 지정된다. 물론 여기에서 가장 중요한 단어는 첫 번째이다. 이 말에는 슬라이스에 순서가 있다는 의미가 내포되어 있다. 슬라이스의 순서는 openjpa.slice.Names이 명시적으로 지정될 때 목록에 의해 지정된다. 그렇지 않은 경우에는 ID의 사전순으로 슬라이스의 순서(발견적이지만 명확한 순서)가 결정된다. 하지만 이러한 암묵적인 발견적 방법을 피하기 위해 openjpa.slice.Namesopenjpa.slice.Master를 명시적으로 지정하는 것이 좋다.

각 슬라이스의 가용성

다중 데이터베이스 시나리오의 경우 사용할 수 없는 데이터베이스가 하나 이상 발생할 수 있다. 다음 속성은 Slice가 하나 이상의 파티션에 연결할 수 없을 때 수행할 작동을 지정한다.

    <property name="openjpa.slice.Lenient" value="true"/>
            

이 특성을 true로 설정하면 하나 이상의 슬라이스에 연결할 수 없더라도 Slice가 계속 실행된다. 기본적으로 이 값은 false이며, 이 경우에는 구성된 슬라이스 중에 연결할 수 없는 슬라이스가 있을 경우 Slice가 시작되지 않는다. 예제를 보면 세 번째 슬라이스가 올바르지 않은 데이터베이스 URL을 가리키고 있다. 이 특성을 true로 설정하면 Slice가 두 개의 올바른 슬라이스를 사용하여 시작되며 연결할 수 있는 슬라이스를 무시할 수 있다.

개별 실제 데이터베이스 구성하기

논리적 ID로 식별된 각 슬라이스는 해당 실제 데이터베이스 URL 및 기타 특성을 지정해야 한다. 아래 예제에서는 One이라는 논리적 ID로 식별된 단일 슬라이스의 슬라이스 관련 구성을 보여 준다.

    <property name="openjpa.slice.One.ConnectionURL"    
              value="jdbc:derby:target/database/slice1"/>
            

이 특성은 jdbc:derby:target/database/slice1이라는 URL을 사용하여 One이라는 논리적 ID로 식별된 파티션을 Derby 데이터베이스의 실제 인스턴스에 지정한다.

앞에서 언급한 대로 각 슬라이스 관련 구성 특성 이름에는 openjpa.slice.<논리적 슬라이스 ID>라는 접두부가 있고 그 뒤에 원래 OpenJPA 특성 키 접미부(예: ConnectionURL)가 있다. 이러한 이름 지정 규칙이 사용되므로 사용자는 OpenJPA 특성과는 독립적으로 각 슬라이스를 구성할 수 있다. 이에 반해 모든 슬라이스에 공통으로 적용되는 구성 특성은 원래 OpenJPA 특성처럼 단순하게 지정할 수 있다. 따라서 아래 예제 구성에서는 JDBC 데이터베이스 드라이버가 공통 특성으로 지정되어 있으며 모든 슬라이스에 적용된다.

     <property name="openjpa.ConnectionDriverName"   
               value="org.apache.derby.jdbc.EmbeddedDriver"/>
            

또한 Slice에서는 다음 구문을 사용하여 논리적 ID가 Four이고 동일 구성 내에서 MySQL 데이터베이스를 나타내는 네 번째 슬라이스를 지정할 수 있다.

    <property name="openjpa.slice.Four.ConnectionURL"    
              value="jdbc:mysql://localhost/slice4"/>
    <property name="openjpa.slice.Four.ConnectionDriverName"
              value="com.mysql.jdbc.Driver"/>
            

이러한 경우에는 슬라이스 관련 특성이 특정 네 번째 슬라이스의 공통 특성을 오버라이드한다.

런타임 작동을 위한 구성

Slice의 주요 설계 목표는 애플리케이션 코드를 일반적인 단일 데이터베이스에서 사용할 때와 완전히 동일하게 유지할 수 있도록 스토리지 환경을 캡슐화하는 것이었다. 이에 반해 사용자 애플리케이션에서는 기본 슬라이스에 대한 정보와 함께 활성 슬라이스의 특정 서브세트를 일부 쿼리의 대상으로 지정하는 등의 어느 정도의 제어가 필요하다. 애플리케이션 코드에 영향을 주지 않고 Slice를 활성화하는 동시에 약간의 제어가 가능하도록 하려는 약간 모순된 목표를 유지하기 위해 Slice에서는 플러그인 정책 기반 방법을 사용한다. 이 방법에서는 정책 인터페이스를 사용자 애플리케이션에서 구현하고 구성에서 지정한다. 런타임 동안 Slice에서는 이 사용자 구현을 호출한 후 리턴된 값을 사용하여 플로우를 제어한다. 사용 가능한 정책 도구는 다음과 같다.

  • 데이터 분배 정책 — 새 지속적 인스턴스를 저장하는 슬라이스를 제어한다.
  • 복제 정책 — 복제된 인스턴스를 저장하는 슬라이스를 제어한다.
  • 쿼리 대상 정책 — 슬라이스 서브세트에 대해 실행할 쿼리를 지정한다.
  • 검색기 대상 정책 — 슬라이스 서브세트에 대해 실행할 기본 키 기준 검색 조작을 지정한다.

이 섹션에서는 다음과 같은 구성 가능한 정책을 중심으로 분산 데이터베이스 환경과 관련된 Slice의 런타임 작동에 대해 설명한다.

Slice의 데이터 분배 정책

Slice에서는 데이터베이스 스키마 변경이 필요하지 않다. 유사한 파티션 기반 지속성 솔루션에서는 파티션 ID를 식별하기 위해 특수 열을 데이터베이스 스키마에 추가하는 경우가 자주 있지만 Slice에서는 그러한 추가 스키마 레벨 정보가 필요하지 않다. 왜냐하면 논리적 이름을 통해 지속성 인스턴스와 원래 데이터베이스 파티션 사이의 연관을 관리하기 때문이다. 이 연관은 특정 슬라이스에 있는 지속성 인스턴스를 읽어올 때 설정된다. 하지만 새 인스턴스가 지속성 인스턴스가 되는 경우에는 Slice가 새 인스턴스에 연관시킬 데이터베이스 파티션을 결정할 수 없다. 그러므로 애플리케이션에서 새 인스턴스에 연관시킬 슬라이스를 지정해야 한다. 애플리케이션에서는 데이터 분배 정책을 통해 새 인스턴스에 대한 슬라이스를 지정한다. 이 정책은 다음과 같이 persistence.xml에서 구성할 수 있다.

    <property name="openjpa.slice.DistributionPolicy"
                   value="acme.UserDistributionPolicy"/>
            

특성 값은 org.apache.openjpa.slice.DistributionPolicy 인터페이스의 사용자 구현에 대한 완전한 클래스 이름을 지정한다. 사용자 애플리케이션에서는 인터페이스 계약(interface contract)을 통해 새 지속성 엔티티의 논리적 슬라이스를 결정할 수 있다.


Listing 2. 데이터 분배 정책 인터페이스 계약

package org.apache.openjpa.slice;
public interface DistributionPolicy { 
 String distribute(Object pc, List<String> slices, Object context);  
}
            

입력 인수는 다음과 같다.

  • pc는 지속할 인스턴스이다. 이는 EntityManager.persist(pc)의 입력 인수로 전달된 인스턴스와 같다.
  • slices는 변경되지 않는 논리적 슬라이스 ID 목록이다. 이 목록에는 현재 연결할 수 없는 슬라이스가 포함되지 않는다.
  • context는 나중에 사용하기 위해 예약된 불투명 오브젝트이다. 현재 이 컨텍스트는 구현 편의를 위해 현재 지속성 컨텍스트와 같다. 이 암묵적 의미는 미래의 사용을 보증하지 않는다.

구현에서는 지정된 논리적 슬라이스 ID 중 하나를 리턴해야 한다.

Slice는 지속되고 있는 모든 루트 오브젝트 인스턴스에서 이 사용자 구현을 호출한다. 루트 오브젝트 인스턴스는 애플리케이션에서 호출하는 EntityManager.persist(Object r)에 대한 명시적 입력 인수이다. 단일 엔티티 r에 대한 명시적 persist(r) 조작은 다른 관련 엔티티를 간접적으로 지속시킬 수 있다는 점에 유의해야 한다. JPA 어노테이션(또는 맵핑 디스크립터)은 persist(), refresh(), merge() 또는 remove()와 같은 지속성 조작이 관계 경로를 따라 이동하는 방법을 사용하여 Java 참조 관계를 꾸밀 수 있다. 따라서 인스턴스 r이 또 다른 인스턴스 q에 관련되어 있고 rq 사이의 관계에 캐스케이드 PERSIST가 어노테이션으로 지정되어 있을 경우 q가 지속될 뿐만 아니라 r도 함께 지속된다. 이 작동을 이행 지속성이라고 한다.

새 루트 인스턴스 r을 지속하는 동안 Slice에서는 r에서 연결할 수 있는 모든 관련 엔티티가 동일한 슬라이스에 저장된다는 중요한 결정을 내린다. 따라서 분배 정책의 사용자 구현은 루트 엔티티 r에 대해서 호출된다. Slice는 자동으로 루트 인스턴스에 대한 이행 클로저 C(r)를 계산한 다음 현재 분배 정책에 결정된 대로 C(r)의 각 멤버를 r과 같은 슬라이스에 지정한다. 이처럼 이행 클로저를 같은 위치에 배치해야 하는 이유는 가상 데이터베이스에서 여러 실제 데이터베이스에 대한 결합을 실행할 수 없고 따라서 논리적으로 연관된 레코드가 다른 데이터베이스에 있을 경우 관계를 페치할 수 없기 때문이다. 이 제한을 동일 위치 제한조건이라고 한다. 지연 관계(lazy relation)가 파티션 간에 존재할 수 있고 동일한 엔티티 인스턴스가 어떻게 여러 슬라이스에 복제될 수 있는지 등과 같이 동일 위치 제한조건을 해결하는 방법에 대해서는 나중에 자세히 설명한다.

대부분의 애플리케이션의 경우에는 사용자 애플리케이션에서 데이터 분배 정책을 제공하지만 Slice에서는 입문자 또는 실험용 프로토타입을 위한 몇 개의 기성 구현 정책만 제공한다. 기본 정책은 임의의 슬라이스를 모든 새 인스턴스에 지정하는 기성 정책을 지정한다.

여러 슬라이스에 동일한 엔티티 저장하기

분배 정책은 단일 슬라이스에 저장된 엔티티에 유용하다. 그렇지만 동일 위치 제한조건에 따르면 모든 관련 인스턴스를 동일한 슬라이스에 저장해야 한다. 이 제한조건은 특정 공통 데이터 사용 패턴(예를 들어, 다른 여러 유형에서 참조되는 Stock Ticker Symbol 또는 Country Code나 Customer Type과 같은 마스터 데이터)에 과도한 제한이 된다. 그러한 경우 한 유형을 여러 슬라이스에 복제되도록 지정할 수 있다. persistence.xml 구성에서는 다음과 같이 복제 유형 이름을 쉼표로 구분된 목록으로 열거해야 한다.

    <property name="openjpa.slice.ReplicatedTypes"
                   value="acme.domain.Foo,acme.domain.Bar"/>
            

복제된 유형의 인스턴스를 지속시키면 데이터 분배 정책 대신 복제 정책이 호출된다. 복제된 엔티티는 둘 이상의 슬라이스에 저장될 수 있기 때문에 이 정책 인터페이스는 데이터 분배 정책과 유사하지만 리턴 유형이 다르다.


Listing 3. 복제 정책 인터페이스 계약

package org.apache.openjpa.slice;
public interface ReplicationPolicy { 
 String[] replicate(Object pc, List<String> slices, Object context);  
}
            

입력 인수의 의미는 데이터 분배 정책과 동일하게 유지되는 반면 이제 리턴값에는 단일 슬라이스 ID 대신 슬라이스 ID 배열이 포함된다. 널 리턴값은 모든 활성 슬라이스를 의미하는 반면 빈 배열은 예외를 발생시킨다. 다시 한번 Slice는 복제된 엔티티가 저장된 모든 슬라이스 ID를 추적하며, 복제된 인스턴스가 수정되면 해당하는 모든 슬라이스에 동일한 업데이트가 커미트된다. 따라서 복제된 엔티티 인스턴스는 여러 데이터베이스에 동일한 여러 사본을 가지고 있는 단일 논리적 엔티티라고 간주할 수 있다. 기본 복제 정책은 엔티티를 모든 활성 슬라이스에 복제한다.

쿼리에 복제된 유형이 포함된 경우, Slice에서는 'select count(o) from CountryCode o'와 같은 집계 쿼리가 여러 슬라이스에 있는 중복 CountryCode 인스턴스를 계산하여 수많은 결과를 리턴하는 상황이 발생하지 않도록 하기 위해 복제된 엔티티에 대한 슬라이스의 개별 결과를 필터링한다.

슬라이스 서브세트를 쿼리의 대상으로 지정하기

기본적으로 Slice는 모든 활성 슬라이스에 대해 쿼리를 실행하며 필요한 경우 결과를 메모리에 통합한다. 하지만 사용자가 각 쿼리를 슬라이스 서브세트에 대해 실행되도록 지정할 수 있다. 사용자 애플리케이션에서는 쿼리 대상 정책 인터페이스를 통해 이러한 쿼리 대상을 제어할 수 있다.


Listing 4. 쿼리 대상 정책 계약

package org.apache.openjpa.slice;
public interface QueryTargetPolicy { 
 String[] getTargets(String query, Map<Object,Object> \
params, List<String> slices, Object context);  
}
            

입력 인수는 JPQL 문자열인 query와 쿼리에 바인드된 매개변수 값이면서 각 값이 키로 인덱싱된 params이다. 나머지 매개변수의 의미는 데이터 분배 또는 복제 정책과 같다.

리턴값은 지정된 쿼리를 실행할 슬라이스를 지정한다. 빈 배열이나 널 배열은 올바른 리턴값이 아니다. 이 인터페이스는 모든 쿼리 실행 전에 호출된다. 기본 쿼리 대상 정책은 없다.

슬라이스 서브세트를 검색기의 대상으로 지정하기

탐색기 대상 정책은 find() 호출에 대해 바인드된 매개변수가 없다는 점을 제외하면 쿼리 대상 정책과 매우 유사하다. 인터페이스는 Listing 5와 같다.


Listing 5. 검색기 대상 정책 인터페이스 계약

package org.apache.openjpa.slice;
public interface FinderTargetPolicy { 
 String[] getTargets(Class<?> cls, Object oid, List<String> slices, Object context);  
}
            

입력 인수는 검색 대상 엔티티 클래스인 cls와 검색 대상 지속성 ID인 oid 이다. 나머지 매개변수의 의미는 다른 정책과 같다.

리턴값의 계약은 쿼리 대상 정책과 유사하다.

기본 검색기 대상 정책이 없으므로 find()는 기본적으로 모든 슬라이스에서 인스턴스를 검색한다.

분산 쿼리 실행

Slice는 슬라이스별로 여러 스레드를 사용하여 데이터베이스 조작을 동시에 실행한다. 스레드는 지속성 유닛별로 캐시된 스레드 풀에서 관리된다. 이 풀은 애플리케이션의 동시성 요구에 따라 증가하며 실행이 완료된 스레드는 풀로 리턴된다.

가상 데이터베이스에서는 실제 데이터베이스에 대한 쿼리 실행을 조정하며 통합된 결과를 준비하기 위해 개별 쿼리의 결과를 메모리에서 사후 처리한다. 정교한 처리를 위해 몇 가지 일반 쿼리가 제공된다.

메모리 내 사후 처리가 없는 쿼리

그림 1에서는 개별 슬라이스의 결과를 메모리에서 추가로 처리하지 않아도 되는 간단한 쿼리를 보여 준다.


그림 1. 메모리 내 사후 처리가 없는 쿼리
기본 select 쿼리와 셋 중 두 개의 데이터베이스 슬라이스에서 리턴된 결과를 보여 주는 다이어그램

    select e from Employee e where e.age < 30
            

쿼리 조건부는 각 슬라이스에서 평가된다. 최종 결과 목록은 개별 슬라이스의 결과 목록을 연결한 것이다. 논리적 슬라이스 ID는 선택된 요소의 순서를 효과적으로 결정하는 데 중요한 역할을 담당한다. 순서는 쿼리 대상 정책에서 리턴한 슬라이스 ID의 순서나 앞에서 설명한 명시적 openjpa.slice.Names를 통해 구성된 순서 또는 암묵적 사전순으로 결정된다. 순서가 {slice1, slice2, slice3}이라면 결과 목록의 요소도 같은 순서로 표시된다. 세 번째 슬라이스에서는 선택 항목을 리턴하지 않는다.

그림 2에서 보여 주는 다음 예제는 ORDER BY 절이 포함된 쿼리이다.

    select e from Employee e where e.age < 30 order by e.name
            


그림 2. 메모리 내 병합이 필요한 ORDER BY 절이 포함된 쿼리
간단한 SQL 쿼리와 셋 중 두 개의 데이터베이스 슬라이스에서 리턴된 후 이름순으로 정렬된 결과를 보여 주는 다이어그램

개별 쿼리 결과가 메모리 내에서 병합된 후 정렬된다. Lii번째 슬라이스의 정렬된 목록이라면 통합된 결과 목록 L은 다음과 같다.

L = sort(ΣLi)
            

각 목록 Li가 자체적으로 정렬되므로 메모리 내 정렬 조작은 스토리지 및 계산 관점에서 효과적일 수 있다.

이 예제에서 결과 목록은 각 슬라이스의 개별적으로 정렬된 목록을 병합한 것이다. 따라서 슬라이스 순서에 따를 경우 첫 번째 슬라이스에 "Mary"가 있기는 하지만 이름순으로 순서가 정해지므로 "Bill"이 결과 목록에 가장 먼저 나타난다.

Top-N 분산 쿼리

그림 3에서 보여 주는 세 번째 예제에서는 쿼리 결과가 상위 N개의 요소로 제한된다.


그림 3. LIMIT BY가 포함된 쿼리
세 개의 모든 데이터베이스 슬라이스에서 리턴된 결과를 setMaxResult 함수로 필터링하여 두 개의 결과를 리턴하는 SQL 쿼리를 보여 주는 다이어그램

Top-N 쿼리는 쿼리에 ORDER BY 절을 포함시킨 후 결과에 대한 한계를 설정하는 방식의 JPA로 구현된다. 다음 쿼리는 가장 젊은 5명의 직원을 검색한다.

    em.createQuery(“select e from Employee e order by e.age”)
        .setMaxResults(5)
        .getResultList(); 
            

분산 쿼리 환경에서 이 쿼리는 각 슬라이스에서 개별적으로 실행된다. 그런 다음 병합된 목록의 상위 두 개의 요소가 메모리의 가상 데이터베이스 계층에서 평가된다. 다시 한번 말하지만 메모리 내 Top-N 계산에서는 요소 x가 최종 목록 L에 있을 경우 x는 개별 목록 Li 중 하나에 있어야 한다는 사실을 이용한다.

분산 환경에서의 집계 쿼리

A(D)를 데이터세트 D에서 평가되는 집계 조작(예: SUM() 또는 MAX())이라고 하자.

집계 연산자는 A(D) = A(R) 일 경우 파티션 가환 연산자로 정의된다. 여기서, R은 각 파티션에서 수행된 A()에 대한 개별 평가의 세트이다. 따라서 R = {A(Di), i=1N)라는 공식이 성립된다.

그림 4에서는 파티션으로 가환되는 집계 연산을 보여 준다.


그림 4. 집계 쿼리
각 슬라이스로부터 수치 결과를 생성한 후 집계하여 최종 결과를 리턴하는 SQL 쿼리를 보여 주는 다이어그램

예제 쿼리는 select SUM(e.age) from Employee e where e.age > 30이다. S가 모든 슬라이스에 있는 30세 이상 직원의 나이의 합이고 S ii번째 슬라이스 내의 합이라면 SSi의 합이라는 것을 쉽게 알 수 있다. 따라서 SUM()은 파티션으로 가환된다. Slice는 MAX(), MIN(), SUM() 또는 COUNT()와 같이 파티션으로 가환되는 모든 집계 조작을 계산할 수 있다.

일반적인 집계 작업이 모두 파티션으로 가환되는 것은 아니다(예: AVG()). 현재 Slice에서는 파티션으로 가환되지 않는 집계 쿼리를 올바르게 평가할 수 있다.

트랜잭션과 Slice

EntityManager가 포함된 트랜잭션은 JTA(Java Transaction API)를 통해 제어하거나 애플리케이션에서 기본 데이터베이스 자원에 대한 자원 트랜잭션을 맵핑하는 EntityTransaction API를 통해 제어할 수 있다. JTA EntityManager의 경우 JTA 트랜잭션이 기본 자원 관리자(즉, 트랜잭션을 실제 데이터베이스로 전달하는 가상 데이터베이스)에게 전달된다. 일반적인 구성은 세 개의 JNDI 등록 데이터 소스가 포함된 JTA 트랜잭션을 위한 컨테이너 환경이다.


Listing 6. JNDI 등록 슬라이스를 사용한 구성 예제

<persistence-unit name=”slice” transaction-type=”JTA”>
<property name="openjpa.slice.Names" value="One,Two,Three"/> 
       <property name="openjpa.slice.Master" value="One"/> 
       <property name="openjpa.slice.One.ConnectionFactoryName" 
value="jdbc/slice-ds1"/> 
       <property name="openjpa.slice.Two.ConnectionFactoryName" 
value="jdbc/slice-ds2"/> 
       <property name="openjpa.slice.Three.ConnectionFactoryName" 
                 value="jdbc/slice-ds3"/>
</persistence-unit>
            

자원 로컬 EntityManager의 경우, 적절한 2단계 커미트 프로토콜에 비해 트랜잭션 보증이 약하기는 하지만 기본 자원 관리자가 실제 데이터베이스에 대한 트랜잭션 관리자 역할을 수행한다. 자원 로컬 트랜잭션에서는 작업 단위가 가장 먼저 분석되면서 관리 인스턴스가 각 기본 슬라이스에 대한 서브세트로 파티셔닝된다. 그런 다음 각 서브세트가 해당 데이터베이스에 저장된다. 슬라이스에 대한 서브세트가 비어 있는 경우에는 해당 서브세트가 무시된다. 저장 작업에 실패한 데이터베이스가 있으면 전체 트랜잭션이 롤백된다.

동일 위치 제한조건

Slice가 캐스케이드된 PERSIST 뒤에 있는 루트 인스턴스 r의 이행 클로저 C(r)를 자동으로 계산한 후 전체 클로저를 단일 슬라이스에 저장하는 방법을 데이터 분배 정책에서 설명한다는 것을 앞에서 살펴보았다. 여기서 중요한 점은 persist() 호출 시에 클로저가 계산된다는 것이다. 따라서 persist() 이후에 추가된 관계는 명시적 클로저의 일부가 아니다.

동일 위치 제한조건을 충족하기 위해 많은 주의를 기울여서 관계가 지정된 이후 루트 엔티티를 지속시키는 것도 중요하지만 그와 동시에 일부러 동일 위치 제한조건을 위반한 관련 인스턴스를 다른 슬라이스에 저장하는 것도 중요하다. 이를 보여 주는 명확한 경험 예제를 살펴보자.

PersonAddress 사이에 단순한 1:1 양방향 관계가 있다고 가정하자. Person.addressPERSIST로 캐스케이드된다. 맵핑 관점에서 Person이 관계의 소유자이다. (즉, ADDRESS 테이블에 대한 외부 키가 PERSON 테이블에 있다.)

OneTwo라는 두 개의 슬라이스가 있다고 가정하자. 그리고 분배 정책에서는 Person의 이름이 A부터 M 사이의 문자로 시작하면 One에 저장되고, 그렇지 않은 경우에는 슬라이스 Two에 저장된다. 이와 유사하게 Address의 우편번호가 짝수로 끝나면 슬라이스 One에 저장되고, 그렇지 않은 경우에는 슬라이스 Two에 저장된다.

이러한 간단한 규칙 하에 Person pAddress a 인스턴스를 작성하고 저장하자.


Listing 7. 상관 제한조건의 효과를 보여 주는 코드 예제

Person p = new Person();
p.setName(“Alan”); // slice One as name starts with letter A
Address a = new Address();
a.setZipCode(12345); // slice Two as zip code is odd digit

em.getTransaction().begin();
p.setAddress(a);
em.persist(p); // relation to address is set before persist
               // Address persisted by transitive persistence
em.getTransaction().commit();
            

Listing 7에서 Person pem.persist(p)가 호출될 때 Address a에 연관된다. 따라서 Address aPerson p와 마찬가지로 슬라이스 One에 저장된다. 분배 정책에서는 우편번호가 홀수로 끝나기 때문에 Address 인스턴스 a에 대한 슬라이스를 슬라이스 Two로 결정한다. 하지만 이 정책은 Address a에 대해서는 호출되지 않고 루트 인스턴스 Person p에 대해서만 호출된다. Slice는 ap의 이행 클로저에 있다는 것을 알게 되므로 분배 정책에 따라 p가 슬라이스 One에 지정될 때 동일한 슬라이스 OneAddress a에도 자동으로 지정된다.

Listing 8의 코드에서는 persist 호출의 순서를 바꿔서 실행한 예제를 보여 준다.


Listing 8. 상관 제한조건을 우회하는 방법을 보여 주는 코드 예제

em.getTransaction().begin();
em.persist(p); // relation to address is not set before persist
p.setAddress(a);
em.persist(a); // a has to be persisted explicitly
em.getTransaction().commit(); 
            

person pAddress a에 대해 분배 정책이 개별적으로 호출되며 두 인스턴스는 서로 다른 슬라이스에 저장된다.

따라서 상관 제한조건을 일부러 위반하여 관련 인스턴스를 별도의 슬라이스에 저장할 수 있기는 하지만 이러한 스토리지 전략의 유용성은 제한적이다. Person 및 관련 Address가 별도의 데이터베이스에 있을 경우 몇 가지 조작만 수행할 수 있다. 예를 들어, 소유 측에서 관계를 게으르게 로드할 수 있다. (즉, 지연 관계일 경우 Person.getAddress()가 다른 데이터베이스에서 올바른 주소를 얻게 된다.) 관계가 비소유 측 또는 'select p from Person p where p.address.zipcode = 12345'와 같이 결합이 필요한 쿼리에서 탐색되는 경우에는 오류 결과가 발생한다.


결론

데이터 파티셔닝은 대용량 데이터 볼륨을 확장하기에 효과적인 전략이다. 특히, 자연스러운 파티션이 있거나(예: 이름별 고객 계정, 지역별 주택 목록) 애플리케이션의 필요에 따라 데이터 분리가 선호되는 경우(멀티테넌트 호스트 플랫폼) 높은 효과를 얻을 수 있다. 표준 JPA의 스펙에서는 암묵적으로 단일 데이터베이스를 저장소로 가정하기 때문에 샤딩 또는 파티셔닝을 효과적으로 처리할 수 있는 방법이 없다. Slice는 데이터 파티션 또는 샤딩을 완벽하게 지원할 수 있도록 OpenJPA 구현을 확장했다. 다른 샤딩 솔루션과는 달리 Slice에서는 기존 스키마에 추가 열을 추가하지 않아도 파티셔닝을 사용할 수 있다. Slice를 사용하는 분배 및 쿼리 대상 지정 기능이 정책 기반 플러그인 인터페이스를 통해 제공되므로 기존 JPA 애플리케이션에서는 새 정책 인터페이스를 추가할 때와 persistence.xml을 다시 구성하는 경우를 제외하고는 코드를 수정하지 않아도 된다.


참고자료

교육

제품 및 기술 얻기

토론

필자소개

Pinaki Poddar

Pinaki Poddar works in middleware technology with an emphasis on object persistence. He is a member of the Expert Group for the Java Persistence API (JSR 317) specification and a committer for the Apache OpenJPA project. In a past life, he contributed to the building of component-oriented integration middleware for a global investment bank and a medical image-processing platform for the healthcare industry. For his doctoral thesis, he developed an indigenous, neural-network-based automatic speech-recognition system.

잘못된 도움말 신고

부정사용 신고

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


잘못된 도움말 신고

부정사용 신고

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


디벨로퍼웍스 로그인


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=593692
ArticleTitle=Slice를 사용하여 OpenJPA 애플리케이션 확장하기
publish-date=08242010
author1-email=ppoddar@us.ibm.com
author1-email-cc=

태그

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

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

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

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

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