From 4f305fc34b0cd1d2146dde4a0352dacd408f3856 Mon Sep 17 00:00:00 2001 From: Ben Grabham Date: Wed, 31 Oct 2018 18:45:34 +0000 Subject: [PATCH] Cache docker container info for fast response times (500ms -> 1ms) (#18) Simple cache in front of DockerContainerInfo --- .../proxy/CachingDockerContainerInfo.java | 64 +++++++++++++++++++ .../proxy/DockerContainerInfoUtils.java | 2 +- .../docker/proxy/DockerProxyRule.java | 3 +- versions.props | 2 +- 4 files changed, 68 insertions(+), 3 deletions(-) create mode 100644 src/main/java/com/palantir/docker/proxy/CachingDockerContainerInfo.java diff --git a/src/main/java/com/palantir/docker/proxy/CachingDockerContainerInfo.java b/src/main/java/com/palantir/docker/proxy/CachingDockerContainerInfo.java new file mode 100644 index 00000000..25603000 --- /dev/null +++ b/src/main/java/com/palantir/docker/proxy/CachingDockerContainerInfo.java @@ -0,0 +1,64 @@ +/* + * Copyright 2017 Palantir Technologies, Inc. All rights reserved. + */ + +package com.palantir.docker.proxy; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; +import java.util.Optional; +import java.util.concurrent.TimeUnit; + +/** + * CachingDockerContainerInfo will cache and refresh container info. If the refresh + * fails four times in a row, the entry will be removed and the next call will return + * the exception to you if it happens again. + */ +public class CachingDockerContainerInfo implements DockerContainerInfo { + private final DockerContainerInfo delegate; + private final LoadingCache> ipForHostCache; + private final LoadingCache> hostForIpCache; + + public CachingDockerContainerInfo(DockerContainerInfo delegate) { + // It takes up to 1s to query docker so we set this to be under a multiple of 5, 10, and 15 by at least 2s + this(delegate, 53, TimeUnit.SECONDS); + } + + @VisibleForTesting + CachingDockerContainerInfo(DockerContainerInfo delegate, long refreshDuration, TimeUnit refreshUnit) { + this.delegate = delegate; + this.ipForHostCache = CacheBuilder.newBuilder() + .expireAfterWrite(4 * refreshDuration, refreshUnit) + .refreshAfterWrite(refreshDuration, refreshUnit) + .build(CacheLoader.from(hostname -> delegate.getIpForHost(hostname))); + this.hostForIpCache = CacheBuilder.newBuilder() + .expireAfterWrite(4 * refreshDuration, refreshUnit) + .refreshAfterWrite(refreshDuration / 4, refreshUnit) + .build(CacheLoader.from(hostname -> delegate.getHostForIp(hostname))); + } + + @Override + public Optional getIpForHost(String hostname) { + Optional ip = ipForHostCache.getUnchecked(hostname); + if (!ip.isPresent()) { + ipForHostCache.invalidate(hostname); + } + return ip; + } + + @Override + public Optional getHostForIp(String ip) { + Optional host = hostForIpCache.getUnchecked(ip); + if (!host.isPresent()) { + hostForIpCache.invalidate(ip); + } + return host; + } + + @Override + public String getNetworkName() { + return delegate.getNetworkName(); + } +} diff --git a/src/main/java/com/palantir/docker/proxy/DockerContainerInfoUtils.java b/src/main/java/com/palantir/docker/proxy/DockerContainerInfoUtils.java index 8e09f118..3f8abdd2 100644 --- a/src/main/java/com/palantir/docker/proxy/DockerContainerInfoUtils.java +++ b/src/main/java/com/palantir/docker/proxy/DockerContainerInfoUtils.java @@ -113,7 +113,7 @@ public static List getContainerIdsInDockerComposeProject( private static List runDockerProcess(DockerExecutable docker, String... args) throws IOException, InterruptedException { Process process = docker.execute(args); - if (!process.waitFor(5, TimeUnit.SECONDS) || process.exitValue() != 0) { + if (!process.waitFor(15, TimeUnit.SECONDS) || process.exitValue() != 0) { throw new IllegalStateException("Unable to execute docker command: " + ImmutableList.copyOf(args)); } return getLinesFromInputStream(process.getInputStream()); diff --git a/src/main/java/com/palantir/docker/proxy/DockerProxyRule.java b/src/main/java/com/palantir/docker/proxy/DockerProxyRule.java index a4416293..6fa2a150 100644 --- a/src/main/java/com/palantir/docker/proxy/DockerProxyRule.java +++ b/src/main/java/com/palantir/docker/proxy/DockerProxyRule.java @@ -40,10 +40,11 @@ public class DockerProxyRule extends ExternalResource { public DockerProxyRule( Function dockerContainerInfoCreator, Class classToLogFor) { - this.dockerContainerInfo = dockerContainerInfoCreator.apply(DockerExecutable.builder() + DockerContainerInfo builtDockerContainerInfo = dockerContainerInfoCreator.apply(DockerExecutable.builder() .dockerConfiguration(DockerMachine.localMachine().build()) .build()); String logDirectory = DockerProxyRule.class.getSimpleName() + "-" + classToLogFor.getSimpleName(); + this.dockerContainerInfo = new CachingDockerContainerInfo(builtDockerContainerInfo); this.dockerComposeRule = DockerComposeRule.builder() .file(getDockerComposeFile(this.dockerContainerInfo.getNetworkName()).getPath()) .waitingForService("proxy", Container::areAllPortsOpen) diff --git a/versions.props b/versions.props index 3e911890..30e0795b 100644 --- a/versions.props +++ b/versions.props @@ -1,4 +1,4 @@ -com.palantir.docker.compose:docker-compose-rule-junit4 = 0.33.0 +com.palantir.docker.compose:docker-compose-rule-junit4 = 0.34.0 junit:junit = 4.12 net.amygdalum:xrayinterface = 0.3.0 one.util:streamex = 0.6.3