Couchbaseの非同期バッチ操作

Couchbaseでの非同期バッチ操作

1. 前書き

using Couchbase in a Spring applicationに関するチュートリアルのこのフォローアップでは、Couchbase SDKの非同期の性質と、それを使用して永続化操作をバッチで実行する方法を探り、アプリケーションがCouchbaseリソースを最適に使用できるようにします。

1.1. CrudServiceインターフェース

まず、汎用のCrudServiceインターフェースを拡張して、バッチ操作を含めます。

public interface CrudService {
    ...

    List readBulk(Iterable ids);

    void createBulk(Iterable items);

    void updateBulk(Iterable items);

    void deleteBulk(Iterable ids);

    boolean exists(String id);
}

1.2. CouchbaseEntityインターフェース

永続化するエンティティのインターフェイスを定義します。

public interface CouchbaseEntity {

    String getId();

    void setId(String id);

}

1.3. AbstractCrudServiceクラス

次に、これらの各メソッドを汎用抽象クラスに実装します。 このクラスは、the previous tutorialで使用したPersonCrudServiceクラスから派生し、次のように始まります。

public abstract class AbstractCrudService implements CrudService {
    private BucketService bucketService;
    private Bucket bucket;
    private JsonDocumentConverter converter;

    public AbstractCrudService(BucketService bucketService, JsonDocumentConverter converter) {
        this.bucketService = bucketService;
        this.converter = converter;
    }

    protected void loadBucket() {
        bucket = bucketService.getBucket();
    }

    ...
}

2. 非同期バケットインターフェイス

Couchbase SDKは、非同期操作を実行するためのAsyncBucketインターフェイスを提供します。 Bucketインスタンスを指定すると、async()メソッドを介して非同期バージョンを取得できます。

AsyncBucket asyncBucket = bucket.async();

3. バッチ操作

AsyncBucketインターフェイスを使用してバッチ操作を実行するには、RxJavaライブラリを使用します。

3.1. バッチ読み取り

ここでは、readBulkメソッドを実装します。 まず、RxJavaのAsyncBucketおよびflatMapメカニズムを使用してドキュメントを非同期でObservable<JsonDocument>に取得し、次にRxJavatoBlockingメカニズムを使用して変換しますこれらをエンティティのリストに追加します。

@Override
public List readBulk(Iterable ids) {
    AsyncBucket asyncBucket = bucket.async();
    Observable asyncOperation = Observable
      .from(ids)
      .flatMap(new Func1>() {
          public Observable call(String key) {
              return asyncBucket.get(key);
          }
    });

    List items = new ArrayList();
    try {
        asyncOperation.toBlocking()
          .forEach(new Action1() {
              public void call(JsonDocument doc) {
                  T item = converter.fromDocument(doc);
                  items.add(item);
              }
        });
    } catch (Exception e) {
        logger.error("Error during bulk get", e);
    }

    return items;
}

3.2. バッチ挿入

再びRxJava’s flatMap構文を使用して、createBulkメソッドを実装します。

バルクミューテーションリクエストは、レスポンスを生成するよりも速く生成され、オーバーロード状態になることがあるため、BackpressureExceptionが検出されるたびに、指数関数的な遅延で再試行を開始します。

@Override
public void createBulk(Iterable items) {
    AsyncBucket asyncBucket = bucket.async();
    Observable
      .from(items)
      .flatMap(new Func1>() {
          @SuppressWarnings("unchecked")
          @Override
          public Observable call(final T t) {
              if(t.getId() == null) {
                  t.setId(UUID.randomUUID().toString());
              }
              JsonDocument doc = converter.toDocument(t);
              return asyncBucket.insert(doc)
                .retryWhen(RetryBuilder
                  .anyOf(BackpressureException.class)
                  .delay(Delay.exponential(TimeUnit.MILLISECONDS, 100))
                  .max(10)
                  .build());
          }
      })
      .last()
      .toBlocking()
      .single();
}

3.3. バッチ更新

updateBulkメソッドでも同様のメカニズムを使用します。

@Override
public void updateBulk(Iterable items) {
    AsyncBucket asyncBucket = bucket.async();
    Observable
      .from(items)
      .flatMap(new Func1>() {
          @SuppressWarnings("unchecked")
          @Override
          public Observable call(final T t) {
              JsonDocument doc = converter.toDocument(t);
              return asyncBucket.upsert(doc)
                .retryWhen(RetryBuilder
                  .anyOf(BackpressureException.class)
                  .delay(Delay.exponential(TimeUnit.MILLISECONDS, 100))
                  .max(10)
                  .build());
          }
      })
      .last()
      .toBlocking()
      .single();
}

3.4. バッチ削除

そして、deleteBulkメソッドを次のように記述します。

@Override
public void deleteBulk(Iterable ids) {
    AsyncBucket asyncBucket = bucket.async();
    Observable
      .from(ids)
      .flatMap(new Func1>() {
          @SuppressWarnings("unchecked")
          @Override
          public Observable call(String key) {
              return asyncBucket.remove(key)
                .retryWhen(RetryBuilder
                  .anyOf(BackpressureException.class)
                  .delay(Delay.exponential(TimeUnit.MILLISECONDS, 100))
                  .max(10)
                  .build());
          }
      })
      .last()
      .toBlocking()
      .single();
}

4. PersonCrudService

最後に、PersonエンティティのAbstractCrudServiceを拡張するSpringサービスPersonCrudServiceを記述します。

すべてのCouchbaseインタラクションは抽象クラスで実装されるため、エンティティクラスの実装は簡単です。すべての依存関係が挿入され、バケットがロードされることを確認するだけです。

@Service
public class PersonCrudService extends AbstractCrudService {

    @Autowired
    public PersonCrudService(
      @Qualifier("TutorialBucketService") BucketService bucketService,
      PersonDocumentConverter converter) {
        super(bucketService, converter);
    }

    @PostConstruct
    private void init() {
        loadBucket();
    }
}

5. 結論

このチュートリアルに示されているソースコードは、github projectで入手できます。

Couchbase Java SDKの詳細については、公式のCouchbase developer documentation siteを参照してください。