All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.couchbase.client.java.bucket.ReplicaReader Maven / Gradle / Ivy

There is a newer version: 3.7.7
Show newest version
/*
 * Copyright (c) 2016 Couchbase, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.couchbase.client.java.bucket;

import com.couchbase.client.core.ClusterFacade;
import com.couchbase.client.core.CouchbaseException;
import com.couchbase.client.core.annotations.InterfaceAudience;
import com.couchbase.client.core.annotations.InterfaceStability;
import com.couchbase.client.core.config.CouchbaseBucketConfig;
import com.couchbase.client.core.logging.CouchbaseLogger;
import com.couchbase.client.core.logging.CouchbaseLoggerFactory;
import com.couchbase.client.core.message.cluster.GetClusterConfigRequest;
import com.couchbase.client.core.message.cluster.GetClusterConfigResponse;
import com.couchbase.client.core.message.kv.BinaryRequest;
import com.couchbase.client.core.message.kv.GetRequest;
import com.couchbase.client.core.message.kv.GetResponse;
import com.couchbase.client.core.message.kv.ReplicaGetRequest;
import com.couchbase.client.deps.io.netty.buffer.ByteBuf;
import com.couchbase.client.java.ReplicaMode;
import com.couchbase.client.java.bucket.api.Get;
import com.couchbase.client.java.bucket.api.Utils;
import com.couchbase.client.java.document.Document;
import com.couchbase.client.java.env.CouchbaseEnvironment;
import com.couchbase.client.java.error.CouchbaseOutOfMemoryException;
import com.couchbase.client.java.error.TemporaryFailureException;
import com.couchbase.client.java.transcoder.Transcoder;
import io.opentracing.Scope;
import io.opentracing.Span;
import rx.Observable;
import rx.Subscriber;
import rx.functions.Action0;
import rx.functions.Func0;
import rx.functions.Func1;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import static com.couchbase.client.java.bucket.api.Utils.addRequestSpanWithParent;
import static com.couchbase.client.java.util.OnSubscribeDeferAndWatch.deferAndWatch;

/**
 * Helper class to deal with reading from zero to N replicas and returning results.
 *
 * @author Michael Nitschinger
 * @since 2.1.4
 */
@InterfaceStability.Uncommitted
@InterfaceAudience.Private
public class ReplicaReader {

    /**
     * The logger used.
     */
    private static final CouchbaseLogger LOGGER = CouchbaseLoggerFactory.getInstance(ReplicaReader.class);

    private ReplicaReader() {}

    /**
     * Perform replica reads to as many nodes a possible based on the given {@link ReplicaMode}.
     *
     * Individual errors are swallowed, but logged.
     *
     * @param core the core reference.
     * @param id the id of the document to load from the replicas.
     * @param type the replica mode type.
     * @param bucket the name of the bucket to load it from.
     * @return a potentially empty observable with the returned raw responses.
     */
    public static > Observable read(final ClusterFacade core, final String id,
        final ReplicaMode type, final String bucket,
        final Map, Transcoder> transcoders, final Class target,
        final CouchbaseEnvironment environment, final long timeout, final TimeUnit timeUnit) {

        return Observable.defer(new Func0>() {
            @Override
            public Observable call() {
                final Span parentSpan;
                if (environment.operationTracingEnabled()) {
                    Scope scope = environment.tracer()
                      .buildSpan("get_from_replica")
                      .startActive(false);
                    parentSpan = scope.span();
                    scope.close();
                } else {
                    parentSpan = null;
                }

                Observable result = assembleRequests(core, id, type, bucket)
                  .flatMap(new Func1>() {
                      @Override
                      public Observable call(final BinaryRequest request) {
                          String name = request instanceof ReplicaGetRequest ? "get_replica" : "get";
                          addRequestSpanWithParent(environment, parentSpan, request, name);

                          Observable result = deferAndWatch(new Func1>() {
                              @Override
                              public Observable call(Subscriber subscriber) {
                                  request.subscriber(subscriber);
                                  return core.send(request);
                              }
                          }).filter(new Get.GetFilter(environment));

                          if (timeout > 0) {
                              // individual timeout to clean out ops at some point
                              result = result.timeout(timeout, timeUnit, environment.scheduler());
                          }

                          return result.onErrorResumeNext(GetResponseErrorHandler.INSTANCE)
                            .map(new Get.GetMap(environment, transcoders, target, id));
                      }
                  });

                if (timeout > 0) {
                    result = result.timeout(timeout, timeUnit, environment.scheduler());
                }

                return result.doOnTerminate(new Action0() {
                      @Override
                      public void call() {
                          if (environment.operationTracingEnabled() && parentSpan != null) {
                              environment.tracer().scopeManager()
                                .activate(parentSpan, true)
                                .close();
                          }
                      }
                  })
                  .cacheWithInitialCapacity(type.maxAffectedNodes());
            }
        });
    }

    /**
     * Helper method to assemble all possible/needed replica get requests.
     *
     * The number of configured replicas is also loaded on demand for each request. In the future, this can be
     * maybe optimized.
     *
     * @param core the core reference.
     * @param id the id of the document to load from the replicas.
     * @param type the replica mode type.
     * @param bucket the name of the bucket to load it from.
     * @return a list of requests to perform (both regular and replica get).
     */
    private static Observable assembleRequests(final ClusterFacade core, final String id,
        final ReplicaMode type, final String bucket) {
        if (type != ReplicaMode.ALL) {
            return Observable.just((BinaryRequest) new ReplicaGetRequest(id, bucket, (short) type.ordinal()));
        }

        return Observable.defer(new Func0>() {
                @Override
                public Observable call() {
                    return core.send(new GetClusterConfigRequest());
                }
            })
            .map(new Func1() {
                @Override
                public Integer call(GetClusterConfigResponse response) {
                    CouchbaseBucketConfig conf = (CouchbaseBucketConfig) response.config().bucketConfig(bucket);
                    return conf.numberOfReplicas();
                }
            })
            .flatMap(new Func1>() {
                @Override
                public Observable call(Integer max) {
                    List requests = new ArrayList();
                    requests.add(new GetRequest(id, bucket));
                    for (int i = 0; i < max; i++) {
                        requests.add(new ReplicaGetRequest(id, bucket, (short) (i + 1)));
                    }
                    return Observable.from(requests);
                }
            });
    }

    /**
     * This error handler silences all errors, but also logs them properly.
     */
    private static class GetResponseErrorHandler implements Func1> {

        public static final GetResponseErrorHandler INSTANCE = new GetResponseErrorHandler();

        @Override
        public Observable call(Throwable throwable) {
            LOGGER.info("Individual ReplicaGet failed, but ignoring. Reason: {}", throwable.toString());
            return Observable.empty();
        }
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy