2013년 10월 4일 금요일

내장 및 원격 질의

인피니스팬 메일링 리스트를 받아보고 있다면, 아마 질의에 관한 새로운 개발진행에 대해 살짝 엿들었을 것이다. 새로운 DSL, 핫로드 클라이언트를 사용한 원격 질의, 구글의 프로토콜 버퍼(protobuf)에 기반한 새로운 마샬러들이 그것이고, 지금이 이것들의 베일을 제대로 벗길 때이다!

새로운 질의 DSL

인피니스팬 버전 6.0부터, 캐시된 엔트리에 대해서 단순 필터링 DSL에 기반한 질의를 수행하는 새로운 (실험적인) 방법을 제공한다. 이 새로운 DSL의 목적은 질의를 더 간단히 작성하게 하고, 기저의 질의 메커니즘을 모르게 하여 미래에 루씬 이외의 대안적인 질의 엔진을 제공 가능케 하는 동시에 여전히 같은 질의 언어/API를 사용할 수 있도록 하는 데 있다. 이전의 하이버네이트 서치와 루씬 기반의 접근은 여전히 존재하며 계속 지원될 것이다. 사실 이 새로운 DSL은 현재 그 위에서 구현되었있다. 앞으로는 맵리듀스에 기반한 인덱스를 사용하지 않는 검색이 생길 것이며, 어쩌면 다른 뛰어난 검색 기술도 가능할 것이다.

내장모드에서 DSL 기반 질의를 실행하는 것은 현재의 루씬 기반 질의를 실행하는 것과 거의 동일하다. 필요한 작업은 infinispan-query-dsl.jar와 infinispan-query.jar를 클래스패스에 넣고, 캐시의 인덱싱을 활성화 하고 캐시에 저장될 POJO에 어노테이션을 붙이는 것이 전부다.

ConfigurationBuilder cfg =newConfigurationBuilder();
cfg.indexing().enable();

DefaultCacheManager cacheManager =newDefaultCacheManager(cfg.build());

Cache cache = cacheManager.getCache();

다른 방법으로, 사용자 가이드에서 설명했듯이 인덱싱은 (그리고 다른 모든 것도) XML을 사용해 설정 가능하다. 그래서 여기서는 자세히 들어가지 않는다.
하이버네이트 서치 어노테이션을 붙인 엔티티는 아래와 같을 것이다.

import org.hibernate.search.annotations.*;
...

@Indexed
public class User {

    @Field(store = Store.YES, analyze = Analyze.NO)
    private String name;

    @Field(store = Store.YES, analyze = Analyze.NO, indexNullAs = Field.DEFAULT_NULL_TOKEN)
    private String surname;

    @IndexedEmbedded(indexNullAs = Field.DEFAULT_NULL_TOKEN)
    private List addresses;

    // .. the rest omitted for brevity
}

DSL 기반 질의를 실행하기 위해 아래처럼 (캐시 범위의) SearchManager로부터 QueryFactory 얻고 질의를 만든다.

import org.infinispan.query.Search;
import org.infinispan.query.dsl.QueryFactory;
import org.infinispan.query.dsl.Query;
...

QueryFactory qf = Search.getSearchManager(cache).getQueryFactory();

Query q = qf.from(User.class)
    .having("name").eq("John")
    .toBuilder().build();

List list = q.list();

assertEquals(1, list.size());
assertEquals("John", list.get(0).getName());
assertEquals("Doe", list.get(0).getSurname());

이것이 전부이다. DSL이 실제로 무엇을 할 수 있는지에 관한 호기심을 불러 일으켰으리라 생각된다. FilterConditionEndContext에서 지원 필터 동작 리스트를 확인할 수 있다. 하위 조건들을 포함하여 불린 연산으로 복수개의 조건을 조합하는 것도 역시 가능하다.

Query q = qf.from(User.class)
    .having("name").eq("John")
    .and().having("surname").eq("Doe")
    .and().not(qf.having("address.street").like("%Tanzania%").or().having("address.postCode").in("TZ13", "TZ22"))
    .toBuilder().build();

이 DSL은 상당히 멋지고, 사용자 피드백에 기반하여 미래에 계속 확장될 것이다. 이는 또한 결과 페이징, 소팅, 프로젝션, 내장 객체에 대한 지원을 제공하며, 모두 QueryDslConditionsTest에서 확인 가능하다. 적절한 사용자 가이드가 나오기 전까지는 이 클래스를 살펴 보기를 권한다. 그러나 이것은 관계형 데이터베이스가 아니다. 모든 질의는 단일 대상 엔티티 (그리고 그것의 내장 엔티티들) 범위에서 쓰여진다는 것을 명심하라. 조인은 (아직) 없고, 상관 하위질의도 없고, 그룹핑이나 aggregation도 없다.

좀 더 들어가서, 아마도 이 새 DSL에 관해서 가장 흥미로운 것은 핫로드 클라이언트를 통해 원격으로 사용하는 것일것이다. 이 도약을 위해서 먼저 캐시 엔트리를 저장하고 그것들을 마샬링하는 공통 형식을 채택했어야만 했다. 이는 또한 사용 언어간 제약에 없고, 객체 스키마의 진화를 지원할 만큼 충분히 탄탄하여야 한다. 하지만, 아마도 무엇보다 이 형식은 단지 불명확한 blob이기 보다는 하나의 스키마를 가져야 했다. 그렇지 않으면 인덱싱과 검색이 의미없다. 프로토콜 버퍼로 들어가보자.


프로토콜 버퍼 마샬러

자바 핫로드 클라이언트를 사용하기 위해 RemoteCacheManager를 설정하는 것은 간단하다.

import org.infinispan.client.hotrod.configuration.ConfigurationBuilder;
...

ConfigurationBuilder clientBuilder = new ConfigurationBuilder();
clientBuilder.addServer()
    .host("127.0.0.1").port(11234)
    .marshaller(new ProtoStreamMarshaller());

아래 순서대로 하면 User 인스턴스를 프로토콜 버퍼 형식으로 원격 캐시에 저장하고 불러올 수 있다.

1. 사용자 엔티티를 위한 Protobuf 타입을 .proto파일에 선언하고 컴파일하여 .protobin 바이너리 기술자(binary descriptor)로 만든다.
2. 그 바이너리 기술자를 아래와 같이 RemoteCacheManager의 ProtoStreamMarshaller 인스턴스에 등록한다.

ProtoStreamMarshaller.getSerializationContext(remoteCacheManager)
    .registerProtofile("my-test-schema.protobin");

3. 엔티티마다 마샬러를 등록한다.

ProtoStreamMarshaller.getSerializationContext(remoteCacheManager)
    .registerMarshaller(User.class, new UserMarshaller());

2, 3번 단계는 Protostream 라이브러리가 동작하는 방법과 매우 가깝다. 그것은 아주 간단하지만 여기서 자세히 살펴볼 수는 없다. UserMarshaller 예제를 살펴보는 것이 많은 도움이 될 것이다.

사용자 객체를 프로토콜 버퍼 형식으로 유지하는 것은 다른 언어로 쓰여진 클라이언트들이 그것들을 사용할 수 있다는 데에 이점이 있다. 이것이 흥미롭게 들리지 않는 사람이라도 그것들이 쉽게 인덱싱 될 수 있다는 사실은 매력적이라고 생각할 것이다.


핫로드 클라이언트를 통한 원격 질의

RemoteCacheManagery를 위에서 설명한대로 설정하고, 다음 단계에서 원격 질의를 활성화 한다.

1. DSL jar를 클라이언트의 클래스패스에, infinispan-remote-query-server.jar를 서버의 클래스패스에 그리고 infinispan-remote-query-client.jar를 양쪽에 추가한다.
2. 캐시 설정에서 인덱싱을 활성화 한다. 내장모드도 동일하다.
3. 서버에서 (EmbeddedCacheManager 마다 하나의 인스턴스가 존재하는) ProtobufMetadataManager MBean의 registerProtofile 메소드를 사용하여 protobuf 바이너리 기술자를 등록한다.

캐시안에 들어온 모든 데이터는 엔티티에 하이버네이트 서치 어노테이션을 붙이지 않고 인덱싱 된다. 사실 이 클래스들은 자바 클라이언트에만 의미가 있으며 서버에는 존재하지도 않는다.

핫로드 클라이언트를 통해 질의를 실행하는 것은 내장모드와 아주 비슷하다. DSL은 사실 같다. 단지 작은 차이점은 QueryFactory를 얻는 방법이다.

import org.infinispan.client.hotrod.Search;
import org.infinispan.query.dsl.QueryFactory;
import org.infinispan.query.dsl.Query;
...

remoteCache.put(2, new User("John", "Doe", 33));

QueryFactory qf = Search.getQueryFactory(remoteCache);

Query query = qf.from(User.class)
    .having("name").eq("John")
    .toBuilder().build();

List list = query.list();
assertEquals(1, list.size());
assertEquals("John", list.get(0).getName());
assertEquals("Doe", list.get(0).getSurname());

이것보숑! 오늘의 여행일정이 끝났다. 인피니스팬 질의를 계속 눈여겨 보며, 당신의 커맨트를 공유해달라.

원문:
Thursday, 26 September 2013, Embedded and remote queries in Infinispan 6.0.0.Beta1

댓글 없음:

댓글 쓰기