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

com.couchbase.client.core.env.IoEnvironment Maven / Gradle / Ivy

There is a newer version: 2.7.0
Show newest version
/*
 * Copyright (c) 2018 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.core.env;

import com.couchbase.client.core.annotation.Stability;
import com.couchbase.client.core.deps.io.netty.channel.EventLoopGroup;
import com.couchbase.client.core.deps.io.netty.channel.epoll.Epoll;
import com.couchbase.client.core.deps.io.netty.channel.epoll.EpollEventLoopGroup;
import com.couchbase.client.core.deps.io.netty.channel.kqueue.KQueue;
import com.couchbase.client.core.deps.io.netty.channel.kqueue.KQueueEventLoopGroup;
import com.couchbase.client.core.deps.io.netty.channel.nio.NioEventLoopGroup;
import com.couchbase.client.core.deps.io.netty.util.concurrent.DefaultThreadFactory;
import com.couchbase.client.core.error.InvalidArgumentException;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.time.Duration;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;

import static com.couchbase.client.core.util.CbCollections.isNullOrEmpty;
import static com.couchbase.client.core.util.Validators.notNull;

/**
 * The {@link IoEnvironment} holds the I/O event loops and state.
 * 

* Note that this class only contains tunables and state for the I/O event loops that drive the actual operations * inside netty. If you are looking for general configuration if I/O properties, those are located inside the * {@link IoConfig} class instead. *

* By default the IO environment creates 3 distinct {@link EventLoopGroup EventLoopGroups}. One for HTTP services * (which includes query, search, analytics and views), one for the KV service and one for the management service. The * HTTP and the KV service try to occupy a "fair" number of thread pools for each. Usually it will be half the number * of reported logical CPUs by the JVM, but maximum of 8 and minimum of 2 each. The management service will only ever * occupy one thread. The HTTP and KV thread pools are split in SDK 3 (they were not in SDK 2) so that longer running * N1QL queries and larger streaming results do not interfere with high-throughput low-latency KV workloads as much. *

* By default, the SDK will use "native" event loop groups - epoll on linux and kqueue on OSX in favor of the more * generic and slower NIO transport. This should work fine out of the box nearly always, but you can disable it through * the {@link #enableNativeIo(boolean)} builder setting. *

* You cannot re-use your own event loop groups from your own application because the SDK ships a repackaged netty * version to avoid version and classpath issues. * * @since 2.0.0 */ public class IoEnvironment { static { final String samplingProperty = "com.couchbase.client.core.deps.io.netty.leakDetection.samplingInterval"; final long defaultSamplingInterval = 65536; if (isNullOrEmpty(System.getProperty(samplingProperty))) { System.setProperty(samplingProperty, Long.toString(defaultSamplingInterval)); } } /** * Native IO is enabled by default. */ public static final boolean DEFAULT_NATIVE_IO_ENABLED = true; @Stability.Internal public static final int DEFAULT_EVENT_LOOP_THREAD_COUNT = fairThreadCount(); private final boolean nativeIoEnabled; private final int eventLoopThreadCount; private final Supplier managerEventLoopGroup; private final Supplier kvEventLoopGroup; private final Supplier queryEventLoopGroup; private final Supplier analyticsEventLoopGroup; private final Supplier searchEventLoopGroup; private final Supplier viewEventLoopGroup; private final Supplier eventingEventLoopGroup; /** * Creates the {@link IoEnvironment} with default settings. * * @return the created environment. */ public static IoEnvironment create() { return builder().build(); } /** * Creates a Builder for the {@link IoEnvironment} to customize its settings. * * @return the {@link Builder} to customize the settings. */ public static IoEnvironment.Builder builder() { return new Builder(); } /** * Allows to specify a custom event loop group (I/O event loop thread pool) for the management service. *

* Note that you usually do not need to tweak the event loop for the manager service, only if you perform * long-running management queries that interfere with regular traffic. *

* Note: tweaking the dedicated event loops should be done with care and only after profiling * indicated that the default event loop setup is not achieving the desired performance characteristics. Please * see the javadoc for the {@link IoEnvironment} class for an explanation how the event loops play together for * all the services and what effect a custom pool might have. * * @param eventLoopGroup the dedicated event loop group to use. * @return this {@link Builder} for chaining purposes. */ public static Builder managerEventLoopGroup(final EventLoopGroup eventLoopGroup) { return builder().managerEventLoopGroup(eventLoopGroup); } /** * Allows to specify a custom event loop group (I/O event loop thread pool) for the management service. *

* Note: tweaking the dedicated event loops should be done with care and only after profiling * indicated that the default event loop setup is not achieving the desired performance characteristics. Please * see the javadoc for the {@link IoEnvironment} class for an explanation how the event loops play together for * all the services and what effect a custom pool might have. * * @param eventLoopGroup the dedicated event loop group to use. * @return this {@link Builder} for chaining purposes. */ public static Builder kvEventLoopGroup(final EventLoopGroup eventLoopGroup) { return builder().kvEventLoopGroup(eventLoopGroup); } /** * Allows to specify a custom event loop group (I/O event loop thread pool) for the query service. *

* Note: tweaking the dedicated event loops should be done with care and only after profiling * indicated that the default event loop setup is not achieving the desired performance characteristics. Please * see the javadoc for the {@link IoEnvironment} class for an explanation how the event loops play together for * all the services and what effect a custom pool might have. * * @param eventLoopGroup the dedicated event loop group to use. * @return this {@link Builder} for chaining purposes. */ public static Builder queryEventLoopGroup(final EventLoopGroup eventLoopGroup) { return builder().queryEventLoopGroup(eventLoopGroup); } /** * Allows to specify a custom event loop group (I/O event loop thread pool) for the analytics service. *

* Note: tweaking the dedicated event loops should be done with care and only after profiling * indicated that the default event loop setup is not achieving the desired performance characteristics. Please * see the javadoc for the {@link IoEnvironment} class for an explanation how the event loops play together for * all the services and what effect a custom pool might have. * * @param eventLoopGroup the dedicated event loop group to use. * @return this {@link Builder} for chaining purposes. */ public static Builder analyticsEventLoopGroup(final EventLoopGroup eventLoopGroup) { return builder().analyticsEventLoopGroup(eventLoopGroup); } /** * Allows to specify a custom event loop group (I/O event loop thread pool) for the search service. *

* Note: tweaking the dedicated event loops should be done with care and only after profiling * indicated that the default event loop setup is not achieving the desired performance characteristics. Please * see the javadoc for the {@link IoEnvironment} class for an explanation how the event loops play together for * all the services and what effect a custom pool might have. * * @param eventLoopGroup the dedicated event loop group to use. * @return this {@link Builder} for chaining purposes. */ public static Builder searchEventLoopGroup(final EventLoopGroup eventLoopGroup) { return builder().searchEventLoopGroup(eventLoopGroup); } /** * Allows to specify a custom event loop group (I/O event loop thread pool) for the view service. *

* Note: tweaking the dedicated event loops should be done with care and only after profiling * indicated that the default event loop setup is not achieving the desired performance characteristics. Please * see the javadoc for the {@link IoEnvironment} class for an explanation how the event loops play together for * all the services and what effect a custom pool might have. * * @param eventLoopGroup the dedicated event loop group to use. * @return this {@link Builder} for chaining purposes. */ public static Builder viewEventLoopGroup(final EventLoopGroup eventLoopGroup) { return builder().viewEventLoopGroup(eventLoopGroup); } /** * Allows to specify a custom event loop group (I/O event loop thread pool) for the eventing service. *

* Note: tweaking the dedicated event loops should be done with care and only after profiling * indicated that the default event loop setup is not achieving the desired performance characteristics. Please * see the javadoc for the {@link IoEnvironment} class for an explanation how the event loops play together for * all the services and what effect a custom pool might have. * * @param eventLoopGroup the dedicated event loop group to use. * @return this {@link Builder} for chaining purposes. */ public static Builder eventingEventLoopGroup(final EventLoopGroup eventLoopGroup) { return builder().eventingEventLoopGroup(eventLoopGroup); } /** * Overrides the number of threads used per event loop. *

* If not manually overridden, a fair thread count is calculated, see {@link #fairThreadCount()} for more * information on the heuristics. *

* Note that the count provided will only be used by event loops that the SDK creates. If you configure a custom * event loop (i.e. through {@link #kvEventLoopGroup(EventLoopGroup)}) you are responsible for sizing it * appropriately on your own. * * @param eventLoopThreadCount the number of event loops to use per pool. * @return the {@link Builder} for chaining purposes. */ public static Builder eventLoopThreadCount(int eventLoopThreadCount) { return builder().eventLoopThreadCount(eventLoopThreadCount); } /** * If set to false (enabled by default) will force using the java NIO based IO transport. *

* Usually the native transports used (epoll on linux and kqueue on OSX) are going to be faster and more efficient * than the generic NIO one. We recommend to only set this to false if you experience issues with the native * transports or instructed by couchbase support to do so for troubleshooting reasons. * * @param nativeIoEnabled if native IO should be enabled or disabled. * @return this {@link Builder} for chaining purposes. */ public static Builder enableNativeIo(boolean nativeIoEnabled) { return builder().enableNativeIo(nativeIoEnabled); } /** * Returns this environment as a map so it can be exported into i.e. JSON for display. */ @Stability.Volatile Map exportAsMap() { final Map export = new LinkedHashMap<>(); export.put("nativeIoEnabled", nativeIoEnabled); export.put("eventLoopThreadCount", eventLoopThreadCount); final Set eventLoopGroups = new HashSet<>(); eventLoopGroups.add(managerEventLoopGroup.get().getClass().getSimpleName()); eventLoopGroups.add(kvEventLoopGroup.get().getClass().getSimpleName()); eventLoopGroups.add(queryEventLoopGroup.get().getClass().getSimpleName()); eventLoopGroups.add(analyticsEventLoopGroup.get().getClass().getSimpleName()); eventLoopGroups.add(searchEventLoopGroup.get().getClass().getSimpleName()); eventLoopGroups.add(viewEventLoopGroup.get().getClass().getSimpleName()); eventLoopGroups.add(eventingEventLoopGroup.get().getClass().getSimpleName()); export.put("eventLoopGroups", eventLoopGroups); return export; } private IoEnvironment(final Builder builder) { nativeIoEnabled = builder.nativeIoEnabled; eventLoopThreadCount = builder.eventLoopThreadCount; Supplier httpDefaultGroup = null; if (builder.queryEventLoopGroup == null || builder.analyticsEventLoopGroup == null || builder.searchEventLoopGroup == null || builder.viewEventLoopGroup == null || builder.eventingEventLoopGroup == null) { httpDefaultGroup = createEventLoopGroup(nativeIoEnabled, eventLoopThreadCount, "cb-io-http"); } managerEventLoopGroup = builder.managerEventLoopGroup == null ? createEventLoopGroup(nativeIoEnabled, 1, "cb-io-manager") : builder.managerEventLoopGroup; sanityCheckEventLoop(managerEventLoopGroup); kvEventLoopGroup = builder.kvEventLoopGroup == null ? createEventLoopGroup(nativeIoEnabled, eventLoopThreadCount, "cb-io-kv") : builder.kvEventLoopGroup; sanityCheckEventLoop(kvEventLoopGroup); queryEventLoopGroup = builder.queryEventLoopGroup == null ? httpDefaultGroup : builder.queryEventLoopGroup; sanityCheckEventLoop(queryEventLoopGroup); analyticsEventLoopGroup = builder.analyticsEventLoopGroup == null ? httpDefaultGroup : builder.analyticsEventLoopGroup; sanityCheckEventLoop(analyticsEventLoopGroup); searchEventLoopGroup = builder.searchEventLoopGroup == null ? httpDefaultGroup : builder.searchEventLoopGroup; sanityCheckEventLoop(searchEventLoopGroup); viewEventLoopGroup = builder.viewEventLoopGroup == null ? httpDefaultGroup : builder.viewEventLoopGroup; sanityCheckEventLoop(viewEventLoopGroup); eventingEventLoopGroup = builder.eventingEventLoopGroup == null ? httpDefaultGroup : builder.eventingEventLoopGroup; sanityCheckEventLoop(eventingEventLoopGroup); } /** * Helper method to check if the event loop group is allowed with the current setup. * * @param group the group to check. */ private void sanityCheckEventLoop(final Supplier group) { if (!nativeIoEnabled && !(group.get() instanceof NioEventLoopGroup)) { throw InvalidArgumentException.fromMessage("Native IO is disabled and the EventLoopGroup is not a NioEventLoopGroup"); } } /** * Returns the {@link EventLoopGroup} to be used for config traffic. * * @return the selected event loop group. */ public Supplier managerEventLoopGroup() { return managerEventLoopGroup; } /** * Returns the {@link EventLoopGroup} to be used for Key/Value traffic. * * @return the selected event loop group. */ public Supplier kvEventLoopGroup() { return kvEventLoopGroup; } /** * Returns the {@link EventLoopGroup} to be used for N1QL Query traffic. * * @return the selected event loop group. */ public Supplier queryEventLoopGroup() { return queryEventLoopGroup; } /** * Returns the {@link EventLoopGroup} to be used for analytics traffic. * * @return the selected event loop group. */ public Supplier analyticsEventLoopGroup() { return analyticsEventLoopGroup; } /** * Returns the {@link EventLoopGroup} to be used for search traffic. * * @return the selected event loop group. */ public Supplier searchEventLoopGroup() { return searchEventLoopGroup; } /** * Returns the {@link EventLoopGroup} to be used for view traffic. * * @return the selected event loop group. */ public Supplier viewEventLoopGroup() { return viewEventLoopGroup; } /** * Returns the {@link EventLoopGroup} to be used for eventing traffic. * * @return the selected event loop group. */ public Supplier eventingEventLoopGroup() { return eventingEventLoopGroup; } /** * Returns true if native IO is enabled and can be used if supported. * * @return true if enabled. */ public boolean nativeIoEnabled() { return nativeIoEnabled; } /** * Returns the thread count per event loop. */ public int eventLoopThreadCount() { return eventLoopThreadCount; } /** * Instructs all the owned event loops to shut down. * * @param timeout the maximum amount of time to wait before returning back control. * @return a mono that completes once finished. */ public Mono shutdown(final Duration timeout) { return Flux.merge( shutdownGroup(managerEventLoopGroup, timeout), shutdownGroup(kvEventLoopGroup, timeout), shutdownGroup(queryEventLoopGroup, timeout), shutdownGroup(analyticsEventLoopGroup, timeout), shutdownGroup(searchEventLoopGroup, timeout), shutdownGroup(viewEventLoopGroup, timeout), shutdownGroup(eventingEventLoopGroup, timeout) ).then(); } /** * Helper method to shut down an individual event loop group. * * @param groupSupplier the event loop group potentially to be shutdown. * @param timeout the maximum time to wait until shutdown abort. * @return a mono indicating completion. */ private static Mono shutdownGroup(final Supplier groupSupplier, final Duration timeout) { if (groupSupplier instanceof OwnedSupplier) { EventLoopGroup group = groupSupplier.get(); if (!group.isShutdown() && !group.isShuttingDown()) { return Mono.create(sink -> group.shutdownGracefully(0, timeout.toMillis(), TimeUnit.MILLISECONDS) .addListener(future -> { if (future.isSuccess()) { sink.success(); } else { sink.error(future.cause()); } }) ); } } return Mono.empty(); } /** * Helper method to select the best event loop group type based on the features * available on the current platform. * *

If KQueue or Epoll native transports are available, it will use those. If not * there is always the fallback to the Nio transport which is always available.

* * @param nativeIoEnabled native IO enabled. * @param numThreads number of threads to to assign to the group. * @param poolName the name of the threads. * @return the created group. */ private static OwnedSupplier createEventLoopGroup(final boolean nativeIoEnabled, final int numThreads, final String poolName) { final ThreadFactory threadFactory = new DefaultThreadFactory(poolName, true); if (nativeIoEnabled && Epoll.isAvailable()) { return new OwnedSupplier<>(new EpollEventLoopGroup(numThreads, threadFactory)); } else if (nativeIoEnabled && KQueue.isAvailable()) { return new OwnedSupplier<>(new KQueueEventLoopGroup(numThreads, threadFactory)); } else { return new OwnedSupplier<>(new NioEventLoopGroup(numThreads, threadFactory)); } } /** * Picks a "fair" and sensible thread count, for use with a default event * loop size. * *

It is kinda hard to figure out how big the event loop pools should be, but for * now we run with the following algorithm: use half the virtual core size, but a minimum * of 2 and a maximum of 8. This can always be overridden by the user if needed.

* * @return the number of threads deemed to be fair for the current system. */ private static int fairThreadCount() { int cores = Runtime.getRuntime().availableProcessors() / 2; cores = Math.max(cores, 2); cores = Math.min(cores, 8); return cores; } public static class Builder { private boolean nativeIoEnabled = DEFAULT_NATIVE_IO_ENABLED; private Supplier managerEventLoopGroup = null; private Supplier kvEventLoopGroup = null; private Supplier queryEventLoopGroup = null; private Supplier analyticsEventLoopGroup = null; private Supplier searchEventLoopGroup = null; private Supplier viewEventLoopGroup = null; private Supplier eventingEventLoopGroup = null; private int eventLoopThreadCount = DEFAULT_EVENT_LOOP_THREAD_COUNT; /** * Allows to specify a custom event loop group (I/O event loop thread pool) for the management service. *

* Note that you usually do not need to tweak the event loop for the manager service, only if you perform * long-running management queries that interfere with regular traffic. *

* Note: tweaking the dedicated event loops should be done with care and only after profiling * indicated that the default event loop setup is not achieving the desired performance characteristics. Please * see the javadoc for the {@link IoEnvironment} class for an explanation how the event loops play together for * all the services and what effect a custom pool might have. * * @param eventLoopGroup the dedicated event loop group to use. * @return this {@link Builder} for chaining purposes. */ public Builder managerEventLoopGroup(final EventLoopGroup eventLoopGroup) { this.managerEventLoopGroup = checkEventLoopGroup(eventLoopGroup); return this; } /** * Allows to specify a custom event loop group (I/O event loop thread pool) for the KV service. *

* Note: tweaking the dedicated event loops should be done with care and only after profiling * indicated that the default event loop setup is not achieving the desired performance characteristics. Please * see the javadoc for the {@link IoEnvironment} class for an explanation how the event loops play together for * all the services and what effect a custom pool might have. * * @param eventLoopGroup the dedicated event loop group to use. * @return this {@link Builder} for chaining purposes. */ public Builder kvEventLoopGroup(final EventLoopGroup eventLoopGroup) { this.kvEventLoopGroup = checkEventLoopGroup(eventLoopGroup); return this; } /** * Allows to specify a custom event loop group (I/O event loop thread pool) for the query service. *

* Note: tweaking the dedicated event loops should be done with care and only after profiling * indicated that the default event loop setup is not achieving the desired performance characteristics. Please * see the javadoc for the {@link IoEnvironment} class for an explanation how the event loops play together for * all the services and what effect a custom pool might have. * * @param eventLoopGroup the dedicated event loop group to use. * @return this {@link Builder} for chaining purposes. */ public Builder queryEventLoopGroup(final EventLoopGroup eventLoopGroup) { this.queryEventLoopGroup = checkEventLoopGroup(eventLoopGroup); return this; } /** * Allows to specify a custom event loop group (I/O event loop thread pool) for the analytics service. *

* Note: tweaking the dedicated event loops should be done with care and only after profiling * indicated that the default event loop setup is not achieving the desired performance characteristics. Please * see the javadoc for the {@link IoEnvironment} class for an explanation how the event loops play together for * all the services and what effect a custom pool might have. * * @param eventLoopGroup the dedicated event loop group to use. * @return this {@link Builder} for chaining purposes. */ public Builder analyticsEventLoopGroup(final EventLoopGroup eventLoopGroup) { this.analyticsEventLoopGroup = checkEventLoopGroup(eventLoopGroup); return this; } /** * Allows to specify a custom event loop group (I/O event loop thread pool) for the search service. *

* Note: tweaking the dedicated event loops should be done with care and only after profiling * indicated that the default event loop setup is not achieving the desired performance characteristics. Please * see the javadoc for the {@link IoEnvironment} class for an explanation how the event loops play together for * all the services and what effect a custom pool might have. * * @param eventLoopGroup the dedicated event loop group to use. * @return this {@link Builder} for chaining purposes. */ public Builder searchEventLoopGroup(final EventLoopGroup eventLoopGroup) { this.searchEventLoopGroup = checkEventLoopGroup(eventLoopGroup); return this; } /** * Allows to specify a custom event loop group (I/O event loop thread pool) for the eventing service. *

* Note: tweaking the dedicated event loops should be done with care and only after profiling * indicated that the default event loop setup is not achieving the desired performance characteristics. Please * see the javadoc for the {@link IoEnvironment} class for an explanation how the event loops play together for * all the services and what effect a custom pool might have. * * @param eventLoopGroup the dedicated event loop group to use. * @return this {@link Builder} for chaining purposes. */ public Builder eventingEventLoopGroup(final EventLoopGroup eventLoopGroup) { this.eventingEventLoopGroup = checkEventLoopGroup(eventLoopGroup); return this; } /** * Allows to specify a custom event loop group (I/O event loop thread pool) for the view service. *

* Note: tweaking the dedicated event loops should be done with care and only after profiling * indicated that the default event loop setup is not achieving the desired performance characteristics. Please * see the javadoc for the {@link IoEnvironment} class for an explanation how the event loops play together for * all the services and what effect a custom pool might have. * * @param eventLoopGroup the dedicated event loop group to use. * @return this {@link Builder} for chaining purposes. */ public Builder viewEventLoopGroup(final EventLoopGroup eventLoopGroup) { this.viewEventLoopGroup = checkEventLoopGroup(eventLoopGroup); return this; } /** * Helper method to perform validation on the event loop group passed in. * * @param eventLoopGroup the event loop group to check. * @return the created external supplier. */ private static Supplier checkEventLoopGroup(final EventLoopGroup eventLoopGroup) { return new ExternalSupplier<>(notNull(eventLoopGroup, "EventLoopGroup")); } /** * If set to false (enabled by default) will force using the java NIO based IO transport. *

* Usually the native transports used (epoll on linux and kqueue on OSX) are going to be faster and more efficient * than the generic NIO one. We recommend to only set this to false if you experience issues with the native * transports or instructed by couchbase support to do so for troubleshooting reasons. * * @param nativeIoEnabled if native IO should be enabled or disabled. * @return this {@link Builder} for chaining purposes. */ public Builder enableNativeIo(final boolean nativeIoEnabled) { this.nativeIoEnabled = nativeIoEnabled; return this; } /** * Overrides the number of threads used per event loop. *

* If not manually overridden, a fair thread count is calculated, see {@link #fairThreadCount()} for more * information on the heuristics. *

* Note that the count provided will only be used by event loops that the SDK creates. If you configure a custom * event loop (i.e. through {@link #kvEventLoopGroup(EventLoopGroup)}) you are responsible for sizing it * appropriately on your own. * * @param eventLoopThreadCount the number of event loops to use per pool. * @return this {@link Builder} for chaining purposes. */ public Builder eventLoopThreadCount(final int eventLoopThreadCount) { if (eventLoopThreadCount < 1) { throw InvalidArgumentException.fromMessage("EventLoopThreadCount cannot be smaller than 1"); } this.eventLoopThreadCount = eventLoopThreadCount; return this; } @Stability.Internal public IoEnvironment build() { return new IoEnvironment(this); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy